본문 바로가기
Programming/Python

[DL] over(under)fitting 최적, Dropout, Callback

기본 데이터 정리 및 model 함수 정의 후 호출

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
    
model = model_fn()

model.summary()

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten (Flatten)           (None, 784)               0         
                                                                 
 dense (Dense)               (None, 100)               78500     
                                                                 
 dense_1 (Dense)             (None, 10)                1010      
                                                                 
=================================================================
Total params: 79510 (310.59 KB)
Trainable params: 79510 (310.59 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________

 

지난 모형과 동일한 값이 호출되었다.

 

 

verbose: default = 1 (모든 진행과정 표시)      = 0 (훈련과정 출력 생략)      = 2 (진행막대 생략한 출력)

model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')

history = model.fit(train_scaled, train_target, epochs=5, verbose=0)  #fit을 담는다.
print(history.history.keys())

dict_keys(['loss', 'accuracy'])

 

 

이걸 시각화해보자

import matplotlib.pyplot as plt

plt.plot(history.history['loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.savefig('7_3-01', dpi=300)
plt.show()

loss 시각화

 

plt.plot(history.history['accuracy'])
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.savefig('7_3-02', dpi=300)
plt.show()

accuracy 시각화

 

epoch가 늘어날수록 loss는 줄고 정확도는 늘어난다. -> epoch를 늘리면 다다익선일까?

 

 

이번엔 epoch = 20으로 설정하였다

model = model_fn()
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
history = model.fit(train_scaled, train_target, epochs=20, verbose=0) # verbose = 0: 출력 생략!

plt.plot(history.history['loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.savefig('7_3-03', dpi=300)
plt.show()

loss가 줄어드긴 하는데.. 과적합 문제는 정말 없는걸까?

 

(!) Deep learning model 최적화 대상은 lose function이다. accuracy가 아니다! (정확도를 따로 확인 X)

 

 

DL에서는 두 훈련과 검증세트 각각의 손실을 사용하여 over(under)fitting 문제를 파악한다.

validation_data 옵션에 검증에 사용할 train set과  검증 set을 튜플로 입력한다.

RMSprop(default) 최적화 기준으로 실행해보면

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))
                    
                    
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.savefig('7_3-04', dpi=300)
plt.show()

val의 global minimum지점은 epoch = 5인 때이다. 

 

다른 최적화기법을 사용하고싶다면 가장 무난한 방법은 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.savefig('7_3-05', dpi=300)
plt.show()

 

epoch가 7.5인 지점에서 loss가 최소화된다. 해당 loss값과 위 RMSprop경우의 loss값과 비교해보면 Adam의 loss가 적다.

이는 Adam이 이 데이터 셋에 적절하게 잘 맞는 방법이라는 의미다.

 

 

 

 

 

 

 

 

Dropout: 출력이 0인 뉴런을 임의로 집어넣어 ovefitting 방지하는 방법 

why?) 임의 0 뉴런 삽입 -> 특정 뉴런 의존 불가 -> 모든 input에 더욱 더 세밀한 점검이 적용됨 -> 안정적 예측

정의한 함수 model_fn()에 keras.layers.Dropout()을 적용한다.

model = model_fn(keras.layers.Dropout(0.3))  #전체 데이터의 30% dropout

model.summary()

Model: "sequential_4"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten_4 (Flatten)         (None, 784)               0         
                                                                 
 dense_8 (Dense)             (None, 100)               78500     
                                                                 
 dropout (Dropout)           (None, 100)               0                    <- 훈련되는 모델 파라미터 無
                                                                 
 dense_9 (Dense)             (None, 10)                1010      
                                                                 
=================================================================
Total params: 79510 (310.59 KB)
Trainable params: 79510 (310.59 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________

 

-> dropout은 일부 뉴런 출력을 0으로 만들지만 전체 출력 배열 크기에 영향 無

 

 

원칙적으로 훈련 후 평가나 예측시에 dropout을 제외하고 수행해야하지만, Keras는 자동으로 이를 제외하고 수행

그러므로 별다른 추가작업 없이 손실양상을 다시 살펴본다.

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.savefig('7_3-06', dpi=300)
plt.show()

전반적으로 이전보다 크게 loss가 줄었다.

해당그래프에서는 약 12 epoch가 최소 손실이다.

따라서, epoch 수행시 overfitting되므로 그래프 결과에 따른 값(12)만큼 epoch 수행으로 다시 바꿔야한다.

 

 

 

 

model save and load

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))
                    
                    
model.save_weights('model-weights.h5')   # <- 가중치만 저장 (파라미터, 절편 등)
model = model_fn(keras.layers.Dropout(0.3))   # <- 불러오기를 위한 model 객체 설정

model.load_weights('model-weights.h5')  # <- 불러오기


model.save('model-whole.h5') # <- 모델 구조 포함 전체 저장

 

(잠깐!) .h5?

  • 학습한 model 을 파일로 저장할 때, 보통 .h5 확장자로 저장.
    • .h5 확장자는, HDF5 포맷으로 데이터 저장 의미.
  • .h5 has model and 가중치(weight) info.

 

h5파일이 잘 저장되었는지 다음 외부 명령을 통해 알아보자

!ls -al *.h5

#!: 외부명령 실행 때 사용.    Is: dir 내 파일 밑 하위 dir 목록나열
#-a: 숨겨진 파일 및 dr 전부 표시.   
# -l: 자세한 목록형식 표시,파일 권한 소유자 그룹크기 등
# *.h5: 확장자가 .h5인 파일만 나열.  *는 일치하는 모든 파일 선택

 

'ls' is not recognized as an internal or external command,
operable program or batch file.

 

 

만약 위와 같은 오류가 출력되었다면 이 코드로 대체한다

!dir *.h5  # 인식이 안되는 이유는 unix/Linux명령으로 간주되었기 때문. 이걸로 쓰자

 Volume in drive C has no label.
 Volume Serial Number is D001-AFD7

 Directory of C:\Users\사용자이름\Desktop\2023_study\Python\Python_ML

09/18/2023  01:30 PM         4,046,672 best-cnn-model.h5
09/19/2023  09:19 AM           981,176 best-model.h5
09/19/2023  09:18 AM           333,320 model-weights.h5
09/19/2023  09:18 AM           981,176 model-whole.h5
               4 File(s)      6,342,344 bytes
               0 Dir(s)  261,669,965,824 bytes free

 

 

아주 인식이 잘되고 리스트도 잘 보인다

 

 

 

 

 

이번엔 훈련되지 않은 새 model을 설정하고 파라미터를 weights h5 파일에서 로드한다.

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

model.load_weights('model-weights.h5')

 

만약 load에 오류가 있다면 경로에 띄어쓰기 및 한글이 있기 때문이다.

 

 

 

 

np.argmax()로 최댓값이 있는 자리의 index를 = label로 지정.  axis = -1 <- 각 행의 최댓값을 인덱스. (0이면 열의 최댓값)

import numpy as np

val_labels = np.argmax(model.predict(val_scaled), axis=-1)
print(np.mean(val_labels == val_target))  # 예측=실제인지 bool data 평균 구하기(True=1, False=0)

 

375/375 [==============================] - 0s 770us/step
0.8830833333333333   <- 정답률 약 88퍼

 

 

 

 

load_model()로 통째로 로드하여 평가해보자

model = keras.models.load_model('model-whole.h5')
# 텐서플로 2.3에서는 버그(https://github.com/tensorflow/tensorflow/issues/42890) 때문에 
# compile() 메서드를 호출해야 합니다.
# model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')

model.evaluate(val_scaled, val_target)

375/375 [==============================] - 0s 853us/step - loss: 0.3257 - accuracy: 0.8831
[0.32570087909698486, 0.8830833435058594]

 

위와 동일한 결과 출력되었다.

 

 

 

 

Callback(콜백)

 

근데 지금까지 그래프로 over(under)fitting 여부를 확인하고 재훈련했는데 이 2번 과정 없이 한 번에 하는 방법은 없을까?

이게 바로 Callback(콜백)개념이다. callbacks.Modelcheckpoint(파일이름, 조건)을 객체화한다.

이떄 save_best_only= True 입력시, 최소 손실 최대 정확도 지점만 저장하는 옵션으로 인식된다.

그리고 fit 훈련시, callbacks 옵션을 리스트로 설정한다. 그러면 fit 20번 epoch과정에서 최고 값의 h5가 저장된다.

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)

