Training Neural Network

변현섭·2024년 8월 1일
0

지금은? AI 전성시대!

목록 보기
18/21
post-thumbnail

1. Loss Curve

fit() 메서드로 모델을 훈련할 때, 아래와 같은 메시지가 출력된 것을 기억할 것이다.

<keras.src.callbacks.History at 0x7d0938309540>

이 메시지의 정체는 Keras의 fit() 메서드가 반환한 History 클래스의 객체이다. History 객체에는 훈련 과정에서 계산한 지표인 loss와 accuracy 값이 저장되어 있다. 즉, History 객체를 이용하여, Epoch 값에 따른 Loss 값의 변화를 그래프로 그려볼 수 있다는 것이다. 우리는 이러한 그래프를 Loss Curve(손실 곡선)이라 부른다.

※ Accuracy가 아닌 Loss 값으로 그래프를 그리는 이유
모델의 성능을 평가할 때에 편의상 Accuracy 지표를 사용하긴 하지만, 사실 인공 신경망 모델이 최적화하는 대상은 손실 함수이다. 따라서, 모델이 잘 훈련되었는지 판단하는 지표는 Accuracy가 아닌, Loss가 되어야 한다. 실제로 Loss의 감소가 Accuracy의 향상으로 이어지지 않는 경우도 간혹 존재한다.

① 모델 훈련에 사용할 데이터 Set을 준비한다.

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
train_scaled, val_scaled, train_target, val_target = train_test_split(train_scaled, train_target, test_size=0.2, random_state=42)

② 이번에는 모델을 생성하는 별도의 메서드를 정의해보도록 하겠다.

  • layers 매개변수에 추가할 층을 지정하면, 은닉층과 출력층 사이에 새로운 층을 추가할 수 있도록 만들어주었다..
def create_model(layers=None):
  model = keras.Sequential()
  model.add(keras.layers.Flatten(input_shape=(28, 28)))
  model.add(keras.layers.Dense(100, activation='relu'))

  if layers:
    model.add(layers)

  model.add(keras.layers.Dense(10, activation='softmax'))

  return model

create_model 메서드를 통해 모델을 생성한다.

model = create_model()

④ 모델을 훈련시킨다. 이 때, fit() 메서드의 반환 값을 result에 저장한다.

  • verbose 매개변수는 훈련 과정 출력을 조절한다.
    • 0으로 설정: 훈련 과정을 출력하지 않는다.
    • 1로 설정: Epoch마다 진행 막대, 손실, 정확도 등의 지표를 출력한다.
    • 2로 설정: 진행 막대는 출력하지 않고 손실, 정확도 등의 지표는 출력한다.
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
result = model.fit(train_scaled, train_target, epochs=5, verbose=0)

⑤ result에는 각 지표를 key로 갖는 History 딕셔너리가 들어있다.

print(result.history.keys()) # dict_keys(['loss', 'accuracy']) 출력

result.history['loss']에는, 각 Epoch마다 계산된 손실 값을 순서대로 나열한 리스트가 저장되어 있다.

plt.plot()에 1차원 배열을 전달하면, 인덱스를 x 값으로, 원소를 y 값으로 갖는 그래프가 그려진다.

import matplotlib.pyplot as plt

plt.plot(result.history['loss'])
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.show()

⑧ 물론, 필요에 따라 Accuracy Curve를 그릴 수도 있다.

  • Epoch의 횟수가 증가할수록, loss는 감소하고 accuracy는 향상된다.
plt.plot(result.history['accuracy'])
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.show()

⑨ Epoch 횟수가 너무 많아지면 과대 적합이 발생하므로, 이를 방지하기 위해선 검증 Set에서의 그래프도 그려보아야 한다.

  • 언제 과대적합이 발생하는지 알아보기 위해 Epoch 횟수를 20으로 늘려보자.
  • fit() 메서드의 validation_data 매개변수를 사용하여 검증 Set을 전달할 수 있다.
model = create_model()
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
result = model.fit(train_scaled, train_target, epochs=20, verbose=0, validation_data=(val_scaled, val_target))

⑩ result의 History 객체에도 검증 Set에서의 loss와 accuracy가 추가되었다.

print(result.history.keys())

⑪ 훈련 Set과 검증 Set에서의 손실 곡선을 그려보자.

  • 5번째 Epoch 이후부터 과대 적합의 양상이 나타난다.
plt.plot(result.history['loss'])
plt.plot(result.history['val_loss'])
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend(['Train', 'Validataion'])
plt.show()

⑫ Optimizer를 변경하는 것으로, 과대 적합을 방지할 수도 있다. RMSprop 대신 사용할 Optimizer로 자주 사용되는 것은 Adam이다.

  • 10번째 Epoch까지도 검증 Set에서의 손실이 감소하는 것으로 보아, 이 데이터 Set에는 Adam Optimizer가 더 잘 맞는다고 판단할 수 있다.
