Python/ML & DL 공부

[DL] 07-3 신경망 모델 훈련

dori_0 2022. 3. 27. 19:26

혼자 공부하는 머신러닝+딥러닝 책을 바탕으로 공부한 내용입니다.

 

CH7 딥러닝 시작 ③

최상의 신경망 모델 얻기

 


 

케라스 API를 사용해 모델을 훈련하는데 필요한 다양한 도구들을 알아보자

 

▶ 손실 곡선

먼저, 패션 MNIST 데이터셋을 불러와 훈련 세트와 검증 세트로 나누자

from tensorflow import keras
from sklearn.model_selection import train_test_split

(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
train_scaled = train_input / 255.0
train_scaled, val_scaled, train_target, val_target = train_test_split(
    train_scaled, train_target, test_size=0.2, random_state=42)

 

이전과는 다르게 모델을 만드는 함수를 정의한 후 사용할 것이다.

# 모델 만들기
def model_fn(a_layer=None):
    model = keras.Sequential()
    model.add(keras.layers.Flatten(input_shape=(28,28)))
    model.add(keras.layers.Dense(100, activation='relu'))
    if a_layer:
        model.add(a_layer)
    model.add(keras.layers.Dense(10, activation='softmax'))
    return model
  • if 구문의 역할은 model_fn() 함수에 a_layer 매개변수로 케라스 층을 추가하면 은닉층 뒤에 하나의 층을 추가하는 것

 

model = model_fn()
model.summary()

 

model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
history = model.fit(train_scaled, train_target, epochs=5, verbose=0)
print(history.history.keys())  #dict_keys(['loss', 'accuracy'])
  • fit() 메서드의 결과를 history 변수에 담았다
  • fit() 메서드는 History 클래스 객체를 반환한다. (훈련 과정에서 계산한 지표인 손실, 정확도 값이 저장되어 있음)

 

 

에포크 횟수에 따른 loss와 accuracy를 그래프로 그려보자

# loss 그래프
import matplotlib.pyplot as plt

plt.plot(history.history['loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

# 정확도 그래프
plt.plot(history.history['accuracy'])
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()

  • 에포크 횟수가 많아질수록 손실이 감소하고 정확도가 향상한다.

 

 

에포크 횟수를 20으로 늘려서 모델을 훈련하고 손실 그래프를 그려보자

# 에포크 20으로 늘리기
model = model_fn()
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
history = model.fit(train_scaled, train_target, epochs=20, verbose=0)

plt.plot(history.history['loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

  • 손실이 잘 감소하는 것을 확인할 수 있다.

 

 

▶ 검증 손실

에포크에 따른 과대적합, 과소적합을 파악하려면 훈련 세트 뿐만 아니라 검증 세트의 그래프도 그려봐야한다.

( 인공 신경망 모델이 최적화하는 대상은 정확도가 아니라 손실 함수이다.)

 

 

에포크마다 검증 손실을 계산하기 위해 fit() 메서드에 검증 데이터를 전달하였다.

model = model_fn()
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
history = model.fit(train_scaled, train_target, epochs=20, verbose=0,
                    validation_data=(val_scaled, val_target))
print(history.history.keys())

  • val_loss에는 검증 세트에 대한 손실, val_accuracy에는 검증 세트에 대한 정확도가 들어있다.

 

 

과대/과소적합 문제를 조사하기 위해 훈련 손실과 검증 손실을 그래프로 그려보자

# 훈련 손실, 검증 손실 그래프
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

  • 검증 손실이 감소하다가 2.5번째 에포크 부터 손실이 다시 상승하는 것을 확인할 수 있다.
  • 검증 손실이 상승하는 시점을 뒤로 늦추면 손실이 줄어들뿐만 아니라 검증 세트에 대한 정확도도 증가한다.

 

 

옵티마이저 하이퍼파라미터를 조정하여 과대적합을 완화시킬 수 있는지 확인해보자

기본 RMSprop 옵티마이저 대신 Adam 옵티마이저를 적용해 볼 것이다.

# Adam 옵티마이저 적용
model = model_fn()
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')
history = model.fit(train_scaled, train_target, epochs=20, verbose=0,
                    validation_data=(val_scaled, val_target))

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

  • 과대적합이 훨씬 줄어든 것을 확인할 수 있다.

 

 

▶ 드롭아웃

  1. 은닉층에 있는 뉴런의 출력을 랜덤하게 꺼서 과대적합을 막는 기법
  2. 드롭아웃은 훈련 중에 적용되며 평가나 예측에서는 적용하지 않음(텐서플로우는 이를 자동으로 처리함)
  3. 케라스에서는 keras.layers 패키지 아래 Dropout 클래스로 제공함

 

앞서 정의한 model_fn() 함수에 드롭아웃 객체를 전달하여 층을 추가해보자

model = model_fn(keras.layers.Dropout(0.3))
model.summary()

  • 30% 정도를 드롭아웃 함
  • Dropout은 훈련되는 모델 파라미터가 없는 것을 확인할 수 있다.

 

 

이전처럼 훈련 손실과 검증 손실의 그래프를 그려 비교해보자

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')
history = model.fit(train_scaled, train_target, epochs=20, verbose=0,
                    validation_data=(val_scaled, val_target))

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

  • 과대적합이 확실히 줄어든 것을 확인할 수 있다.

 

이 모델은 20번의 에포크 동안 훈련했으므로 다소 과대적합 되어 있다.

과대적합 되지 않은 모델을 얻기 위해서 에포크 횟수를 10으로 하고 다시 훈련해보자

 

 

 

▶ 모델 저장과 복원

# 에포크 횟수 10으로 변경
model = model_fn(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')
history = model.fit(train_scaled, train_target, epochs=10, verbose=0,
                    validation_data=(val_scaled, val_target))

 

① 케라스 모델은 훈련된 모델의 파라미터를 저장하는 save_weights() 메서드를 제공한다.

② 모델 구조와 모델 파라미터를 함께 저장하는 save() 메서드도 제공한다.

# 훈련된 모델의 파라미터 저장
model.save_weights('model-weights.h5')
# 모델 구조, 모델 파라미터 저장
model.save('model-whole.h5')

!ls -al *.h5

  • 두 파일이 잘 만들어졌다.

 

 

①, ②를 사용하여 모델을 복원해볼 것이다.

 

① 훈련하지 않은 새로운 모델을 만들고 model-weights.h5 파일에서 훈련된 모델 파라미터를 읽어 사용하기

# model-weights.h5 사용
model = model_fn(keras.layers.Dropout(0.3))
model.load_weights('model-weights.h5')
  • load_weights() 메서드를 사용하려면 save_weights() 메서드로 저장했던 모델과 정확히 같은 구조를 가져야 한다.

 

이 모델의 검증 정확도를 확인해보자

케라스의 predict() 메서드는 사이킷런과 달리 샘플마다 10개의 클래스에 대한 확률을 반환한다.

10개의 확률 중 가장 큰 값의 인덱스를 골라 타깃 레이블과 비교해 정확도를 계산해보자

import numpy as np

val_labels = np.argmax(model.predict(val_scaled), axis=-1)
np.mean(val_labels == val_target) # 타깃값도 0부터 시작함 (같으면 1, 다르면 0)
  • 넘파이 argmax() 함수는 배열에서 가장 큰 값의 인덱스를 반환한다.
  • argmax() 함수의 axis=-1은 배열의 마지막 차원을 따라 최댓값을 고른다
  • axis=1이면 열을 따라 각 행의 최댓값의 인덱스를 선택, axis=0이면 행을 따라 각 열의 최댓값의 인덱스를 선택

 

 

② 모델 전체를 model-whole.h5 파일에서 읽어 바로 사용하기

# model-whole.h5 사용
model = keras.models.load_model('model-whole.h5')
model.evaluate(val_scaled, val_target)

  • 모델이 저장된 파일을 읽을 때는 load_model() 함수 사용
  • 같은 모델을 저장하고 다시 불렀기 때문에 위와 동일한 정확도를 얻었다.

 

우리는 20번의 에포크동안 모델을 훈련한 후 검증 점수가 상승하는 지점을 확인했다.
그 다음 모델을 과대적합 되지 않는 에포크만큼(10) 다시 훈련했다.
이렇게 모델을 두 번씩 훈련하지 않고 한 번에 끝낼 수 있는 방법은 콜백을 사용하는 것이다!

 

 

▶ 콜백

  1. 케라스 모델을 훈련하는 도중 어떤 작업을 수행할 수 있도록 도와주는 도구
  2. 대표적으로 최상의 모델을 자동으로 저장해주거나 검증 점수가 더 이상 향상되지 않으면 일찍 종료해주는 것이 있다.
  3. 조기종료 - 검증 점수가 더 이상 감소하지 않고 상승하여 과대적합이 일어나면 훈련을 멈추는 기법 (계산 비용, 시간을 절약할 수 있음)

 

model = model_fn(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
              metrics='accuracy')
# callback
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-model.h5',
                                                save_best_only=True)
model.fit(train_scaled, train_target, epochs=20, verbose=0,
          validation_data=(val_scaled, val_target),
          callbacks=[checkpoint_cb])
  • ModelCheckpoint 클래스의 객체 checkpoint_cb를 만든 후 callbacks 매개변수에 리스트로 전달
  • best-model.h5에 최상의 검증 점수를 낸 모델이 저장된다.

 

 

이 모델을 load_model()로 다시 읽어서 예측을 수행해보자

model = keras.models.load_model('best-model.h5')
model.evaluate(val_scaled, val_target)

  • ModelCheckpoint 콜백이 가장 낮은 검증 점수의 모델을 자동으로 저장해주었다.

 

 

여전히 20번의 에포크를 수행하는데, 검증 점수가 상승하기 시작하면 과대적합이 커지므로 훈련을 계속 할 필요가 없다. 따라서 과대적합이 시작되기 전에 훈련을 미리 중지하는 조기 종료를 사용할 것이다.

 

 

  1. 케라스는 조기 종료를 위한 EarlyStopping 콜백을 제공함
  2. patience=2로 지정하면 2번 연속 검증 점수가 향상되지 않았을 때 훈련을 중지함
  3. restore_best_weights=True로 지정하면 가장 낮은 검증 손실을 낸 모델 파라미터로 되돌림
# 조기 종료
model = model_fn(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
              metrics='accuracy')
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-model.h5',
                                                save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=2,
                                                  restore_best_weights=True)
history = model.fit(train_scaled, train_target, epochs=20, verbose=0,
                    validation_data=(val_scaled, val_target),
                    callbacks=[checkpoint_cb, early_stopping_cb])
  • EarlyStopping 콜백과 ModelCheckpoint 콜백을 함께 사용하면 가장 낮은 검증 손실의 모델을 파일에 저장하고 검증 손실이 다시 상승할 때 훈련을 중지할 수 있다.
  • 훈련을 중지한 뒤 현재 모델의 파라미터를 최상의 파라미터로 되돌린다.

 

print(early_stopping_cb.stopped_epoch)
  • stoppen_epoch 속성에서 몇 번째 에포크에서 훈련이 중지되었는지 확인할 수 있다.
  • 13번째 에포크에서 훈련이 중지되었다. (11번째 에포크가 최상의 모델, 에포크 횟수는 0부터 시작함)

 

 

훈련 손실과 검증 손실을 출력해서 확인해보자

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

  • 11번째 에포크에서 가장 낮은 손실을 기록했고 13번째 에포크에서 훈련이 중지되었다.

 

마지막으로 조기 종료로 얻은 모델을 사용해 검증 세트에 대한 성능을 확인해보면

model.evaluate(val_scaled, val_target)

 

 

 

▶ 정리

  1. 과대적합을 막기 위해 신경망에서 즐겨 사용하는 대표적인 규제 방법인 드롭아웃을 알아보았다.
  2. 케라스는 훈련된 모델의 파라미터를 저장하고 다시 불러오는 메서드를 제공한다.
    • 모델 전체를 파일에 저장하고 파일에서 모델을 만들수도 있다.
    • 과대적합이 되기 전의 에포크를 수동으로 찾아 모델을 다시 훈련하는 대신 콜백을 사용하면 자동으로 최상의 모델을 유지할 수 있다.