from tensorflow import keras
keras.__version__
'2.4.0'
이 노트북은 케라스 창시자에게 배우는 딥러닝 책의 4장 4절의 코드 예제입니다. 책에는 더 많은 내용과 그림이 있습니다. 이 노트북에는 소스 코드에 관련된 설명만 포함합니다. 이 노트북의 설명은 케라스 버전 2.2.2에 맞추어져 있습니다. 케라스 최신 버전이 릴리스되면 노트북을 다시 테스트하기 때문에 설명과 코드의 결과가 조금 다를 수 있습니다.
이전 장에 있던 세 가지 예제인 영화 리뷰 예측, 토픽 분류, 주택 가격 회귀 모두 홀드아웃 데이터에서 모델의 성능이 몇 번의 에포크 후에 최고치에 다다랐다가 감소되기 시작했습니다. 즉, 모델이 금방 훈련 데이터에 과대적합되기 시작합니다. 과대적합은 모든 머신 러닝 문제에서 발생합니다. 머신 러닝을 마스터하려면 과대적합을 다루는 방법을 꼭 배워야 합니다.
머신 러닝의 근본적인 이슈는 최적화와 일반화 사이의 줄다리기입니다. 최적화는 가능한 훈련 데이터에서 최고의 성능을 얻으려고 모델을 조정하는 과정입니다(머신 러닝의 학습). 반면 일반화는 훈련된 모델이 이전에 본 적 없는 데이터에서 얼마나 잘 수행되는지를 의미합니다. 물론 모델을 만드는 목적은 좋은 일반화 성능을 얻는 것입니다. 하지만 일반화 성능을 제어할 방법이 없습니다. 단지 훈련 데이터를 기반으로 모델을 조정할 수만 있습니다.
훈련 초기에 최적화와 일반화는 상호 연관되어 있습니다. 훈련 데이터의 손실이 낮을수록 테스트 데이터의 손실이 낮습니다. 이런 상황이 발생할 때 모델이 과소적합되었다고 말합니다. 모델의 성능이 계속 발전될 여지가 있습니다. 즉, 네트워크가 훈련 데이터에 있는 관련 특성을 모두 학습하지 못했습니다. 하지만 훈련 데이터에 여러 번 반복 학습하고 나면 어느 시점부터 일반화 성능이 더이상 높아지지 않습니다. 검증 세트의 성능이 멈추고 감소되기 시작합니다. 즉, 모델이 과대적합되기 시작합니다. 이는 훈련 데이터에 특화된 패턴을 학습하기 시작했다는 의미입니다. 이 패턴은 새로운 데이터와 관련성이 적어 잘못된 판단을 하게 만듭니다.
모델이 관련성이 없고 좋지 못한 패턴을 훈련 데이터에서 학습하지 못하도록 하려면 가장 좋은 방법은 더 많은 훈련 데이터를 모으는 것입니다. 더 많은 데이터에서 훈련된 모델은 자연히 일반화 성능이 더 뛰어 납니다. 데이터를 더 모으는 것이 불가능할 때 차선책은 모델이 수용할 수 있는 정보의 양을 조절하거나 저장할 수 있는 정보에 제약을 가하는 것입니다. 네트워크가 적은 수의 패턴만을 기억할 수 있다면 최적화 과정에서 가장 중요한 패턴에 집중하게 될 것입니다. 이런 패턴은 더 나은 일반화 성능을 제공할 수 있습니다.
이런 식으로 과대적합을 피하는 처리 과정을 규제라고 합니다. 가장 널리 사용되는 규제 기법을 알아보고 이전 장에서 본 영화 리뷰 분류 모델에 실제로 적용해 성능을 향상시켜 보겠습니다.
노트: 이 노트북에서는 편의상 IMDB 테스트 세트를 검증 세트로 사용합니다.
3장 5절에 있는 코드를 사용해 데이터를 준비합니다:
from tensorflow.keras.datasets import imdb
import numpy as np
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
def vectorize_sequences(sequences, dimension=10000):
# 크기가 (len(sequences), dimension))이고 모든 원소가 0인 행렬을 만듭니다
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1. # results[i]에서 특정 인덱스의 위치를 1로 만듭니다
return results
# 훈련 데이터를 벡터로 변환합니다
x_train = vectorize_sequences(train_data)
# 테스트 데이터를 벡터로 변환합니다
x_test = vectorize_sequences(test_data)
# 레이블을 벡터로 변환합니다
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz 17465344/17464789 [==============================] - 1s 0us/step
과대적합을 막는 가장 단순한 방법은 모델의 크기, 즉 모델에 있는 학습 파라미터의 수를 줄이는 것입니다. 파라미터의 수는 층의 수와 각 층의 유닛 수에 의해 결정됩니다. 딥러닝에서 모델에 있는 학습 파라미터의 수를 종종 모델의 용량이라고 말합니다. 당연하게 파라미터가 많은 모델이 기억 용량이 더 많습니다. 훈련 샘플과 타깃 사이를 딕셔너리와 같은 1:1 매핑으로 완벽하게 학습할 수도 있습니다. 이런 매핑은 일반화 능력이 없습니다. 예를 들어 500,000개의 이진 파라미터가 있는 모델은 MNIST 훈련 세트의 숫자 이미지의 클래스를 쉽게 모두 학습할 수 있습니다. 50,000개의 숫자 이미지 하나마다 10개의 이진 파라미터만 있으면 됩니다. 하지만 이런 모델은 새로운 숫자 샘플을 분류하는 용도로는 쓸모가 없습니다. 항상 유념해야 할 것은 딥러닝 모델은 훈련 데이터에 잘 맞으려는 경향을 가진다는 점입니다. 하지만 진짜 문제는 최적화가 아니고 일반화입니다.
다른 한편으로 네트워크가 기억 용량에 제한이 있다면 이런 매핑을 쉽게 학습하지 못할 것입니다. 따라서 손실을 최소화하기 위해 타깃에 대한 예측 성능을 가진 압축된 표현을 학습해야 합니다. 정확히 이런 표현이 우리의 관심 대상입니다. 동시에 기억해야할 것은 과소적합되지 않도록 충분한 파라미터를 가진 모델을 사용해야 한다는 점입니다. 모델의 기억 용량이 부족해서는 안 됩니다. 너무 많은 용량과 충분하지 않은 용량 사이의 절충점을 찾아야 합니다.
안타깝지만 알맞은 층의 수나 각 층의 유닛 수를 결정할 수 있는 마법 같은 공식은 없습니다. 데이터에 알맞는 모델 크기를 찾으려면 각기 다른 구조를 (당연히 테스트 세트가 아니고 검증 세트에서) 평가해 보아야 합니다. 적절한 모델 크기를 찾는 일반적인 작업 흐름은 비교적 적은 수의 층과 파라미터로 시작합니다. 그다음 검증 손실이 감소되기 시작할 때까지 층이나 유닛의 수를 늘리는 것입니다.
영화 리뷰 분류 모델에 적용해 보죠. 원래 네트워크는 다음과 같습니다:
from tensorflow.keras import models
from tensorflow.keras import layers
original_model = models.Sequential()
original_model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
original_model.add(layers.Dense(16, activation='relu'))
original_model.add(layers.Dense(1, activation='sigmoid'))
original_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
더 작은 네트워크로 바꾸어 보죠:
smaller_model = models.Sequential()
smaller_model.add(layers.Dense(6, activation='relu', input_shape=(10000,)))
smaller_model.add(layers.Dense(6, activation='relu'))
smaller_model.add(layers.Dense(1, activation='sigmoid'))
smaller_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
원본 네트워크와 축소된 네트워크의 검증 손실을 비교해 보죠. 점으로 표현된 것이 작은 네트워크이고 덧셈 기호가 원래 네트워크 입니다(검증 손실이 작은 것이 좋은 모델입니다).
original_hist = original_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
Epoch 1/20 49/49 [==============================] - 3s 52ms/step - loss: 0.4468 - acc: 0.8234 - val_loss: 0.3712 - val_acc: 0.8476 Epoch 2/20 49/49 [==============================] - 1s 23ms/step - loss: 0.2561 - acc: 0.9098 - val_loss: 0.2969 - val_acc: 0.8831 Epoch 3/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2011 - acc: 0.9272 - val_loss: 0.2808 - val_acc: 0.8879 Epoch 4/20 49/49 [==============================] - 1s 21ms/step - loss: 0.1695 - acc: 0.9394 - val_loss: 0.3065 - val_acc: 0.8792 Epoch 5/20 49/49 [==============================] - 1s 22ms/step - loss: 0.1464 - acc: 0.9492 - val_loss: 0.3153 - val_acc: 0.8779 Epoch 6/20 49/49 [==============================] - 1s 21ms/step - loss: 0.1278 - acc: 0.9555 - val_loss: 0.3269 - val_acc: 0.8774 Epoch 7/20 49/49 [==============================] - 1s 20ms/step - loss: 0.1120 - acc: 0.9628 - val_loss: 0.3647 - val_acc: 0.8711 Epoch 8/20 49/49 [==============================] - 1s 20ms/step - loss: 0.1024 - acc: 0.9640 - val_loss: 0.4611 - val_acc: 0.8494 Epoch 9/20 49/49 [==============================] - 1s 20ms/step - loss: 0.0903 - acc: 0.9701 - val_loss: 0.4141 - val_acc: 0.8657 Epoch 10/20 49/49 [==============================] - 1s 22ms/step - loss: 0.0816 - acc: 0.9738 - val_loss: 0.4279 - val_acc: 0.8652 Epoch 11/20 49/49 [==============================] - 1s 23ms/step - loss: 0.0716 - acc: 0.9774 - val_loss: 0.4576 - val_acc: 0.8612 Epoch 12/20 49/49 [==============================] - 1s 22ms/step - loss: 0.0630 - acc: 0.9796 - val_loss: 0.4895 - val_acc: 0.8588 Epoch 13/20 49/49 [==============================] - 1s 23ms/step - loss: 0.0570 - acc: 0.9821 - val_loss: 0.5179 - val_acc: 0.8594 Epoch 14/20 49/49 [==============================] - 1s 21ms/step - loss: 0.0485 - acc: 0.9863 - val_loss: 0.5483 - val_acc: 0.8578 Epoch 15/20 49/49 [==============================] - 1s 20ms/step - loss: 0.0437 - acc: 0.9874 - val_loss: 0.5787 - val_acc: 0.8570 Epoch 16/20 49/49 [==============================] - 1s 21ms/step - loss: 0.0357 - acc: 0.9898 - val_loss: 0.6180 - val_acc: 0.8520 Epoch 17/20 49/49 [==============================] - 1s 23ms/step - loss: 0.0316 - acc: 0.9915 - val_loss: 0.6929 - val_acc: 0.8459 Epoch 18/20 49/49 [==============================] - 1s 21ms/step - loss: 0.0258 - acc: 0.9938 - val_loss: 0.7066 - val_acc: 0.8486 Epoch 19/20 49/49 [==============================] - 1s 21ms/step - loss: 0.0228 - acc: 0.9938 - val_loss: 0.7782 - val_acc: 0.8438 Epoch 20/20 49/49 [==============================] - 1s 22ms/step - loss: 0.0200 - acc: 0.9948 - val_loss: 0.7946 - val_acc: 0.8483
smaller_model_hist = smaller_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
Epoch 1/20 49/49 [==============================] - 3s 54ms/step - loss: 0.6174 - accuracy: 0.7296 - val_loss: 0.5623 - val_accuracy: 0.7910 Epoch 2/20 49/49 [==============================] - 1s 20ms/step - loss: 0.5027 - accuracy: 0.8482 - val_loss: 0.4799 - val_accuracy: 0.8528 Epoch 3/20 49/49 [==============================] - 1s 19ms/step - loss: 0.4116 - accuracy: 0.8950 - val_loss: 0.4064 - val_accuracy: 0.8817 Epoch 4/20 49/49 [==============================] - 1s 19ms/step - loss: 0.3277 - accuracy: 0.9188 - val_loss: 0.3476 - val_accuracy: 0.8837 Epoch 5/20 49/49 [==============================] - 1s 23ms/step - loss: 0.2621 - accuracy: 0.9303 - val_loss: 0.3099 - val_accuracy: 0.8877 Epoch 6/20 49/49 [==============================] - 1s 24ms/step - loss: 0.2170 - accuracy: 0.9374 - val_loss: 0.2897 - val_accuracy: 0.8890 Epoch 7/20 49/49 [==============================] - 1s 24ms/step - loss: 0.1862 - accuracy: 0.9435 - val_loss: 0.2836 - val_accuracy: 0.8880 Epoch 8/20 49/49 [==============================] - 1s 20ms/step - loss: 0.1644 - accuracy: 0.9513 - val_loss: 0.2841 - val_accuracy: 0.8868 Epoch 9/20 49/49 [==============================] - 1s 23ms/step - loss: 0.1479 - accuracy: 0.9557 - val_loss: 0.2893 - val_accuracy: 0.8848 Epoch 10/20 49/49 [==============================] - 1s 25ms/step - loss: 0.1336 - accuracy: 0.9605 - val_loss: 0.2971 - val_accuracy: 0.8834 Epoch 11/20 49/49 [==============================] - 1s 27ms/step - loss: 0.1221 - accuracy: 0.9639 - val_loss: 0.3075 - val_accuracy: 0.8805 Epoch 12/20 49/49 [==============================] - 1s 21ms/step - loss: 0.1113 - accuracy: 0.9682 - val_loss: 0.3196 - val_accuracy: 0.8782 Epoch 13/20 49/49 [==============================] - 1s 24ms/step - loss: 0.1014 - accuracy: 0.9715 - val_loss: 0.3289 - val_accuracy: 0.8775 Epoch 14/20 49/49 [==============================] - 1s 25ms/step - loss: 0.0927 - accuracy: 0.9748 - val_loss: 0.3452 - val_accuracy: 0.8752 Epoch 15/20 49/49 [==============================] - 1s 26ms/step - loss: 0.0852 - accuracy: 0.9774 - val_loss: 0.3542 - val_accuracy: 0.8740 Epoch 16/20 49/49 [==============================] - 1s 22ms/step - loss: 0.0779 - accuracy: 0.9802 - val_loss: 0.3801 - val_accuracy: 0.8688 Epoch 17/20 49/49 [==============================] - 1s 21ms/step - loss: 0.0708 - accuracy: 0.9827 - val_loss: 0.3914 - val_accuracy: 0.8683 Epoch 18/20 49/49 [==============================] - 1s 24ms/step - loss: 0.0641 - accuracy: 0.9850 - val_loss: 0.4075 - val_accuracy: 0.8654 Epoch 19/20 49/49 [==============================] - 1s 25ms/step - loss: 0.0580 - accuracy: 0.9866 - val_loss: 0.4190 - val_accuracy: 0.8672 Epoch 20/20 49/49 [==============================] - 1s 23ms/step - loss: 0.0524 - accuracy: 0.9885 - val_loss: 0.4352 - val_accuracy: 0.8647
epochs = range(1, 21)
original_val_loss = original_hist.history['val_loss']
smaller_model_val_loss = smaller_model_hist.history['val_loss']
import matplotlib.pyplot as plt
# ‘b+’는 파란색 덧셈 기호을 의미합니다
plt.plot(epochs, original_val_loss, 'b+', label='Original model')
# ‘bo’는 파란색 점을 의미합니다
plt.plot(epochs, smaller_model_val_loss, 'bo', label='Smaller model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()
여기서 볼 수 있듯이 작은 네트워크가 기본 네트워크보다 더 나중에 과대적합되기 시작했습니다(네 번째 에포크가 아니라 여섯 번째 에포크에서). 과대적합이 시작되었을 때 성능이 더 천천히 감소되었습니다.
이번에는 문제에 필요한 것보다 훨씬 더 많은 용량을 가진 네트워크를 비교해 보겠습니다:
bigger_model = models.Sequential()
bigger_model.add(layers.Dense(1024, activation='relu', input_shape=(10000,)))
bigger_model.add(layers.Dense(1024, activation='relu'))
bigger_model.add(layers.Dense(1, activation='sigmoid'))
bigger_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
bigger_model_hist = bigger_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
Epoch 1/20 49/49 [==============================] - 3s 60ms/step - loss: 0.5463 - accuracy: 0.7870 - val_loss: 0.3228 - val_accuracy: 0.8591 Epoch 2/20 49/49 [==============================] - 1s 23ms/step - loss: 0.2209 - accuracy: 0.9112 - val_loss: 0.3089 - val_accuracy: 0.8795 Epoch 3/20 49/49 [==============================] - 1s 22ms/step - loss: 0.1182 - accuracy: 0.9574 - val_loss: 0.3678 - val_accuracy: 0.8844 Epoch 4/20 49/49 [==============================] - 1s 23ms/step - loss: 0.0456 - accuracy: 0.9859 - val_loss: 0.4784 - val_accuracy: 0.8819 Epoch 5/20 49/49 [==============================] - 1s 21ms/step - loss: 0.1170 - accuracy: 0.9864 - val_loss: 0.5068 - val_accuracy: 0.8846 Epoch 6/20 49/49 [==============================] - 1s 22ms/step - loss: 6.9129e-04 - accuracy: 1.0000 - val_loss: 0.6863 - val_accuracy: 0.8859 Epoch 7/20 49/49 [==============================] - 1s 22ms/step - loss: 0.1429 - accuracy: 0.9886 - val_loss: 0.5295 - val_accuracy: 0.8789 Epoch 8/20 49/49 [==============================] - 1s 24ms/step - loss: 2.7313e-04 - accuracy: 1.0000 - val_loss: 0.7309 - val_accuracy: 0.8804 Epoch 9/20 49/49 [==============================] - 1s 22ms/step - loss: 2.1291e-05 - accuracy: 1.0000 - val_loss: 0.8335 - val_accuracy: 0.8838 Epoch 10/20 49/49 [==============================] - 1s 21ms/step - loss: 3.2619e-06 - accuracy: 1.0000 - val_loss: 0.9701 - val_accuracy: 0.8834 Epoch 11/20 49/49 [==============================] - 1s 22ms/step - loss: 5.6623e-07 - accuracy: 1.0000 - val_loss: 1.1078 - val_accuracy: 0.8839 Epoch 12/20 49/49 [==============================] - 1s 22ms/step - loss: 1.0568e-07 - accuracy: 1.0000 - val_loss: 1.2271 - val_accuracy: 0.8844 Epoch 13/20 49/49 [==============================] - 1s 23ms/step - loss: 3.0774e-08 - accuracy: 1.0000 - val_loss: 1.3046 - val_accuracy: 0.8839 Epoch 14/20 49/49 [==============================] - 1s 22ms/step - loss: 1.4670e-08 - accuracy: 1.0000 - val_loss: 1.3469 - val_accuracy: 0.8840 Epoch 15/20 49/49 [==============================] - 1s 22ms/step - loss: 9.4874e-09 - accuracy: 1.0000 - val_loss: 1.3727 - val_accuracy: 0.8844 Epoch 16/20 49/49 [==============================] - 1s 22ms/step - loss: 7.0578e-09 - accuracy: 1.0000 - val_loss: 1.3926 - val_accuracy: 0.8844 Epoch 17/20 49/49 [==============================] - 1s 22ms/step - loss: 5.6377e-09 - accuracy: 1.0000 - val_loss: 1.4072 - val_accuracy: 0.8844 Epoch 18/20 49/49 [==============================] - 1s 22ms/step - loss: 4.7102e-09 - accuracy: 1.0000 - val_loss: 1.4203 - val_accuracy: 0.8845 Epoch 19/20 49/49 [==============================] - 1s 23ms/step - loss: 4.0476e-09 - accuracy: 1.0000 - val_loss: 1.4307 - val_accuracy: 0.8844 Epoch 20/20 49/49 [==============================] - 1s 23ms/step - loss: 3.5510e-09 - accuracy: 1.0000 - val_loss: 1.4400 - val_accuracy: 0.8843
다음은 더 큰 네트워크가 기본 네트워크에 비해 얼마나 차이나는지를 보여줍니다. 점이 용량이 큰 네트워크의 검증 손실이고 덧셈 기호는 원본 네트워크의 검증 손실입니다.
bigger_model_val_loss = bigger_model_hist.history['val_loss']
plt.plot(epochs, original_val_loss, 'b+', label='Original model')
plt.plot(epochs, bigger_model_val_loss, 'bo', label='Bigger model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()
용량이 큰 네트워크는 첫 번째 에포크 이후 거의 바로 과대적합이 시작되어 갈수록 더 심해집니다. 검증 손실도 매우 불안정합니다.
다음은 두 네트워크의 훈련 손실입니다:
original_train_loss = original_hist.history['loss']
bigger_model_train_loss = bigger_model_hist.history['loss']
plt.plot(epochs, original_train_loss, 'b+', label='Original model')
plt.plot(epochs, bigger_model_train_loss, 'bo', label='Bigger model')
plt.xlabel('Epochs')
plt.ylabel('Training loss')
plt.legend()
plt.show()
여기서 볼 수 있듯이 용량이 큰 네트워크는 훈련 손실이 매우 빠르게 0에 가까워집니다. 용량이 많은 네트워크일수록 더 빠르게 훈련 데이터를 모델링할 수 있습니다(결국 훈련 손실이 낮아집니다). 하지만 더욱 과대적합에 민감해집니다(결국 훈련과 검증 손실 사이에 큰 차이가 발생합니다).
오캄의 면도날 이론을 알고 있을지 모르겠습니다. 어떤 것에 대한 두 가지의 설명이 있다면 더 적은 가정을 필요하는 간단한 설명이 옳을 것이라는 이론입니다. 이 개념은 신경망으로 학습되는 모델에도 적용됩니다. 어떤 훈련 데이터와 네트워크 구조가 주어졌을 때 데이터를 설명할 수 있는 가중치 값의 집합은 여러 개(여러 개의 모델)입니다. 간단한 모델이 복잡한 모델보다 덜 과대적합될 가능성이 높습니다.
여기에서 간단한 모델은 파라미터 값 분포의 엔트로피가 작은 모델입니다(또는 앞 절에서 본 것처럼 적은 수의 파라미터를 가진 모델입니다). 그러므로 과대적합을 완화시키기 위한 일반적인 방법은 네트워크의 복잡도에 제한을 두어 가중치가 작은 값을 가지도록 강제하는 것입니다. 가중치 값의 분포가 더 균일하게 됩니다. 이를 가중치 규제라고 하고 네트워크의 손실 함수에 큰 가중치에 연관된 비용을 추가합니다. 두 가지 형태의 비용이 있습니다.
케라스에서 가중치 규제 인스턴스를 층의 키워드 매개변수로 전달하여 가중치 규제를 추가할 수 있습니다. 영화 리뷰 분류 네트워크에 L2 가중치 규제를 추가해 보죠:
from tensorflow.keras import regularizers
l2_model = models.Sequential()
l2_model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
activation='relu', input_shape=(10000,)))
l2_model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
activation='relu'))
l2_model.add(layers.Dense(1, activation='sigmoid'))
l2_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
l2(0.001)
는 가중치 행렬의 모든 원소를 제곱하고 0.001
을 곱하여 네트워크의 전체 손실에 더해진다는 의미입니다. 이 페널티 항은 훈련할 때만 추가됩니다. 이 네트워크의 손실은 테스트보다 훈련할 때 더 높을 것입니다.
L2 규제 페널티의 효과를 확인해 보죠:
l2_model_hist = l2_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
Epoch 1/20 49/49 [==============================] - 3s 53ms/step - loss: 0.4987 - accuracy: 0.8176 - val_loss: 0.3810 - val_accuracy: 0.8826 Epoch 2/20 49/49 [==============================] - 1s 21ms/step - loss: 0.3241 - accuracy: 0.9020 - val_loss: 0.3467 - val_accuracy: 0.8852 Epoch 3/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2753 - accuracy: 0.9191 - val_loss: 0.3347 - val_accuracy: 0.8877 Epoch 4/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2572 - accuracy: 0.9273 - val_loss: 0.3370 - val_accuracy: 0.8873 Epoch 5/20 49/49 [==============================] - 1s 21ms/step - loss: 0.2425 - accuracy: 0.9327 - val_loss: 0.3635 - val_accuracy: 0.8769 Epoch 6/20 49/49 [==============================] - 1s 21ms/step - loss: 0.2342 - accuracy: 0.9361 - val_loss: 0.3546 - val_accuracy: 0.8801 Epoch 7/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2271 - accuracy: 0.9388 - val_loss: 0.3607 - val_accuracy: 0.8802 Epoch 8/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2184 - accuracy: 0.9425 - val_loss: 0.3682 - val_accuracy: 0.8791 Epoch 9/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2200 - accuracy: 0.9414 - val_loss: 0.4186 - val_accuracy: 0.8614 Epoch 10/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2111 - accuracy: 0.9431 - val_loss: 0.3840 - val_accuracy: 0.8739 Epoch 11/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2104 - accuracy: 0.9430 - val_loss: 0.4121 - val_accuracy: 0.8660 Epoch 12/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2073 - accuracy: 0.9452 - val_loss: 0.4038 - val_accuracy: 0.8717 Epoch 13/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2034 - accuracy: 0.9464 - val_loss: 0.4219 - val_accuracy: 0.8668 Epoch 14/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2029 - accuracy: 0.9460 - val_loss: 0.3999 - val_accuracy: 0.8720 Epoch 15/20 49/49 [==============================] - 1s 20ms/step - loss: 0.1974 - accuracy: 0.9496 - val_loss: 0.4439 - val_accuracy: 0.8585 Epoch 16/20 49/49 [==============================] - 1s 19ms/step - loss: 0.1983 - accuracy: 0.9475 - val_loss: 0.4151 - val_accuracy: 0.8695 Epoch 17/20 49/49 [==============================] - 1s 20ms/step - loss: 0.1921 - accuracy: 0.9494 - val_loss: 0.4286 - val_accuracy: 0.8685 Epoch 18/20 49/49 [==============================] - 1s 22ms/step - loss: 0.1876 - accuracy: 0.9533 - val_loss: 0.4219 - val_accuracy: 0.8682 Epoch 19/20 49/49 [==============================] - 1s 20ms/step - loss: 0.1897 - accuracy: 0.9510 - val_loss: 0.4683 - val_accuracy: 0.8597 Epoch 20/20 49/49 [==============================] - 1s 20ms/step - loss: 0.1808 - accuracy: 0.9542 - val_loss: 0.4338 - val_accuracy: 0.8666
l2_model_val_loss = l2_model_hist.history['val_loss']
plt.plot(epochs, original_val_loss, 'b+', label='Original model')
plt.plot(epochs, l2_model_val_loss, 'bo', label='L2-regularized model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()
여기서 볼 수 있듯이 두 모델이 동일한 파라미터 수를 가지고 있더라도 L2 규제를 사용한 모델(점)이 기본 모델(덧셈 기호)보다 훨씬 더 과대적합에 잘 견디고 있습니다.
케라스에서 L2 규제 대신에 다음과 같은 가중치 규제 중 하나를 사용할 수 있습니다:
from keras import regularizers
# L1 규제
regularizers.l1(0.001)
# L1과 L2 규제 병행
regularizers.l1_l2(l1=0.001, l2=0.001)
l1_model = models.Sequential()
l1_model.add(layers.Dense(16, kernel_regularizer=regularizers.l1(0.0001),
activation='relu', input_shape=(10000,)))
l1_model.add(layers.Dense(16, kernel_regularizer=regularizers.l1(0.0001),
activation='relu'))
l1_model.add(layers.Dense(1, activation='sigmoid'))
l1_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
l1_model_hist = l1_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
Epoch 1/20 49/49 [==============================] - 3s 54ms/step - loss: 0.5637 - accuracy: 0.8135 - val_loss: 0.4330 - val_accuracy: 0.8806 Epoch 2/20 49/49 [==============================] - 1s 20ms/step - loss: 0.3798 - accuracy: 0.8940 - val_loss: 0.3813 - val_accuracy: 0.8854 Epoch 3/20 49/49 [==============================] - 1s 20ms/step - loss: 0.3367 - accuracy: 0.9046 - val_loss: 0.3592 - val_accuracy: 0.8892 Epoch 4/20 49/49 [==============================] - 1s 21ms/step - loss: 0.3150 - accuracy: 0.9115 - val_loss: 0.3722 - val_accuracy: 0.8802 Epoch 5/20 49/49 [==============================] - 1s 21ms/step - loss: 0.3036 - accuracy: 0.9150 - val_loss: 0.3573 - val_accuracy: 0.8872 Epoch 6/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2948 - accuracy: 0.9185 - val_loss: 0.3597 - val_accuracy: 0.8872 Epoch 7/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2891 - accuracy: 0.9204 - val_loss: 0.3672 - val_accuracy: 0.8836 Epoch 8/20 49/49 [==============================] - 1s 21ms/step - loss: 0.2817 - accuracy: 0.9254 - val_loss: 0.3659 - val_accuracy: 0.8855 Epoch 9/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2775 - accuracy: 0.9232 - val_loss: 0.3833 - val_accuracy: 0.8767 Epoch 10/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2710 - accuracy: 0.9282 - val_loss: 0.3748 - val_accuracy: 0.8828 Epoch 11/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2700 - accuracy: 0.9281 - val_loss: 0.4316 - val_accuracy: 0.8619 Epoch 12/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2638 - accuracy: 0.9310 - val_loss: 0.3805 - val_accuracy: 0.8820 Epoch 13/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2561 - accuracy: 0.9364 - val_loss: 0.3899 - val_accuracy: 0.8782 Epoch 14/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2598 - accuracy: 0.9329 - val_loss: 0.3924 - val_accuracy: 0.8785 Epoch 15/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2520 - accuracy: 0.9368 - val_loss: 0.4021 - val_accuracy: 0.8753 Epoch 16/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2523 - accuracy: 0.9364 - val_loss: 0.4012 - val_accuracy: 0.8765 Epoch 17/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2441 - accuracy: 0.9409 - val_loss: 0.4077 - val_accuracy: 0.8741 Epoch 18/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2419 - accuracy: 0.9400 - val_loss: 0.4228 - val_accuracy: 0.8714 Epoch 19/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2389 - accuracy: 0.9421 - val_loss: 0.4175 - val_accuracy: 0.8740 Epoch 20/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2375 - accuracy: 0.9418 - val_loss: 0.4187 - val_accuracy: 0.8728
l1_model_val_loss = l1_model_hist.history['val_loss']
plt.plot(epochs, original_val_loss, 'b+', label='Original model')
plt.plot(epochs, l1_model_val_loss, 'bo', label='L1-regularized model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()
l1l2_model = models.Sequential()
l1l2_model.add(layers.Dense(16, kernel_regularizer=regularizers.l1_l2(l1=0.0001, l2=0.0001),
activation='relu', input_shape=(10000,)))
l1l2_model.add(layers.Dense(16, kernel_regularizer=regularizers.l1_l2(l1=0.0001, l2=0.0001),
activation='relu'))
l1l2_model.add(layers.Dense(1, activation='sigmoid'))
l1l2_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
l1l2_model_hist = l1l2_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
Epoch 1/20 49/49 [==============================] - 3s 57ms/step - loss: 0.5601 - accuracy: 0.8158 - val_loss: 0.4309 - val_accuracy: 0.8786 Epoch 2/20 49/49 [==============================] - 1s 20ms/step - loss: 0.3765 - accuracy: 0.8943 - val_loss: 0.3766 - val_accuracy: 0.8872 Epoch 3/20 49/49 [==============================] - 1s 19ms/step - loss: 0.3360 - accuracy: 0.9062 - val_loss: 0.3665 - val_accuracy: 0.8858 Epoch 4/20 49/49 [==============================] - 1s 20ms/step - loss: 0.3151 - accuracy: 0.9124 - val_loss: 0.3779 - val_accuracy: 0.8821 Epoch 5/20 49/49 [==============================] - 1s 20ms/step - loss: 0.3095 - accuracy: 0.9134 - val_loss: 0.3694 - val_accuracy: 0.8839 Epoch 6/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2995 - accuracy: 0.9182 - val_loss: 0.3725 - val_accuracy: 0.8826 Epoch 7/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2926 - accuracy: 0.9217 - val_loss: 0.3693 - val_accuracy: 0.8838 Epoch 8/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2876 - accuracy: 0.9235 - val_loss: 0.4244 - val_accuracy: 0.8662 Epoch 9/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2841 - accuracy: 0.9241 - val_loss: 0.3841 - val_accuracy: 0.8804 Epoch 10/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2816 - accuracy: 0.9273 - val_loss: 0.3823 - val_accuracy: 0.8826 Epoch 11/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2746 - accuracy: 0.9300 - val_loss: 0.3877 - val_accuracy: 0.8804 Epoch 12/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2717 - accuracy: 0.9295 - val_loss: 0.3842 - val_accuracy: 0.8812 Epoch 13/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2678 - accuracy: 0.9319 - val_loss: 0.4187 - val_accuracy: 0.8678 Epoch 14/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2631 - accuracy: 0.9344 - val_loss: 0.3976 - val_accuracy: 0.8776 Epoch 15/20 49/49 [==============================] - 1s 21ms/step - loss: 0.2585 - accuracy: 0.9352 - val_loss: 0.3955 - val_accuracy: 0.8781 Epoch 16/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2545 - accuracy: 0.9355 - val_loss: 0.3966 - val_accuracy: 0.8781 Epoch 17/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2483 - accuracy: 0.9391 - val_loss: 0.4042 - val_accuracy: 0.8755 Epoch 18/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2459 - accuracy: 0.9397 - val_loss: 0.4225 - val_accuracy: 0.8698 Epoch 19/20 49/49 [==============================] - 1s 20ms/step - loss: 0.2422 - accuracy: 0.9445 - val_loss: 0.4183 - val_accuracy: 0.8724 Epoch 20/20 49/49 [==============================] - 1s 21ms/step - loss: 0.2366 - accuracy: 0.9440 - val_loss: 0.4138 - val_accuracy: 0.8748
l1l2_model_val_loss = l1l2_model_hist.history['val_loss']
plt.plot(epochs, original_val_loss, 'b+', label='Original model')
plt.plot(epochs, l1l2_model_val_loss, 'bo', label='L1,L2-regularized model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()
드롭아웃은 토론토 대학의 제프리 힌튼과 그의 학생들이 개발했습니다. 신경망을 위해 사용되는 규제 기법 중에서 가장 효과적이고 널리 사용되는 방법 중에 하나입니다. 네트워크의 층에 드롭아웃을 적용하면 훈련하는 동안 무작위로 층의 일부 출력 특성을 제외시킵니다(0으로 만듭니다). 한 층이 정상적으로 훈련하는 동안에는 어떤 입력 샘플에 대해 [0.2, 0.5, 1.3, 0.8, 1.1]
벡터를 출력한다고 가정합시다. 드롭아웃을 적용하면 이 벡터의 일부가 무작위로 0으로 바뀝니다. 예를 들면 [0, 0.5, 1.3, 0, 1.1]
이 됩니다. 드롭아웃 비율은 0이 될 특성의 비율입니다. 보통 0.2에서 0.5 사이로 지정됩니다. 테스트 단계에서는 어떤 유닛도 드롭아웃되지 않습니다. 대신에 층의 출력을 드롭아웃 비율에 비례하여 줄여 줍니다. 훈련할 때보다 더 많은 유닛이 활성화되기 때문입니다.
크기가 (batch_size, features)
인 한 층의 출력을 담고 있는 넘파이 행렬을 생각해 보겠습니다. 훈련할 때는 이 행렬의 값의 일부가 랜덤하게 0이 됩니다:
# 훈련할 때 유닛의 출력 중 50%를 버립니다
layer_output *= np.random.randint(0, high=2, size=layer_output.shape)
테스트할 때는 드롭아웃 비율로 출력을 낮추어 주어야 합니다. 여기에서는 0.5배만큼 스케일을 조정했습니다(앞에서 절반의 유닛을 드롭아웃했으므로):
# 테스트 단계
layer_output *= 0.5
훈련 단계에 이 두 연산을 포함시켜 테스트 단계에는 출력을 그대로 두도록 구현할 수 있습니다. 실제로 종종 이런 방식으로 구현합니다:
# 훈련 단계
layer_output *= np.randint(0, high=2, size=layer_output.shape)
# 여기에서 스케일을 낮추는 대신 높입니다![image.png](attachment:image.png)
layer_output /= 0.5
이 기법이 이상하고 무계획적으로 보일 수 있습니다. 왜 드롭아웃이 과대적합을 줄이는 데 도움이 될까요? 힌튼은 은행에서 사용하는 부정 방지 메커니즘에서 착안했다고 합니다. 그의 말을 빌리면 “은행에 갔을 때 행원들이 계속 바뀌길래 왜 그런지를 물었습니다. 자신들도 이유는 모르지만 자주 업무가 바뀐다고 했습니다. 나는 은행에서 부정 행위를 하려면 직원들 사이의 유대가 필요하기 때문이라고 판단했습니다. 각 샘플에 대해 뉴런의 일부를 무작위하게 제거하면 뉴런의 부정한 협업을 방지하고 결국 과대적합을 감소시킨다는 것을 깨달았습니다."
핵심 아이디어는 층의 출력 값에 노이즈를 추가하여 중요하지 않은 우연한 패턴(힌튼이 이야기한 부정한 협업)을 깨뜨리는 것입니다. 노이즈가 없다면 네트워크가 이 패턴을 기억하기 시작할 것입니다.
케라스에서는 층의 출력 바로 뒤에 Dropout
층을 추가하여 네트워크에 드롭아웃을 적용할 수 있습니다:
model.add(layers.Dropout(0.5))
IMDB 네트워크에 두 개의 Dropout
층을 추가하고 과대적합을 얼마나 줄여주는지 확인해 보겠습니다:
dpt_model = models.Sequential()
dpt_model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
dpt_model.add(layers.Dropout(0.5))
dpt_model.add(layers.Dense(16, activation='relu'))
dpt_model.add(layers.Dropout(0.5))
dpt_model.add(layers.Dense(1, activation='sigmoid'))
dpt_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
dpt_model_hist = dpt_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
Epoch 1/20 49/49 [==============================] - 2s 47ms/step - loss: 0.5620 - accuracy: 0.7064 - val_loss: 0.4052 - val_accuracy: 0.8664 Epoch 2/20 49/49 [==============================] - 1s 19ms/step - loss: 0.4086 - accuracy: 0.8379 - val_loss: 0.3078 - val_accuracy: 0.8863 Epoch 3/20 49/49 [==============================] - 1s 18ms/step - loss: 0.3280 - accuracy: 0.8810 - val_loss: 0.2818 - val_accuracy: 0.8878 Epoch 4/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2744 - accuracy: 0.9033 - val_loss: 0.2792 - val_accuracy: 0.8888 Epoch 5/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2423 - accuracy: 0.9187 - val_loss: 0.2839 - val_accuracy: 0.8898 Epoch 6/20 49/49 [==============================] - 1s 19ms/step - loss: 0.2126 - accuracy: 0.9302 - val_loss: 0.3057 - val_accuracy: 0.8858 Epoch 7/20 49/49 [==============================] - 1s 19ms/step - loss: 0.1928 - accuracy: 0.9353 - val_loss: 0.3241 - val_accuracy: 0.8863 Epoch 8/20 49/49 [==============================] - 1s 19ms/step - loss: 0.1787 - accuracy: 0.9436 - val_loss: 0.3348 - val_accuracy: 0.8860 Epoch 9/20 49/49 [==============================] - 1s 20ms/step - loss: 0.1606 - accuracy: 0.9471 - val_loss: 0.3735 - val_accuracy: 0.8848 Epoch 10/20 49/49 [==============================] - 1s 19ms/step - loss: 0.1464 - accuracy: 0.9515 - val_loss: 0.4019 - val_accuracy: 0.8822 Epoch 11/20 49/49 [==============================] - 1s 20ms/step - loss: 0.1392 - accuracy: 0.9525 - val_loss: 0.4256 - val_accuracy: 0.8804 Epoch 12/20 49/49 [==============================] - 1s 19ms/step - loss: 0.1324 - accuracy: 0.9567 - val_loss: 0.4346 - val_accuracy: 0.8795 Epoch 13/20 49/49 [==============================] - 1s 18ms/step - loss: 0.1264 - accuracy: 0.9587 - val_loss: 0.4524 - val_accuracy: 0.8775 Epoch 14/20 49/49 [==============================] - 1s 18ms/step - loss: 0.1194 - accuracy: 0.9605 - val_loss: 0.4912 - val_accuracy: 0.8770 Epoch 15/20 49/49 [==============================] - 1s 18ms/step - loss: 0.1180 - accuracy: 0.9602 - val_loss: 0.5113 - val_accuracy: 0.8770 Epoch 16/20 49/49 [==============================] - 1s 19ms/step - loss: 0.1112 - accuracy: 0.9626 - val_loss: 0.5181 - val_accuracy: 0.8757 Epoch 17/20 49/49 [==============================] - 1s 19ms/step - loss: 0.1128 - accuracy: 0.9639 - val_loss: 0.5546 - val_accuracy: 0.8762 Epoch 18/20 49/49 [==============================] - 1s 19ms/step - loss: 0.1060 - accuracy: 0.9655 - val_loss: 0.5615 - val_accuracy: 0.8750 Epoch 19/20 49/49 [==============================] - 1s 19ms/step - loss: 0.1033 - accuracy: 0.9652 - val_loss: 0.5839 - val_accuracy: 0.8734 Epoch 20/20 49/49 [==============================] - 1s 20ms/step - loss: 0.1028 - accuracy: 0.9668 - val_loss: 0.5921 - val_accuracy: 0.8725
결과 그래프를 그려 보죠:
dpt_model_val_loss = dpt_model_hist.history['val_loss']
plt.plot(epochs, original_val_loss, 'b+', label='Original model')
plt.plot(epochs, dpt_model_val_loss, 'bo', label='Dropout-regularized model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()
여기에서도 기본 네트워크보다 확실히 향상되었습니다.
정리하면 신경망에서 과대적합을 방지하기 위해 가장 널리 사용하는 방법은 다음과 같습니다.