DLD(Daejeon Learning Day) 2020을 위해 작성된 노트북입니다.
import tensorflow as tf
tf.__version__
'2.6.0'
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()
X_train = X_train.reshape(-1, 784) / 255.
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz 11493376/11490434 [==============================] - 0s 0us/step 11501568/11490434 [==============================] - 0s 0us/step
X_train.shape
(60000, 784)
Sequential()
클래스와 함수형 API의 관계¶Sequential()
:
시퀀셜 모델에 10개의 유닛을 가진 완전 연결 층을 추가합니다.
seq_model = tf.keras.Sequential()
seq_model.add(tf.keras.layers.Dense(units=10,
activation='softmax',
input_shape=(784,)))
seq_model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (None, 10) 7850 ================================================================= Total params: 7,850 Trainable params: 7,850 Non-trainable params: 0 _________________________________________________________________
seq_model.compile(loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
seq_model.fit(X_train, y_train, batch_size=32, epochs=2)
Epoch 1/2 1875/1875 [==============================] - 4s 2ms/step - loss: 0.4404 - accuracy: 0.8830 Epoch 2/2 1875/1875 [==============================] - 3s 2ms/step - loss: 0.3028 - accuracy: 0.9154
<keras.callbacks.History at 0x7fa70b327a90>
함수형 API를 사용할 때는 Input()
을 사용해 입력의 크기를 정의해야 합니다. 하지만 InputLayer
층이 추가되어 있습니다.
inputs = tf.keras.layers.Input(784)
outputs = tf.keras.layers.Dense(units=10,
activation='softmax')(inputs) # __call()__ 메서드 호출
# dense = tf.keras.layers.Dense(units=10, activation='softmax')
# outputs = dense(inputs)
func_model = tf.keras.Model(inputs, outputs)
func_model.summary()
Model: "model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) [(None, 784)] 0 _________________________________________________________________ dense_1 (Dense) (None, 10) 7850 ================================================================= Total params: 7,850 Trainable params: 7,850 Non-trainable params: 0 _________________________________________________________________
func_model.compile(loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
func_model.fit(X_train, y_train, batch_size=32, epochs=2)
Epoch 1/2 1875/1875 [==============================] - 3s 1ms/step - loss: 0.4453 - accuracy: 0.8799 Epoch 2/2 1875/1875 [==============================] - 3s 2ms/step - loss: 0.3022 - accuracy: 0.9156
<keras.callbacks.History at 0x7fa70b2b8590>
Input
의 정체는 무엇일까요? 이 함수는 InputLayer
클래스의 객체를 만들어 그 결과를 반환합니다.
type(tf.keras.layers.Input)
function
사실 신경망의 입력층은 입력 그 자체입니다. InputLayer
객체의 입력 노드 출력을 그대로 Dense
층에 주입할 수 있습니다. 모든 층은 입력과 출력 노드를 정의합니다.
# inputs = tf.keras.layers.Input(784)
input_layer = tf.keras.layers.InputLayer(784)
inputs = input_layer._inbound_nodes[0].outputs
outputs = tf.keras.layers.Dense(units=10,
activation='softmax')(inputs)
input_layer_model = tf.keras.Model(inputs, outputs)
input_layer_model.summary()
Model: "model_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_2 (InputLayer) [(None, 784)] 0 _________________________________________________________________ dense_2 (Dense) (None, 10) 7850 ================================================================= Total params: 7,850 Trainable params: 7,850 Non-trainable params: 0 _________________________________________________________________
input_layer_model.compile(loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
input_layer_model.fit(X_train, y_train, batch_size=32, epochs=2)
Epoch 1/2 1875/1875 [==============================] - 3s 1ms/step - loss: 0.4364 - accuracy: 0.8832 Epoch 2/2 1875/1875 [==============================] - 3s 1ms/step - loss: 0.3020 - accuracy: 0.9161
<keras.callbacks.History at 0x7fa70b2b5f50>
함수형 API를 사용한 모델은 layers
속성에 InputLayer
클래스를 포함합니다.
func_model.layers
[<keras.engine.input_layer.InputLayer at 0x7fa709a39410>, <keras.layers.core.Dense at 0x7fa70997ee90>]
하지만 시퀀셜 모델은 layers
속성에 InputLayer
클래스가 보이지 않습니다.
seq_model.layers
[<keras.layers.core.Dense at 0x7fa70f861310>]
모델은 감춰진 _self_tracked_trackables
속성이 또 있습니다. 여기에서 InputLayer
클래스를 확인할 수 있습니다(텐서플로 2.5 이전 버전에서는 _layers
속성을 사용합니다).
seq_model._self_tracked_trackables
[<keras.engine.input_layer.InputLayer at 0x7fa70e41a6d0>, <keras.layers.core.Dense at 0x7fa70f861310>]
또는 _input_layers
속성에서도 확인할 수 있습니다.
seq_model._input_layers, func_model._input_layers
([<keras.engine.input_layer.InputLayer at 0x7fa70e41a6d0>], [<keras.engine.input_layer.InputLayer at 0x7fa709a39410>])
seq_model._output_layers, func_model._output_layers
([<keras.layers.core.Dense at 0x7fa70f861310>], [<keras.layers.core.Dense at 0x7fa70997ee90>])
Model
클래스로 만든 func_model
은 사실 Functional
클래스의 객체입니다. Model
클래스는 서브클래싱에 사용합니다.
func_model.__class__
keras.engine.functional.Functional
시퀀셜 모델은 함수형 모델의 특별한 경우입니다. (Model
--> Functional
--> Sequential
)
tf.layers.Layer
클래스를 상속하고 build()
메서드에서 가중치를 만든다음 call()
메서드에서 연산을 구현합니다.
class MyDense(tf.keras.layers.Layer):
def __init__(self, units, activation=None, **kwargs):
# units와 activation 매개변수 외에 나머지 변수를 부모 클래스의 생성자로 전달합니다.
super(MyDense, self).__init__(**kwargs)
self.units = units
# 문자열로 미리 정의된 활성화 함수를 선택합니다. e.g., 'softmax', 'relu'
self.activation = tf.keras.activations.get(activation)
def build(self, input_shape):
# __call__() 메서드를 호출할 때 호출됩니다. 가중치 생성을 지연합니다.
# 가중치와 절편을 생성합니다.
self.kernel = self.add_weight(name='kernel',
shape=[input_shape[-1], self.units],
initializer='glorot_uniform' # 케라스의 기본 초기화
)
self.bias = self.add_weight(name='bias',
shape=[self.units],
initializer='zeros')
def call(self, inputs): # training=None은 training은 배치 정규화나 드롭아웃 같은 경우 사용
# __call__() 메서드를 호출할 때 호출됩니다.
# 실제 연산을 수행합니다. [batch_size, units]
z = tf.matmul(inputs, self.kernel) + self.bias
if self.activation:
return self.activation(z)
return z
inputs = tf.keras.layers.Input(784)
# Layer.__call__() --> MyDense().build() --> Layer.build() --> MyDense().call()
outputs = MyDense(units=10, activation='softmax')(inputs)
my_dense_model = tf.keras.Model(inputs, outputs)
my_dense_model.summary()
Model: "model_2" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_3 (InputLayer) [(None, 784)] 0 _________________________________________________________________ my_dense (MyDense) (None, 10) 7850 ================================================================= Total params: 7,850 Trainable params: 7,850 Non-trainable params: 0 _________________________________________________________________
my_dense_model.compile(loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
my_dense_model.fit(X_train, y_train, batch_size=32, epochs=2)
Epoch 1/2 1875/1875 [==============================] - 3s 2ms/step - loss: 0.4409 - accuracy: 0.8838 Epoch 2/2 1875/1875 [==============================] - 3s 1ms/step - loss: 0.3026 - accuracy: 0.9166
<keras.callbacks.History at 0x7fa70aeb0c90>
# fit(), compile(), predict(), evaluate() 등의 메서드 제공
class MyModel(tf.keras.Model):
def __init__(self):
super(MyModel, self).__init__()
self.output_layer = MyDense(units=10, activation='softmax')
def call(self, inputs):
return self.output_layer(inputs)
my_model = MyModel()
my_model.compile(loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
my_model.fit(X_train, y_train, batch_size=32, epochs=2)
Epoch 1/2 1875/1875 [==============================] - 3s 2ms/step - loss: 0.4376 - accuracy: 0.8830 Epoch 2/2 1875/1875 [==============================] - 3s 1ms/step - loss: 0.3024 - accuracy: 0.9157
<keras.callbacks.History at 0x7fa70adf0b50>
class MyCustomStep(MyModel):
def train_step(self, data):
# fit()에서 전달된 데이터
x, y = data
# 그레이디언트 기록 시작
with tf.GradientTape() as tape:
# 정방향 계산
y_pred = self(x)
# compile() 메서드에서 지정한 손실 계산
loss = self.compiled_loss(y, y_pred)
# 훈련가능한 파라미터에 대한 그레이디언트 계산
gradients = tape.gradient(loss, self.trainable_variables)
# 파라미터 업데이트
self.optimizer.apply_gradients(zip(gradients, self.trainable_variables))
# TF 2.4에서는
# self.optimizer.minimize(loss, self.trainable_variables, tape=tape)
# compile() 메서드에서 지정한 지표 계산
self.compiled_metrics.update_state(y, y_pred)
# 현재까지 지표와 결괏값을 딕셔너리로 반환
return {m.name: m.result() for m in self.metrics}
my_custom_step = MyCustomStep()
my_custom_step.compile(loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
my_custom_step.fit(X_train, y_train, batch_size=32, epochs=2)
Epoch 1/2 1875/1875 [==============================] - 4s 2ms/step - loss: 0.4370 - accuracy: 0.8837 Epoch 2/2 1875/1875 [==============================] - 3s 2ms/step - loss: 0.3023 - accuracy: 0.9158
<keras.callbacks.History at 0x7fa70ade5fd0>