from tensorflow import keras

import numpy

x = numpy.array([0, 1, 2, 3, 4]) 
y = x * 2 + 1

model = keras.models.Sequential()
model.add(keras.layers.Dense(1,input_shape=(1,)))
model.compile('SGD', 'mse')

model.fit(x[:2], y[:2], epochs=1000, verbose=0)
print(model.predict(x))

"""
Explanation: Ex 1-1 by Keras in Tensflow 2.0
Keras가 이제 텐서플로의 기본 상위 인터페이스가 되었다. 다시 말해 텐서플로에서 인공지능 코드 작성시 케라스를 기본적으로 사용할 수 있게 되었다는 말이다. 
Keras를 텐서플로에서 사용하는 방법은 크게 두가지가 있다. 첫 번째는 오리지널 케라스 방식처럼 케라스를 주 인터페이스로 사용하고 텐서풀로를 백앤드 인공지능 엔진으로 사용하는 방법이다. 이를 텐서플로 2.0 기반 케라스 사용법(Keras in Tensorflow 2.0)이라 하자. 두 번째는 텐서플로로 인공지능 코드를 작성할 때 케라스를 이용하는 방법이다. 이를 케라스 인터페이스를 사용하는 텐서플로 2.0 사용법(Tensorflow 2.0 with Keras IO)이라 하자. 본 책의 9장에도 그와 유사한 접근이 소개되어 있지만 그 때는 둘을 섞어서 사용하는 정도였고 이번에는 기본적으로 텐서플로내에서 케라스를 지원하는 단계가 되었기 때문에 훨씬 편리하고 강력하게 둘이 융합되었다. 첫 번째 방법은 편리함에 방점이 있고 두 번째 방법은 강력함에 방점이 있다. 여기서는 두 가지 방법을 모두 소개한다. 
I. 텐서플로 2.0 기반 케라스 사용법(Keras in Tensorflow 2.0)
End of explanation
"""


import tensorflow as tf2

import numpy as np

x = np.array([0, 1, 2, 3, 4]).astype('float32').reshape(-1,1) 
y = x * 2 + 1

model = tf2.keras.Sequential()
model.add(tf2.keras.layers.Dense(1, input_dim = 1))
model.build()

Optimizer = tf2.keras.optimizers.Adam(learning_rate = 0.01)
for epoch in range(1000):
    with tf2.GradientTape() as tape:
        y_pr = model(x[:2,:1])
        loss = tf2.keras.losses.mean_squared_error(y[:2,:1], y_pr)
    gradients = tape.gradient(loss, model.trainable_variables)
    Optimizer.apply_gradients(zip(gradients, model.trainable_variables))

print(model.predict(x))

"""
Explanation: II. 케라스 인터페이스를 사용하는 텐서플로 2.0 사용법(Tensorflow 2.0 with Keras IO)
간단한 구성
End of explanation
"""


import tensorflow as tf2

import numpy as np

x = np.array([0, 1, 2, 3, 4]).astype('float32').reshape(-1,1) 
y = x * 2 + 1

model = tf2.keras.Sequential()
model.add(tf2.keras.layers.Dense(1, input_dim = 1))
model.build()

print('w=', model.trainable_variables[0].numpy(), 'b=', model.trainable_variables[1].numpy())
print()

Optimizer = tf2.keras.optimizers.Adam(learning_rate = 0.01)
for epoch in range(1000):
    with tf2.GradientTape() as tape:
        y_pr = model(x[:2,:1])
        loss = tf2.keras.losses.mean_squared_error(y[:2,:1], y_pr)
        if epoch < 3: 
            print(f'Epoch:{epoch}')
            print('y_pr:', y_pr.numpy())
            print('y_tr:', y[:2,:1])
            print('loss:', loss.numpy())
            print()
    gradients = tape.gradient(loss, model.trainable_variables)
    Optimizer.apply_gradients(zip(gradients, model.trainable_variables))
print(model.predict(x))

"""
Explanation: 간단한 구성에 진행 결과 보이기
End of explanation
"""


import tensorflow as tf2
from tensorflow import keras

import numpy as np

x = np.array([0, 1, 2, 3, 4]).astype('float32').reshape(-1,1) 
y = x * 2 + 1

class Model(keras.models.Model):
    def __init__(self):
        super().__init__()
        # self.layer = keras.layers.Dense(1, input_shape=[None,1])
        self.layer = keras.layers.Dense(1, input_dim=1)
    def call(self, x):
        return self.layer(x)
model = Model()

Optimizer = tf2.keras.optimizers.Adam(learning_rate = 0.01)
for epoch in range(1000):
    with tf2.GradientTape() as tape:
        y_pr = model(x[:2,:1])
        loss = tf2.keras.losses.mean_squared_error(y[:2,:1], y_pr)
    gradients = tape.gradient(loss, model.trainable_variables)
    Optimizer.apply_gradients(zip(gradients, model.trainable_variables))

print(model.predict(x))

"""
Explanation: 클래스를 이용한 네트웍 모델 구성하기
케라스로 모델을 만들 때 클래스를 이용해 만들 수 있다. 파이토치에서 사용하는 방법이지만 케라스에서도 사용이 가능하다. 이 경우는 뉴럴넷의 각 계층의 구성과 계층간의 연결을 구분해서 작성이 가능해 이해에 도움이 되어 복잡한 네트웍 구성시 도움이 되는 방법이다. 여기서는 클래스를 사용해서 케라스 모델을 만드는 방법을 사용한다. 파이토치 구현을 이해하고 케라스 구현과 비교하는데도 도움이 되리라 보인다.
모델을 model = Model()로 구성하고 별도의 컴파일이나 빌드 과정이 없다. 텐서플로 방식으로 사용하는 경우는 케라스가 모델을 처음 사용하는 시점에서 자동으로 구성을 하기 때문이다. 여기서는 y_pr = model(x[:2,:1])을 수행하는 시점이다. 그 전에는 모델이 빌드되지 않았기 때문에 구조를 model.summary()로 확인할 수 없다. 그렇지만 모델이 사용된 후에는 구성을 볼 수 있다. 이점은 컴파일 과정이 반드시 수반되어야 하는 케라스로 만 사용하는 경우와 다르다. 그 경우는 컴파일을 하고 나면 네트웍 구조를 확인 할 수 있게 된다.
End of explanation
"""


