감사말: 프랑소와 숄레의 Deep Learning with Python, Second Edition 3장에 사용된 코드에 대한 설명을 담고 있으며 텐서플로우 2.6 버전에서 작성되었습니다. 소스코드를 공개한 저자에게 감사드립니다.
구글 코랩 설정: '런타임 -> 런타임 유형 변경' 메뉴에서 GPU를 지정한다. 이후 아래 명령어를 실행했을 때 오류가 발생하지 않으면 필요할 때 GPU가 자동 사용된다.
!nvidia-smi
구글 코랩에서 사용되는 tensorflow 버전을 확인하려면 아래 명령문을 실행한다.
import tensorflow as tf
tf.__version__
tensorflow가 GPU를 사용하는지 여부를 알고 싶으면 주피터 노트북 등 사용하는 편집기 및 파이썬 터미널에서 아래 명령문을 실행한다.
import tensorflow as tf
tf.config.list_physical_devices('GPU')
GradientTape
) 이용import tensorflow as tf
상수 텐서는 한 번 생성되면 값을 수정할 수 없다. 딥러닝 연산에 많이 사용되는 상수 텐서는 다음과 같다.
x = tf.constant([[1., 2.], [3., 4.]])
print(x)
tf.Tensor( [[1. 2.] [3. 4.]], shape=(2, 2), dtype=float32)
x = tf.ones(shape=(2, 1))
print(x)
tf.Tensor( [[1.] [1.]], shape=(2, 1), dtype=float32)
x = tf.zeros(shape=(2, 1))
print(x)
tf.Tensor( [[0.] [0.]], shape=(2, 1), dtype=float32)
normal()
함수: 0과 1사이의 부동소수점을 정규분포를 따르도록 무작위적으로 선택mean
: 평균값stddev
: 표준편차x = tf.random.normal(shape=(3, 1), mean=0., stddev=1.)
print(x)
tf.Tensor( [[0.4956642 ] [0.7919767 ] [0.67300016]], shape=(3, 1), dtype=float32)
uniform()
함수: 지정된 구간에서 부동소수점을 균등분포를 따르도록 무작위적으로 선택minval
: 구간 최솟값maxval
: 구간 최댓값x = tf.random.uniform(shape=(3, 1), minval=0., maxval=1.)
print(x)
tf.Tensor( [[0.2889459 ] [0.7924731 ] [0.12908947]], shape=(3, 1), dtype=float32)
한 번 생성된 상수 텐서는 수정이 불가능하다.
>>> x[0, 0] = 1.0
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-7-242a5d4d3c4a> in <module>
----> 1 x[0, 0] = 1.0
TypeError: 'tensorflow.python.framework.ops.EagerTensor' object does not support item assignment
x[0, 0]
<tf.Tensor: shape=(), dtype=float32, numpy=0.2889459>
type(x[0, 0])
tensorflow.python.framework.ops.EagerTensor
넘파이 어레이는 반면에 수정 가능하다.
import numpy as np
x = np.ones(shape=(2, 2))
x[0, 0] = 0.0
print(x)
[[0. 1.] [1. 1.]]
신경망 모델 훈련 도중에 가중치 텐서는 업데이트될 수 있어야 한다.
이런 텐서는 변수 텐서로 선언해야 하며,
Variaible
클래스로 감싼다.
v = tf.Variable(initial_value=tf.random.normal(shape=(3, 1)))
print(v)
<tf.Variable 'Variable:0' shape=(3, 1) dtype=float32, numpy= array([[ 0.79358006], [ 2.0038128 ], [-1.7179965 ]], dtype=float32)>
Variable
클래스의 assign()
메서드를 활용하여 텐서 항목 전체 또는
일부를 수정할 수 있다.
v.assign(tf.ones((3, 1)))
<tf.Variable 'UnreadVariable' shape=(3, 1) dtype=float32, numpy= array([[1.], [1.], [1.]], dtype=float32)>
주의사항: 모양(shape)이 동일한 텐서를 사용해야 한다.
>>> v.assign(tf.ones((3, 2)))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-13-e381ab0c94e6> in <module>
----> 1 v.assign(tf.ones((3, 2)))
~\anaconda3\lib\site-packages\tensorflow\python\ops\resource_variable_ops.py in assign(self, value, use_locking, name, read_value)
886 else:
887 tensor_name = " " + str(self.name)
--> 888 raise ValueError(
889 ("Cannot assign to variable%s due to variable shape %s and value "
890 "shape %s are incompatible") %
ValueError: Cannot assign to variable Variable:0 due to variable shape (3, 1) and value shape (3, 2) are incompatible
특정 항목을 수정하려면 인덱싱과 함께 사용한다.
v[0, 0]
<tf.Tensor: shape=(), dtype=float32, numpy=1.0>
type(v[0, 0])
tensorflow.python.framework.ops.EagerTensor
상수 텐서의 경우처럼 각 항목은 EagerTensor
객체이지만 이번엔 항목 변환이 가능하다.
텐서플로우 내부에서 변수 텐서인 경우와 아닌 경우를 구분해서 assign()
메서드의 지원여부를
판단하는 것으로 보인다.
v[0, 0].assign(3.)
<tf.Variable 'UnreadVariable' shape=(3, 1) dtype=float32, numpy= array([[3.], [1.], [1.]], dtype=float32)>
assign_add()
메서드는 변수 텐서에 대한 덧셈 연산을 수행한다.
단, 해당 객체의 항목이 업데이트된다.
v.assign_add(tf.ones((3, 1)))
<tf.Variable 'UnreadVariable' shape=(3, 1) dtype=float32, numpy= array([[4.], [2.], [2.]], dtype=float32)>
텐서 연산은 넘파이 어레이에 대한 연산과 거의 같다. 다음은 몇 가지 예제를 보여준다.
참고: 보다 다양한 텐서와 텐서 연산에 대한 자세한 설명은 텐서플로우 가이드(TensorFlow Guide) 영어판을 참고하라.
import numpy as np
a = np.array([[2., 1.],
[3., -2.]], dtype=np.float32)
a = tf.convert_to_tensor(a)
a += tf.ones((2, 2))
print(a)
tf.Tensor( [[ 3. 2.] [ 4. -1.]], shape=(2, 2), dtype=float32)
b = tf.square(a)
print(b)
tf.Tensor( [[ 9. 4.] [16. 1.]], shape=(2, 2), dtype=float32)
c = tf.sqrt(a)
print(c)
tf.Tensor( [[1.7320508 1.4142135] [2. nan]], shape=(2, 2), dtype=float32)
a = tf.nn.relu(a)
print(a)
tf.Tensor( [[3. 2.] [4. 0.]], shape=(2, 2), dtype=float32)
c = tf.sqrt(a)
print(c)
tf.Tensor( [[1.7320508 1.4142135] [2. 0. ]], shape=(2, 2), dtype=float32)
d = b + c
print(d)
tf.Tensor( [[10.732051 5.4142137] [18. 1. ]], shape=(2, 2), dtype=float32)
matmul()
곱셈은 넘파이의 점곱(dot) 연산자와 유사하게 작동하며
2차원 행렬모양의 텐서의 경우 행렬 곱으로 실행된다.
e = tf.matmul(a, b)
print(e)
tf.Tensor( [[59. 14.] [36. 16.]], shape=(2, 2), dtype=float32)
곱셈 연산자(*
)는 항목별 곱셈으로 처리된다.
e *= a
print(e)
tf.Tensor( [[177. 28.] [144. 0.]], shape=(2, 2), dtype=float32)
GradientTape
API (다시 살펴 보기)¶그레이디언트 테이프는 텐서 변수에 의존하는 미분함수의 그레이디언트를 자동 계산해준다. 아래 코드는 제곱 함수의 미분을 계산한다.
$$ f(x) = x^2 \quad \Longrightarrow \quad \nabla f(x) = \frac{df(x)}{dx} = 2x $$input_var = tf.Variable(initial_value=3.)
with tf.GradientTape() as tape:
result = tf.square(input_var)
gradient = tape.gradient(result, input_var)
print(gradient)
tf.Tensor(6.0, shape=(), dtype=float32)
그레이디언트 테이프 기능을 이용하여 신경망 모델 훈련 중에 손실 함수의 그레이디언트를 계산한다.
gradient = tape.gradient(loss, weights)
loss
: weights
텐서 변수에 의존하는 손실 함수weights
: 가중치 어레이상수 텐서에 대해 그레이디언트 테이프를 이용하려면 tape.watch()
메서드로 감싸야 한다.
input_const = tf.constant(3.)
with tf.GradientTape() as tape:
tape.watch(input_const)
result = tf.square(input_const)
gradient = tape.gradient(result, input_const)
print(gradient)
tf.Tensor(6.0, shape=(), dtype=float32)
참고: 2차 미분도 가능하지만 여기서는 관심 대상이 아니다.
time = tf.Variable(0.)
with tf.GradientTape() as outer_tape:
with tf.GradientTape() as inner_tape:
position = 4.9 * time ** 2
speed = inner_tape.gradient(position, time)
acceleration = outer_tape.gradient(speed, time)
순수 텐서플로우 API만을 이용하여 선형 분류 신경망을 구현한다.
np.random.multivariate_normal()
[0, 3]
[[1, 0.5],[0.5, 1]]
[3, 0]
[[1, 0.5],[0.5, 1]]
num_samples_per_class = 1000
# 음성 데이터셋
negative_samples = np.random.multivariate_normal(
mean=[0, 3], cov=[[1, 0.5],[0.5, 1]], size=num_samples_per_class)
# 양성 데이터셋
positive_samples = np.random.multivariate_normal(
mean=[3, 0], cov=[[1, 0.5],[0.5, 1]], size=num_samples_per_class)
두 개의 (1000, 2)
모양의 양성, 음성 데이터셋을 하나의 (2000, 2)
모양의 데이터셋으로 합치면서
동시에 자료형을 np.float32
로 지정한다.
자료형을 지정하지 않으면 np.float64
로 지정되어 보다 많은 메모리와 실행시간을 요구한다.
inputs = np.vstack((negative_samples, positive_samples)).astype(np.float32)
음성 샘플의 타깃은 0, 양성 샘플의 타깃은 1로 지정한다.
targets = np.vstack((np.zeros((num_samples_per_class, 1), dtype="float32"),
np.ones((num_samples_per_class, 1), dtype="float32")))
양성, 음성 샘플을 색깔로 구분하면 다음과 같다.
inputs[:, 0]
: x 좌표inputs[:, 1]
: x 좌표c=targets[:, 0]
: 0 또는 1에 따른 색상 지정import matplotlib.pyplot as plt
plt.scatter(inputs[:, 0], inputs[:, 1], c=targets[:, 0])
plt.show()
input_dim = 2 # 입력 샘플의 특성이 2개
output_dim = 1 # 하나의 값으로 출력
# 가중치: 무작위 초기화
W = tf.Variable(initial_value=tf.random.uniform(shape=(input_dim, output_dim)))
# 편향: 0으로 초기화
b = tf.Variable(initial_value=tf.zeros(shape=(output_dim,)))
아래 함수는 하나의 층만 사용하는 모델이 출력값을 계산하는 과정이다.
def model(inputs):
return tf.matmul(inputs, W) + b
tf.reduce_mean()
: 텐서에 포함된 항목들의 평균값 계산.
넘파이의 np.mean()
과 결과는 동일하지만 텐서플로우의 텐서를 대상으로 함.def square_loss(targets, predictions):
per_sample_losses = tf.square(targets - predictions)
return tf.reduce_mean(per_sample_losses)
하나의 배치에 대해 예측값을 계산한 후에 손실 함수의 그레이디언트를 이용하여 가중치와 편향을 업데이트한다.
learning_rate = 0.1
def training_step(inputs, targets):
with tf.GradientTape() as tape:
predictions = model(inputs)
loss = square_loss(targets, predictions)
grad_loss_wrt_W, grad_loss_wrt_b = tape.gradient(loss, [W, b])
W.assign_sub(grad_loss_wrt_W * learning_rate)
b.assign_sub(grad_loss_wrt_b * learning_rate)
return loss
배치 훈련을 총 40번 반복한다.
for step in range(40):
loss = training_step(inputs, targets)
print(f"Loss at step {step}: {loss:.4f}")
Loss at step 0: 5.6682 Loss at step 1: 0.8087 Loss at step 2: 0.2467 Loss at step 3: 0.1531 Loss at step 4: 0.1309 Loss at step 5: 0.1202 Loss at step 6: 0.1118 Loss at step 7: 0.1043 Loss at step 8: 0.0976 Loss at step 9: 0.0914 Loss at step 10: 0.0857 Loss at step 11: 0.0805 Loss at step 12: 0.0758 Loss at step 13: 0.0714 Loss at step 14: 0.0674 Loss at step 15: 0.0638 Loss at step 16: 0.0605 Loss at step 17: 0.0575 Loss at step 18: 0.0547 Loss at step 19: 0.0521 Loss at step 20: 0.0498 Loss at step 21: 0.0477 Loss at step 22: 0.0457 Loss at step 23: 0.0440 Loss at step 24: 0.0423 Loss at step 25: 0.0409 Loss at step 26: 0.0395 Loss at step 27: 0.0382 Loss at step 28: 0.0371 Loss at step 29: 0.0361 Loss at step 30: 0.0351 Loss at step 31: 0.0342 Loss at step 32: 0.0334 Loss at step 33: 0.0327 Loss at step 34: 0.0321 Loss at step 35: 0.0314 Loss at step 36: 0.0309 Loss at step 37: 0.0304 Loss at step 38: 0.0299 Loss at step 39: 0.0295
훈련상태를 보면 여전히 개선의 여지가 보인다. 따라서 학습을 좀 더 시켜본다.
for step in range(100):
loss = training_step(inputs, targets)
if step % 10 == 0:
print(f"Loss at step {step}: {loss:.4f}")
Loss at step 0: 0.0291 Loss at step 10: 0.0266 Loss at step 20: 0.0256 Loss at step 30: 0.0252 Loss at step 40: 0.0250 Loss at step 50: 0.0250 Loss at step 60: 0.0249 Loss at step 70: 0.0249 Loss at step 80: 0.0249 Loss at step 90: 0.0249
predictions = model(inputs)
예측 결과를 확인하면 다음과 같다. 예측값이 0.5보다 클 때 양성으로 판정하는 것이 좋은데 이유는 샘플들의 레이블이 0 또는 1이기 때문이다. 모델은 훈련과정 중에 음성 샘플은 최대한 0에, 양성 샘플은 최대한 1에 가까운 값으로 예측하여 손실값이 줄도록 노력하며, 옵티마이저가 그렇게 유도한다. 따라서 0과 1의 중간값인 0.5가 판단 기준으로 적절하다.
plt.scatter(inputs[:, 0], inputs[:, 1], c=predictions[:, 0] > 0.5)
plt.show()
결정 경계를 직선으로 그리려면 아래 식을 이용한다.
y = - W[0] / W[1] * x + (0.5 - b) / W[1]
이유는 위 모델의 예측값이 다음과 같이 계산되며,
W[0]*x + W[1]*y + b
위 예측값이 0.5보다 큰지 여부에 따라 음성, 양성이 판단되기 때문이다.
x = np.linspace(-1, 4, 100)
y = - W[0] / W[1] * x + (0.5 - b) / W[1]
plt.plot(x, y, "-r")
plt.scatter(inputs[:, 0], inputs[:, 1], c=predictions[:, 0] > 0.5)
<matplotlib.collections.PathCollection at 0x230b19b7070>
층(layer)의 역할은 다음과 같다.
층은 사용되는 클래스에 따라 다양한 형식의 텐서를 취급한다.
Dense
클래스를 사용하는 밀집층(dense layer):
(샘플수, 특성수)
모양의 2D 텐서로 제공된 데이터셋LSTM
클래스, Conv1D
클래스 등을 사용하는 순환층(recurrent layer):
(샘플수, 타임스텝수, 특성수)
모양의 3D 텐서로 제공된 순차 데이터셋Conv2D
클래스 등을 사용하는 층:
(샘플수, 가로, 세로, 채널수)
모양의 4D 텐서로 제공된 이미지 데이터셋케라스를 활용하여 딥러닝 모델을 구성하는 것은 호환 가능한 층들을 적절하게 연결하여 층을 쌓는 것을 의미한다.
Layer
클래스¶케라스에서 사용되는 모든 층 클래스는 Layer
클래스를 상속하며,
이를 통해 상속받는 __call__()
메서드가
가중치와 편향 벡터를 생성 및 초기화하고 입력 데이터를 출력 데이터로 변환하는 일을 수행한다.
Layer
클래스에서 선언된 __call__()
메서드가 하는 일을 간략하게 나타내면 다음과 같다.
def __call__(self, inputs):
if not self.built:
self.build(inputs.shape)
self.built = True
return self.call(inputs)
위 코드에 사용된 인스턴스 변수와 메서드는 다음과 같다.
self.built
: 가중치와 편향 벡터가 초기화가 되어 있는지 여부 기억self.build(inputs.shape)
: 입력 배치 데이터셋(inputs
)의 모양(shape) 정보를 이용하여
적절한 모양의 가중치 텐서와 편향 텐서를 생성하고 초기화한다.random_normal
)zeros
)self.call(inputs)
: 출력값 계산(forward pass)즉, 층은 입력값이 들어오면 가장 먼저 입력값의 모양을 확인한 다음에 가중치 텐서와 편향 텐서를 생성 및 초기화하며, 그 다음에 아핀 변환과 활성화 함수를 이용하여 출력값을 계산한다.
층은 입렵값을 확인하면서 동시에 입력값의 모양을 확인하기 때문에
2장에서 살펴본 MNIST 모델 사용된 Dense
클래스처럼 입력 데이터에 정보를
미리 요구하지 않는다.
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(512, activation="relu"),
layers.Dense(10, activation="softmax")
])
Dense
클래스 직접 구현하기¶위 설명을 바탕으로 해서 Dense
클래스와 유사하게 작동하는
SimpleDense
클래스를 직접 정의하려면
build()
메서드와 call()
메서드를 아래와 같이 구현하면 된다.
두 메서드의 정의에 사용된 매개변수와 메서드는 다음과 같다.
units
: 출력 샘플의 특성수 지정activation
: 활성화 함수 지정input_shape
: 입력값(inputs
)으로 얻은 입력 배치의 2D 모양 정보. 둘째 항목이 입력 샘플의 특성 수.add_weight(모양, 초기화방법)
: 지정된 모양의 텐서 생성 및 초기화. Layer
클래스에서 상속.from tensorflow import keras
class SimpleDense(keras.layers.Layer):
def __init__(self, units, activation=None):
super().__init__()
self.units = units
self.activation = activation
def build(self, input_shape):
input_dim = input_shape[-1] # 입력 샘플의 특성 수
self.W = self.add_weight(shape=(input_dim, self.units),
initializer="random_normal")
self.b = self.add_weight(shape=(self.units,),
initializer="zeros")
def call(self, inputs):
y = tf.matmul(inputs, self.W) + self.b
if self.activation is not None:
y = self.activation(y)
return y
SimpleDense
층 하나로 이루어진 모델이 작동하는 방식은 다음과 같다.
my_dense = SimpleDense(units=32, activation=tf.nn.relu)
input_tensor = tf.ones(shape=(1000, 784))
출력값 생성: __call__()
메서드를 실행하면 다음 사항들이 연속적으로 처리된다.
W
생성 및 초기호: (784, 32)
b
생성 및 초기호: (32, )
output_tensor = my_dense(input_tensor)
print(output_tensor.shape)
(1000, 32)
딥러닝 모델은 층으로 구성된 그래프라고 할 수 있다.
앞서 살펴 본 Sequential
모델은 층을 일렬로 쌓아 신경망을 구성하며,
층층이 쌓인 층은 아래 층에서 전달한 값을 받아 변환한 후 위 층으로 전달하는
방식으로 작동한다.
예를 들어, 아래 모델은 4개의 SimpleDense
층으로 구성되었다.
각 층의 역할은 앞서 설명한 my_dense
모델의 그것과 동일하다.
즉, 32, 64, 32, 10 개의 특성을 갖는 출력값을 계산한 후 다음 층으로 전달한다.
model = keras.Sequential([
SimpleDense(32, activation="relu"),
SimpleDense(64, activation="relu"),
SimpleDense(32, activation="relu"),
SimpleDense(10, activation="softmax")
])
앞으로 층을 구성하는 다양한 방식을 살펴볼 것이다. 예를 들어, 아래 그림은 나중에 살펴 볼 트랜스포머(Transformer) 모델에 사용된 층들의 복잡한 관계를 보여준다.
모델의 학습과정은 층을 어떻게 구성하였는가에 전적으로 의존한다.
앞서 살펴 보았듯이 각각의 층에서 이루어지는 일은 기본적으로 아핀 변환과 활성화 함수 적용이다.
여러 개의 Dense
층을 Sequential
모델을 이용하여 층을 구성하면
아핀 변환,relu()
등의 활성화 함수를 연속적으로 적용하여
입력 텐서를 특정 모양의 텐서로 변환한다.
반면에 다른 방식으로 구성된 모델은 다른 방식으로 텐서를
하나의 표현에서 다른 표현으로 변환시킨다.
이렇듯 층을 구성하는 방식에 따라 텐서들이 가질 수 있는 표현들의 공간이 정해진다. 이런 의미에서 '망 구성방식(network topology)에 따른 표현 가설 공간(hypothesis space)'이란 표현을 사용한다.
신경망의 구성은 주어진 데이터셋과 모델의 목적에 따라 결정되며 특별한 규칙이 따로 있지는 않다. 주어진 문제를 해결하는 모델의 구성은 이론 보다는 많은 실습을 통한 경험에 의존한다. 앞으로 다양한 예제를 통해 다양한 모델을 구성하는 방식을 다룬다.
모델의 구조를 정의한 후에 아래 세 가지 설정을 추가로 지정해야 한다.
model = keras.Sequential([keras.layers.Dense(1)])
model.compile(optimizer="rmsprop",
loss="mean_squared_error",
metrics=["accuracy"])
세 가지 설정에 사용된 문자열은 지정된 함수를 가리키도록 준비되어 있다. 예를 들어 아래 코드는 앞서의 컴파일과 동일한 결과가 나오도록 옵티마이저, 손실함수, 평가지표에 필요한 객체들을 직접 지정하였다.
model.compile(optimizer=keras.optimizers.RMSprop(),
loss=keras.losses.MeanSquaredError(),
metrics=[keras.metrics.BinaryAccuracy()])
앞으로 다양한 예제를 통해 옵티마이저, 손실함수, 평가지표를 적절하게 선택하는 방법을 살펴볼 것이다.
fit()
메서드 작동법¶모델을 훈련시키려면 fit()
메서드를 적절한 인자들과 함께 호출해야 한다.
Dataset
객체 사용epochs
): 전체 훈련 세트를 몇 번 훈련할 지 지정batch_size
): 배치 경사하강법에 적용될 배치(묶음) 크기 지정아래 코드는 앞서 넘파이 어레이로 생성한 (2000, 2) 모양의 양성, 음성 데이터셋을 대상으로 훈련한다.
history = model.fit(
inputs,
targets,
epochs=5,
batch_size=128
)
Epoch 1/5 16/16 [==============================] - 0s 2ms/step - loss: 2.1943 - binary_accuracy: 0.4811 Epoch 2/5 16/16 [==============================] - 0s 3ms/step - loss: 1.9241 - binary_accuracy: 0.5061 Epoch 3/5 16/16 [==============================] - 0s 2ms/step - loss: 1.7317 - binary_accuracy: 0.4967 Epoch 4/5 16/16 [==============================] - 0s 2ms/step - loss: 1.5507 - binary_accuracy: 0.5146 Epoch 5/5 16/16 [==============================] - 0s 2ms/step - loss: 1.4457 - binary_accuracy: 0.5069
훈련이 종료되면 fit()
메서드는 History
객체를 반환하며,
history
속성에 훈련 과정 중에 측정된 손실값, 평가지표를 에포크 단위로 기억한다.
history.history
{'loss': [2.093653917312622, 1.8939359188079834, 1.7320936918258667, 1.5835086107254028, 1.4453810453414917], 'binary_accuracy': [0.49549999833106995, 0.49549999833106995, 0.4959999918937683, 0.4959999918937683, 0.4964999854564667]}
훈련된 모델이 완전히 새로운 데이터에 대해 예측을 잘하는지 여부를 판단하려면 전체 데이터셋을 훈련 세트와 검증 세트로 구분해야 한다.
아래 코드는 훈련 세트와 검증 세트를 수동으로 구분하는 방법을 보여준다.
np.random.permutation()
함수는 숫자들을 무작위로 섞는다.
이를 이용하여 훈련세트의 인덱스를 무작위로 섞는다.indices_permutation = np.random.permutation(len(inputs))
shuffled_inputs = inputs[indices_permutation]
shuffled_targets = targets[indices_permutation]
num_validation_samples = int(0.3 * len(inputs))
val_inputs = shuffled_inputs[:num_validation_samples]
val_targets = shuffled_targets[:num_validation_samples]
training_inputs = shuffled_inputs[num_validation_samples:]
training_targets = shuffled_targets[num_validation_samples:]
history = model.fit(
training_inputs,
training_targets,
epochs=5,
batch_size=16,
validation_data=(val_inputs, val_targets)
)
Epoch 1/5 88/88 [==============================] - 1s 11ms/step - loss: 1.0726 - binary_accuracy: 0.4979 - val_loss: 0.8352 - val_binary_accuracy: 0.4950 Epoch 2/5 88/88 [==============================] - 0s 3ms/step - loss: 0.6193 - binary_accuracy: 0.5007 - val_loss: 0.4537 - val_binary_accuracy: 0.4950 Epoch 3/5 88/88 [==============================] - 0s 3ms/step - loss: 0.3114 - binary_accuracy: 0.5050 - val_loss: 0.1972 - val_binary_accuracy: 0.5267 Epoch 4/5 88/88 [==============================] - 0s 3ms/step - loss: 0.1178 - binary_accuracy: 0.8171 - val_loss: 0.0595 - val_binary_accuracy: 0.9867 Epoch 5/5 88/88 [==============================] - 0s 3ms/step - loss: 0.0371 - binary_accuracy: 0.9950 - val_loss: 0.0259 - val_binary_accuracy: 1.0000
History
객체는 훈련 세트 뿐만 아니라 검증 세트를 대상으로도 손실값과 평가지표를 기억한다.
history.history
{'loss': [1.072609543800354, 0.6192675828933716, 0.3113844394683838, 0.11782326549291611, 0.037062548100948334], 'binary_accuracy': [0.49785715341567993, 0.5007143020629883, 0.5049999952316284, 0.8171428442001343, 0.9950000047683716], 'val_loss': [0.8351580500602722, 0.4537375867366791, 0.19723956286907196, 0.05950124189257622, 0.025946015492081642], 'val_binary_accuracy': [0.4950000047683716, 0.4950000047683716, 0.5266666412353516, 0.9866666793823242, 1.0]}
훈련 후에 검증 세트를 이용하여 평가하려면 evaluate()
메서드를 이용한다.
loss_and_metrics = model.evaluate(val_inputs, val_targets, batch_size=128)
5/5 [==============================] - 0s 0s/step - loss: 0.0259 - binary_accuracy: 1.0000
훈련된 모델을 활용하는 두 가지 방법이 있다.
먼저, __call__()
메서드를 활용한다. 즉,
데이터셋을 모델과 함께 직접 호출한다.
predictions = model(val_inputs)
print(predictions[:10])
tf.Tensor( [[ 0.08220395] [-0.1380057 ] [ 0.13853893] [ 0.14018369] [ 1.109943 ] [ 0.6667064 ] [ 0.06574634] [ 0.5064674 ] [ 0.15613529] [ 0.88908464]], shape=(10, 1), dtype=float32)
하지만 이 방식은 데이터셋이 매우 크면 적절하지 않을 수 있다.
따라서 predict()
메서드를 이용하여 배치를 활용하는 것을 추천한다.
predictions = model.predict(val_inputs, batch_size=128)
print(predictions[:10])
[[ 0.08220395] [-0.1380057 ] [ 0.13853893] [ 0.14018369] [ 1.109943 ] [ 0.6667064 ] [ 0.06574634] [ 0.5064674 ] [ 0.15613529] [ 0.88908464]]