학습 과정을 시각화
- 지금까지 수행한 결과는 정량적인 평가로 테스트 데이터에 대한 예측 정확도를 측정하였다.
- 학습 데이터에 대해서도 오차 함수의 값이 예측 정확도을 출력하기는 했어도 이는 학습이 진행되는지만을 확인하기 위한 것이었고 어떤 학습이 진행되고 있는지 대략적인 파악만 가능했다.
- 데이터의 규모가 커지면 검증 데이터를 사용해서 학습을 평가하는 작업도 적절한 선에서 실시해야 한다.
- 테스트 데이터에 대한 예측 정확도는 (데이터가 1개일 경우) 결과가 하나의 수치로 표현되지만 학습 데이터 또는 검증 데이터에 관한 예측 정확도는 각 epoch마다 평가해야 하므로 여러 개의 수치를 한 눈에 볼 수 있어야 하며, 이는 숫자들을 보는 것보다는 그래프 형태로 볼 수 있으면 직관적으로 파악이 가능하다.
- 지금까지 구현한 코드에 다음 처리 과정을 추가해보도록 한다.
- 검증 데이터를 사용해서 학습시키고 예측한다.
- 학습시킬 때의 예측 정확도를 시각화한다.
TensorFlow로 구현
- 학습 데이터와 검증 데이터, 테스트 데이터를 준비하는 부분 구현
- 전체 데이터를 학습 데이터 테스트 데이터로 나누고, 다시 학습 데이터를 학습 데이터와 학습한 모델을 평가하기 위한 검증 데이터로 나눈다.
if __name__ == '__main__': MNIST = datasets.fetch_mldata('MNIST original', data_home='.') n = len(MNIST.data) indices = np.random.permutation(range(n))[:N] X = MNIST.data[indices] Y = MNIST.target[indices] Y = np.eye(10)[Y.astype(int)] # 1-of-K hot coding 표현으로 변환 test_size = 0.2 # 학습 데이터와 테스트 데이터를 80:20으로 나눈다 X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=test_size) # 학습 데이터의 20%를 검증 데이터로 사용한다 X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=test_size)
- 모든 학습 데이터를 학습 데이터와 검증 데이터로 완전히 분리하고 같은 검증 데이터를 모델 평가에 사용하는 기법을 홀드아웃 검증(hold-out validation)이라고 한다.
- 학습 데이터를 $K$개의 데이터 셋으로 나누고 그 중 하나를 검증 데이터로 사용하고 나머지 $K-1$개의 데이터를 학습에 사용하는 기법을 $K$-분할교차 검증($K$-cross validation)이라고 한다.
- $K$-분할교차 검증에서는 $K$개 나눈 데이터 셋을 조합해서 총 $K$번의 학습과 검증을 수행한다.
- 이 과정에서 얻어진 예측 정확도의 평균을 모델의 성능으로 간주한다.
- $K$-분할교차 검증이 일반화 성능을 매우 엄밀하게 평가할 수 있지만 딥러닝에서는 모델을 학습시키는데 방대한 시간이 걸릴 경우가 많기 때문에 $K$-분할교차 검증은 잘 사용되지 않는다.
- 검증 데이터에 대한 오차나 예측 정확도는 각 epoch마다 평가해야 하기 때문에 모델을 학습시키는 부분은 다음과 같이 구현한다.
for epoch in range(epoch): X_, Y_ = shuffle(x_train, y_train) for i in range(n_batches): start = i * batch_size end = start + batch_size sess.run(train, feed_dict={ x: X_[start:end], y: Y_[start:end], keeP_prob: p_keep }) # 검증 데이터를 사용해 평가한다 val_loss = loss.eval(session=sess, feed_dict={ x: x_val, y: v_val, keep_prob: 1.0 }) val_acc = accuracy.eval(session=sess, feed_dict={ x: x_val, y: y_val, keep_prob: 1.0 })
- 검증 데이터에 대한 손실을 나타내는 val_loss와 예측 정확도를 나타내는 val_acc를 각 epoch마다 출력해도 학습이 진행되는 상황을 확인할 수 있지만 시각화를 위해 저장해둘 변수를 설정한다. 프로그램 선언부에 명시한다.
history = { 'val_loss' = [], 'val_acc' = [] }
- 이제 각 epoch마다 나오는 val_loss와 val_acc를 추가해 저장한다.
# 학습 진행 상황 데이터를 저장할 변수 설정 history = {'val_loss': [], 'val_acc': []} for epoch in range(epochs): X_, Y_ = shuffle(X_train, Y_train) for i in range(n_batches): start = i * batch_size end = start + batch_size sess.run(train, feed_dict={x: X_[start: end], y: Y_[start: end], keep_prob: p_keep}) # 검증 데이터를 사용해 정확도 측정 val_loss = loss.eval(session=sess, feed_dict={x: X_val y: Y_val, keep_prob: 1.0}) val_accuracy = accuracy.eval(session=sess, feed_dict={x: X_val, y: Y_val, keep_prob: 1.0}) # 검증 데이터에 대한 학습 진행 상황을 저장 history['val_loss'].append(val_loss) history['val_acc'].append(val_accuracy)
matplotlib 패키지를 이용해 그래프 그리기
- 검증 데이터의 정확도를 그래프로 그리자
import matplotlib.pyplot as plt import matplotlib # 폰트 설정 font_name = matplotlib.font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() matplotlib.rc('font', family=font_name) # 도화지 생성 fig = plt.figure() # 정확도 그래프 그리기 plt.plot(range(epochs), history['val_acc'], label='Accuracy', color='darkred') # 축 이름 plt.xlabel('epochs') plt.ylabel('검증 정확도(%)') plt.title('epoch에 따른 검증 데이터에 대한 정확도 그래프') plt.grid(linestyle='--', color='lavender') # 그래프 표시 plt.show() # 그래프 저장 plt.savefig('mnist_tensorflow_acc.png')
- 전체 코드
from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.utils import shuffle import numpy as np import tensorflow as tf import matplotlib.pyplot as plt import matplotlib def inference(x, keep_prob, n_in, n_hiddens, n_out): def weight_variable(shape): initial = tf.truncated_normal(shape, stddev=0.01) return tf.Variable(initial) def bias_variable(shape): initial = tf.zeros(shape) return tf.Variable(initial) # 입력층-은닉층과 은닉층-은닉층 설계 for i, n_hidden in enumerate(n_hiddens): if i == 0: # 입력층-은닉층 input = x input_dim = n_in else: # 은닉층-은닉층 input = output # 출력층이 다음 입력층이 된다 input_dim = n_hiddens[i-1] W = weight_variable([input_dim, n_hidden]) # weight의 shape은 입력층 차원(784) x 뉴런의 개수 b = bias_variable([n_hidden]) # bias의 shape은 뉴런의 개수 hidden_layer = tf.nn.relu(tf.matmul(input, W) + b) # 은닉층의 활성화 함수로 ReLU를 사용 output = tf.nn.dropout(hidden_layer, keep_prob=keep_prob) # 출력은 드롭아웃을 적용 # 은닉층-출력층 설계 W_out = weight_variable([n_hiddens[-1], n_out]) # 출력층의 weight shape은 뉴런의 개수 x 출력층의 차원(10개) b_out = bias_variable([n_out]) # bias의 shape은 출력층의 차원 hypothesis = tf.nn.softmax(tf.matmul(output, W_out) + b_out) # 출력 활성화 함수는 softmax를 사용 return hypothesis def loss(hypothesis, y): # cross entropy cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(hypothesis), reduction_indices=[1])) return cross_entropy def train(loss): optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01) train = optimizer.minimize(loss) return train def accuracy(hypothesis, y): correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(y, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) return accuracy if __name__ == '__main__': MNIST = datasets.fetch_mldata('MNIST original', data_home='.') n = len(MNIST.data) N = int(n * 1.0) indices = np.random.permutation(range(n))[:N] X = MNIST.data[indices] Y = MNIST.target[indices] Y = np.eye(10)[Y.astype(int)] # 1-of-K hot coding 표현으로 변환 test_size = 0.2 # 학습 데이터와 테스트 데이터를 80:20으로 나눈다 X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=test_size) # 학습 데이터의 20%를 검증 데이터로 사용한다 X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=test_size) # 2. 모델을 설정한다 # 입출력 데이터와 은닉층의 뉴런 개수 설정 n_in = len(X[0]) # 입력 데이터 이미지의 크기 28x28(=784) n_hiddens = [200, 200, 200] # 각 은닉층의 뉴런 개수 n_out = len(Y[0]) # 출력 데이터의 개수 (0~9) p_keep = 0.5 # 드롭아웃 확률의 비율 x = tf.placeholder(tf.float32, shape=[None, n_in]) y = tf.placeholder(tf.float32, shape=[None, n_out]) keep_prob = tf.placeholder(tf.float32) # 가설식을 그래프에 추가 hypothesis = inference(x, keep_prob, n_in=n_in, n_hiddens=n_hiddens, n_out=n_out) # 오차 함수를 그래프에 추가 loss = loss(hypothesis, y) # 학습 함수를 그래프에 추가 train = train(loss) # 정확도 예측 함수 그래프에 추가 accuracy = accuracy(hypothesis, y) # 3. 모델을 학습시키면서 그래프를 실행 epochs = 50 batch_size = 200 init = tf.global_variables_initializer() sess = tf.Session() sess.run(init) n_batches = len(X_train) // batch_size # 학습 진행 상황 데이터를 저장할 변수 설정 history = {'val_loss': [], 'val_acc': []} for epoch in range(epochs): X_, Y_ = shuffle(X_train, Y_train) for i in range(n_batches): start = i * batch_size end = start + batch_size sess.run(train, feed_dict={x: X_[start: end], y: Y_[start: end], keep_prob: p_keep}) # 검증 데이터를 사용해 정확도 측정 val_loss = loss.eval(session=sess, feed_dict={x: X_val, y: Y_val, keep_prob: 1.0}) val_accuracy = accuracy.eval(session=sess, feed_dict={x: X_val, y: Y_val, keep_prob: 1.0}) # 검증 데이터에 대한 학습 진행 상황을 저장 history['val_loss'].append(val_loss) history['val_acc'].append(val_accuracy * 100) print(f'epoch : {epoch:03}, Validation loss : {val_loss:.7}, Validation accuracy : {val_accuracy*100:.6} %') # 4. 모델을 평가한다 accuracy_rate = accuracy.eval(session=sess, feed_dict={x: X_test, y: Y_test, keep_prob: 1.0}) print(f'Test Accuracy : {accuracy_rate*100:.6} %') # 폰트 설정 font_name = matplotlib.font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() matplotlib.rc('font', family=font_name) # 도화지 생성 fig = plt.figure() # 정확도 그래프 그리기 plt.plot(range(epochs), history['val_acc'], label='Accuracy', color='darkred') # 축 이름 plt.xlabel('epochs') plt.ylabel('검증 정확도(%)') plt.title('epoch에 따른 검증 데이터에 대한 정확도 그래프') plt.grid(linestyle='--', color='lavender') # 그래프 표시 plt.show() plt.savefig('mnist_tensorflow_acc.png')
- 코드를 실행하면 그림 $11.1$과 같은 그래프를 볼 수 있다
그림 11.1 epoch에 따른 검증 정확도 변화
- 그림 $11.1$을 보면 검증 정확도가 점점 올라갔다가 오느 시점부터는 전혀 학습이 이루어지지 않는다는 것을 알 수 있다.
- 이는 경사 소실 문제와 마찬가지로 softmax 함수(sigmoid 함수)를 통해 전달되는 경사값이 너무 작아져서 $0$처럼 취급되어 계산되는 것이 원인이다.
- 이 문제 해결을을 위해서 오차 함수 식을 다음과 같이 수정해야만 한다.
- 계산에 사용되는 하한값(또는 상한값)을 새롭게 추가된 tf.clip_by_value()가 결정한다.
- 이 하한값을 1e-10$(=10^{-10})$과 같이 작은 값으로 설정해두면 학습을 계산하는데 악영향을 주지 않으면서 $0$으로 나누는 문제를 방지할 수 있다.
def loss(hypothesis, y): # cross entropy cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(tf.clip_by_value(hypothesis, 1e-10, 1.0)), reduction_indices=[1]))
- 오차 함수 식을 수정한 결과 출력된 그래프는 그림 $11.2$와 같다.
그림 11.2 epoch에 따른 검증 정확도 변화(오차 함수 수정 후)
- 오차도 함께 그래프에 표시해보자
# 폰트 설정 font_name = matplotlib.font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() matplotlib.rc('font', family=font_name) # 도화지 생성 fig = plt.figure() # 큰 도화지(fig)를 1x1로 나눈 도화지에 첫 번째 칸에 ax_acc 그래프를 추가한다 ax_acc = fig.add_subplot(111) # 정확도 그래프 그리기 ax_acc.plot(range(epochs), history['val_acc'], label='정확도(%)', color='darkred') # 축 이름 plt.text(3, 14.7, "<----------------정확도(%)", verticalalignment='top', horizontalalignment='right') plt.xlabel('epochs') plt.ylabel('정확도(%)') ax_acc.grid(linestyle='--', color='lavender') # ax_loos 그래프를 x축을 동일하게 사용하여 겹쳐 그리기 선언 ax_loss = ax_acc.twinx() ax_loss.plot(range(epochs), history['val_loss'], label='오차', color='darkblue') plt.text(3, 2.2, "<----------------오차", verticalalignment='top', horizontalalignment='left') plt.ylabel('오차') ax_loss.yaxis.tick_right() ax_loss.grid(linestyle='--', color='lavender') # 그래프 표시 plt.show() plt.legend() plt.savefig('mnist_tensorflow_acc.png')
- 정확도와 오차를 함께 그린 그래프는 $11.3$과 같다.
그림 11.2 epoch에 따른 검증 정확도와 오차를 함께 그린 그래프
Keras로 구현
- Keras에서는 model.fit()의 반환값에 학습이 진행된 정도를 나타내는 값이 저장된 결과가 들어있다.
- 검증 데이터를 사용해서 학습시키는 경우에는 학습시킬 때 다음과 같이 validation_data=를 사용하면 hist 변수에 오차와 정확도가 저장된다.
hist = model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, validation_data=(X_val, Y_val)) val_loss = hist.history['val_loss'] val_acc = hist.history['val_acc']
- 정확도를 그래프로 그리면 그림 $11.3$과 같다.
epochs = 50 batch_size = 200 hist = model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, validation_data=(X_val, Y_val)) val_loss = hist.history['val_loss'] val_acc = hist.history['val_acc'] font_name = matplotlib.font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() matplotlib.rc('font', family=font_name) fig = plt.figure() ax_acc = fig.add_subplot(111) ax_acc.plot(range(epochs), val_acc, label='정확도(%)', color='darkred') plt.text(3, 14.7, "<----------------정확도(%)", verticalalignment='top', horizontalalignment='right') plt.xlabel('epochs') ax_acc.grid(linestyle='--', color='lavender') plt.show()
그림 11.3 Keras로 그린 epoch에 따른 검증 정확도 그래프
- 그림 $11.3$도 어느 순간부터 학습이 제대로 진행되지 않는 것을 알 수 있다. 그 이유는 매개변수를 초기화하는 부분에 있다.
- TensorFlow에서는 weight를 초기화하기 위하여 tf.truncated_normal(shape, stddev=0.01) 함수를 사용하였지만 Keras에서는 아무 것도 사용하지 않아 학습이 제대로 진행되지 않는 것이다.
- weight의 초기값을 어떻게 결정하는지가 학습이 제대로 이루어지는지 아니면 실패하는지에 영향을 준다. 초기값을 설정하는 몇 가지 기법이 연구가 된 상태이다.
- Keras에서는 Dense() 함수에 kernel_initializer=라는 매개변수가 초기값을 결정하는데 영향을 준다. 매개변수를 사용하는 방법은 다음과 같다.
- Keras에서 미리 alias로 정의되어 있는 것 이외의 것을 사용할 때에는 keras.backend를 통해 처리를 수행한다.
- weight_variable() 함수 내부에서 K.truncated_normal(shape, stddev=0.01)이 TensorFlow에서 구현했을 때처럼 표준편차가 $0.01$인 절단 정규분포를 따르는 난수를 반환한다.
- Dense(kernel_initializer=weight_variable) 함수가 실행되면 TensorFlow에서 실행되었던 것처럼 초기값을 가진 weight를 생성한다.
from keras import backend as K def weight_variable(shape): return K.truncated_normal(shape, stddev=0.01) model = Sequential() for i, input_dim in enumerate(([n_in] + n_hiddens)[:-1]): model.add(Dense(input_dim=input_dim, units=n_hiddens[i], kernel_initializer=weight_variable)) model.add(LeakyReLU(alpha=alpha)) model.add(Dropout(p_keep)) model.add(Dense(units=n_out, kernel_initializer=weight_variable)) model.add(Activation('softmax'))
- 절단정규분포는 keras.initializersTruncatedNormal() 함수를 통해 사용할 수도 있다.
- 이 때 weight_variable()은 정의하지 않아도 된다.
from keras.initializers import TruncatedNormal model.add(Dense(units=n_out, kernel_initializer=TruncatedNormal(stddev=0.01)))
- Keras에서 NumPy로 생성한 난수를 그대로 사용해도 된다.
- 이 때 weight_variable()은 다음과 같이 정의한다.
def weight_variable(shape): return np.random.normal(scale=0.01, size=shape)
- 전체 코드를 실행하면 그림 $11.4$와 같은 그래프를 얻을 수 있다.
from sklearn import datasets from sklearn.model_selection import train_test_split import numpy as np from keras.models import Sequential from keras.layers import Dense, Activation, Dropout from keras.optimizers import SGD from keras import backend as K from keras.layers.advanced_activations import LeakyReLU import matplotlib.pyplot as plt import matplotlib MNIST = datasets.fetch_mldata('MNIST original', data_home='.') n = len(MNIST.data) N = int(n * 1.00) # 70,000개의 데이터를 모두 사용 test_size = 0.2 # 학습 데이터와 테스트 데이터를 나누기 전에 입력 데이터를 무작위로 섞는다 indices = np.random.permutation(range(n))[:N] X = MNIST.data[indices] Y = MNIST.target[indices] Y = np.eye(10)[Y.astype(int)] # 1-of-K hot coding 표현으로 변환 # 학습 데이터와 테스트 데이터를 80:20으로 나눈다 X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=test_size) X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=test_size) n_in = len(X[0]) # 입력 데이터 이미지의 크기 28x28(=784) n_hiddens = [200, 200, 200] # 각 은닉층의 뉴런 개수 n_out = len(Y[0]) # 출력 데이터의 개수 (0~9) alpha = 0.01 p_keep = 0.5 # 드롭아웃 확률의 비율 def weight_variable(shape): return K.truncated_normal(shape, stddev=0.01) model = Sequential() for i, input_dim in enumerate(([n_in] + n_hiddens)[:-1]): model.add(Dense(input_dim=input_dim, units=n_hiddens[i], kernel_initializer=weight_variable)) model.add(LeakyReLU(alpha=alpha)) model.add(Dropout(p_keep)) model.add(Dense(units=n_out, kernel_initializer=weight_variable)) model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy']) epochs = 50 batch_size = 200 hist = model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, validation_data=(X_val, Y_val)) val_loss = hist.history['val_loss'] val_acc = hist.history['val_acc'] font_name = matplotlib.font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() matplotlib.rc('font', family=font_name) fig = plt.figure() ax_acc = fig.add_subplot(111) ax_acc.plot(range(epochs), val_acc, label='정확도(%)', color='darkred') plt.text(3, 14.7, "<----------------정확도(%)", verticalalignment='top', horizontalalignment='right') plt.xlabel('epochs') plt.ylabel('정확도(%)') ax_acc.grid(linestyle='--', color='lavender') ax_loss = ax_acc.twinx() ax_loss.plot(range(epochs), val_loss, label='오차', color='darkblue') plt.text(3, 2.2, "<----------------오차", verticalalignment='top', horizontalalignment='left') plt.ylabel('오차') ax_loss.yaxis.tick_right() ax_loss.grid(linestyle='--', color='lavender') plt.legend() plt.show() ax_acc.legend() plt.savefig('mnist_keras_plot.png') evaluation = model.evaluate(X_test, Y_test) print(f'\nloss : {evaluation[0]}, accuracy : {evaluation[1]*100:.6} %')
그림 11.4 weight를 초기화시켜 학습시킨 모델의 그래프
출처 : 정석으로 배우는 딥러닝
'신경망(Neural Network) 스터디' 카테고리의 다른 글
13. 학습률 설정 (0) | 2018.01.29 |
---|---|
12. 데이터 정규화 및 매개변수 초기화 (0) | 2018.01.23 |
10. 신경망 모델 구현 방법 (0) | 2018.01.19 |
9. 오버피팅 문제 해결 (0) | 2018.01.19 |
8. 경사 소실 문제 해결하기 (0) | 2018.01.18 |