model.fit(train_scaled, train_target, epochs=20, verbose=0, 
          validation_data=(val_scaled, val_target),
          callbacks=[checkpoint_cb])  # 이 부분에 callbacks 설정이 있음을 유의한다! (리스트!)
          
# 최고값이 저장된 h5를 load하고 평가한다.          
          
model = keras.models.load_model('best-model.h5')
# 텐서플로 2.3에서는 버그(https://github.com/tensorflow/tensorflow/issues/42890) 때문에 compile() 메서드를 호출해야 합니다.
# model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')

model.evaluate(val_scaled, val_target)

375/375 [==============================] - 0s 793us/step - loss: 0.3127 - accuracy: 0.8889
[0.31265023350715637, 0.8889166712760925]

 

이렇게 20번 epoch과정에서 최고의 결과를 뽑을 수 있다. 그런데 20번 과정중에서 중간에 이미 목표를 달성했다면 중단하는게 더 효율적이지 않았을까? 이 생각으로 나온것이 바로 Early Stopping(조기종료)이다.

 

조기종료는 값이 향상되지 않을때 종료하는 콜백 기법이다.

 

그럼 이 두 콜백기법을 같이 적용해보자. 기존 callbacks.ModelCheckpoint에 더불어서 callbacks.EarlyStopping을 추가한다. 여기에 지정된 patience = 2는 2번 연속 점수가 향상되지 않으면 중단함을 의미한다. 또한 retore_best_weights=True로 최상의 model parameter 형태를 유지한다.

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]) #callbacks에 리스트 2개가 추가!
                    
print(early_stopping_cb.stopped_epoch)  # 몇 번째의 epoch에서 멈추었는지 출력한다.

13     <- epoch는 0부터시작하므로 14번째에서 중단되었다는 의미이다. patience = 2이므로 최고의 epoch 지점은 12(13번째)이다.

 

 

 

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.savefig('7_3-07', dpi=300)
plt.show()

model.evaluate(val_scaled, val_target)

model.evaluate(val_scaled, val_target)
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 0s 815us/step - loss: 0.3272 - accuracy: 0.8811
[0.3272111713886261, 0.8810833096504211]

 

 

이런식으로 그래프와 결과에서 확인할 수 있다.

728x90
반응형