model = create_model()
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')
...

2. Dropout

Dropout은 Neural Network에서 사용되는 Regularization으로, 일부 뉴런의 출력을 0으로 조작하여 과대 적합을 방지한다.

위 그림은 은닉층의 두번째 뉴런이 Dropout 되어 h2의 입력과 출력이 없어진 상태를 나타내고 있다. 이 때, Dropout 되는 뉴런은 랜덤하게 선택되며, 선택할 뉴런의 개수는 하이퍼파라미터가 된다. Dropout을 통해 과대 적합을 방지할 수 있는 원리는 아래와 같다.

  • 특정 뉴런에 과하게 의존하는 일을 방지하므로, 보다 안정적인 예측을 수행할 수 있게 된다.
  • 각 학습 단계에서 서로 다른 뉴런들이 활성화되기 때문에, 보다 다양한 관점에서 데이터를 학습하게 되는데, 이는 Ensemble Learning과 비슷한 효과를 가져온다.

Dropout 층을 만들 때에는, keras.layers의 Dropout 클래스를 사용할 수 있다. 여기서는 30% 정도의 뉴런을 Dropout 해보기로 하자.

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

Dropout Layer는 Flatten Layer와 마찬가지로, 학습에 사용되는 층이 아니므로 훈련 파라미터가 없으며, 신경망 깊이에도 가산되지 않는다. 참고로, Dropout은 모델을 훈련할 때에만 사용되어야 하고, 실제 예측이나 성능 평가에는 적용되어선 안 된다. 하지만 그렇다고해서, Dropout Layer를 수동으로 제거해야 할 필요는 없다. 그 이유는 예측 및 성능 평가를 수행할 때, Keras에서 자동으로 Dropout을 해제하기 때문이다. 이제 Dropout을 적용하여 훈련된 모델의 Loss Curve를 그려보자.

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

plt.plot(result.history['loss'])
plt.plot(result.history['val_loss'])
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend(['Train', 'Validataion'])
plt.show()

10번째 Epoch까지 검증 손실이 감소한다는 점은 비슷하지만, Epoch 횟수가 더 많아져도 검증 손실이 크게 상승하지 않게 되었다. 이러한 점을 통해, 과대 적합을 비교적 잘 억제하고 있음을 확인할 수 있다.

3. 모델 저장 및 불러오기

이번에는 훈련된 모델을 저장하고 불러오는 방법에 대해 알아보기로 하자. 먼저 저장할 모델을 만들기 위해, Epoch 횟수를 10으로 조정하여 모델을 다시 훈련하도록 하겠다.

model = create_model(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
result = model.fit(train_scaled, train_target, epochs=10, verbose=0, validation_data=(val_scaled, val_target))

1) 사전 지식

훈련된 모델을 저장하는 방법은 크게 두 가지로 구분된다.

  • save_weigths/load_weights: 모델의 가중치만 저장
  • save/load_model: 모델의 구조, 가중치, Optimizer를 포함한 전체 모델 저장

또한, 파일을 저장할 때에는 아래와 같은 확장자를 사용할 수 있다.

① TensorFlow Checkpoints(.ckpt)

  • 훈련의 진행 상태(가중치, Optimizer)를 저장
  • 훈련이 중단된 부분부터 다시 시작하기 위한 목적으로 사용된다.
  • save_weigths() 메서드에서 사용하는 기본 포맷이다.

② HDF5(.h5)

  • Keras 모델을 저장하고 불러올 때 주로 사용된다.
  • 계층적 데이터 포맷을 이용해 대규모 데이터를 효율적으로 관리한다.

③ TensorFlow SavedModel(saved_model.pb)

  • 전체 모델을 저장하기 위한 포맷으로, 모델을 배포할 때 사용한다.
  • save() 메서드에서 사용하는 기본 포맷이다.

2) 모델 저장 및 불러오기

save_weigthssave 메서드를 이용하여 모델을 저장한다.

  • Keras 모델이므로, HDF5 포맷을 사용하기로 한다.
  • Keras가 업데이트되면서, .h5 확장자 대신 .weights.h5 확장자를 사용하도록 변경되었다.
model.save_weights('model.weights.h5')
model.save('model-whole.h5')

② 두 파일이 잘 저장되었는지 확인해보자.

!ls -al *.h5

③ model-weights.h5의 가중치를 불러오려면, load_weights()를 사용하면 된다.

  • 이 때, load_weights()의 대상이 되는 모델은 save_weights()를 사용했던 모델과 정확히 같은 구조를 가져야 한다.
model = create_model(keras.layers.Dropout(0.3))
model.load_weights('model.weights.h5')

evaluate() 메서드를 사용하기 위해선 먼저 모델을 compile() 해야 한다. 가중치만 불러올 경우 모델이 컴파일 되지 않은 상태이므로, 모델을 컴파일하거나 아래와 같이 직접 성능을 구해야 한다.

  • Keras의 predict() 메서드는 각 Sample마다 모든 클래스에 대한 확률을 반환한다.
  • 따라서, 10개의 확률 중 가장 높은 확률의 클래스를 선택한 후, 이를 타깃 값과 비교하여 성능을 평가해야 한다.
  • axis=-1은 검증 Set의 마지막 차원을 의미하는 것으로, axis=1과 같다.
import numpy as np

val_labels = np.argmax(model.predict(val_scaled), axis=-1)
print(np.mean(val_labels == val_target))

⑤ 모델 전체를 저장할 경우, evaluate() 메서드를 바로 사용할 수 있다.

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

4. Callback

Keras의 Callback은 모델 훈련 과정에서 특정 이벤트가 발생할 때 사용자 정의 행동을 수행할 수 있게 해주는 기능이다. Callback의 종류에는 여러 가지가 있지만, 여기서는 ModelCheckpoint와 EarlyStopping(조기 종료)에 대해 다뤄보기로 한다.

1) ModelCheckpoint

ModelCheckpoint는 기본적으로 Epoch마다 모델을 저장하기 때문에, 최상의 성능을 기록한 모델을 찾아내는 데에 매우 유용하다. 만약 최상의 모델만 저장하고 싶다면, save_best_only 매개변수를 True로 지정하면 된다. 참고로, ModelCheckpoint Callback은 .keras 확장자를 사용한다.

ModelCheckpoint Callback을 활용하면, 20번의 Epoch 중 최적의 Epoch로 훈련된 모델을 찾는 과정이 매우 간편해진다.

model = create_model(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-model.keras', save_best_only=True)

model.fit(train_scaled, train_target, epochs=20, verbose=0, validation_data=(val_scaled, val_target), callbacks=[checkpoint_cb])

이렇게 해서 저장된 모델은 load_model로 불러올 수 있다.

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

이로써, 검증 손실이 가장 낮은 모델을 손쉽게 얻을 수 있게 되었다. 하지만 아직 문제가 남아있는데, 그것은 조기 종료 없이 20번의 Epoch를 반복 수행한다는 것이다. 사실 검증 손실이 상승하는 시점부터는 굳이 추가적인 Epoch를 반복 수행할 필요가 없다. 그러므로, 이 시점부터는 EarlyStopping Callback을 이용해 조기 종료를 시켜주는 것이 좋다.

2) EarlyStopping

EarlyStopping Callback에 자주 사용되는 매개변수에는 patience와 restore_best_weights가 있다. 먼저 patience는 몇번 연속으로 검증 손실이 상승해야 훈련을 중단할 것인지를 결정한다. 이 때, restore_best_weights를 True로 지정하여 가장 낮은 검증 손실을 기록한 모델 가중치로 되돌아올 수 있다.

model = create_model(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-model.keras', save_best_only=True)

early_stopping_cb = keras.callbacks.EarlyStopping(patience=2, restore_best_weights=True)

model.fit(train_scaled, train_target, epochs=20, verbose=0, validation_data=(val_scaled, val_target), callbacks=[checkpoint_cb, early_stopping_cb])

EarlyStopping 객체에 stopped_epoch 속성을 이용하면, 몇번째 Epoch에서 훈련이 중단되었는지 확인할 수 있다.

print(early_stopping_cb.stopped_epoch) # 14 출력

Epoch 횟수는 0부터 시작하므로, 14는 사실 15번째 Epoch에서 훈련이 중단되었다는 의미이다. patience 값이 2이므로, 최적의 Epoch 횟수는 13번(12)이 되는 것이다. 정말 그러한지 검증 손실 그래프를 그려보기로 하자.

result = model.fit(train_scaled, train_target, epochs=20, verbose=0, validation_data=(val_scaled, val_target), callbacks=[checkpoint_cb, early_stopping_cb])

plt.plot(result.history['loss'])
plt.plot(result.history['val_loss'])
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend(['Train', 'Validataion'])
plt.show()

예상대로, 13번째(12) Epoch에서 검증 손실이 최소가 되었고, 15번째(14) Epoch에서 훈련이 중단되었다. 이처럼 EarlyStopping Callback을 활용하면, 넓은 범위의 Epoch 횟수를 효율적으로 고려할 수 있게 된다.

profile
LG전자 Connected Service 1 Unit 연구원 변현섭입니다.

0개의 댓글