history = model.fit( x_train, y_train,
epochs= 100,
batch_size=100,
validation_data = (x_test, y_test) )
# 훈련 중간중간 마다 테스트 데이터에 대한 정확도를 같이 확인해봅니다.
# 그래서 정확도가 높아지면서 오버피팅이 발생하는지 추세를 확인합니다.
# 예시로 accuracy 리스트 값을 사용
train_acc_list = history.history['accuracy'] # 훈련 데이터의 정확도 리스트
test_acc_list = history.history['val_accuracy'] # 테스트 데이터의 정확도 리스트
# 에포크 수에 따른 x축 데이터 생성
x = np.arange(len(train_acc_list))
# 시각화
plt.figure(figsize=(8, 6)) # 그래프 크기 설정
plt.plot(x, train_acc_list, label='Train Accuracy') # 훈련 데이터 정확도 라인
plt.plot(x, test_acc_list, label='Test Accuracy', linestyle='--') # 테스트 데이터 정확도 라인
# 그래프의 축, 범위 및 레이블 설정
plt.ylim(0.8, 1.0) # y축 범위를 0.8에서 1.0까지로 설정
plt.xlabel('Epochs', fontsize=12) # x축 레이블
plt.ylabel('Accuracy', fontsize=12) # y축 레이블
# 범례 추가 및 위치 설정
plt.legend(loc='lower right', fontsize=12)
# 타이틀 추가 (선택 사항)
plt.title('Train vs Test Accuracy', fontsize=14)
# 그래프 출력
plt.show()
가중치 행렬의 숫자가 W1 만해도 76800개가 만들어지고 W2 는 1000개의 숫자가 처음에 랜덤으로 생성이 됩니다. 학습이 잘되게 하려면 이 숫자들이 정규분포 형태를 보여야합니다.
독립변수들의 데이터가 정규분포 형태이고 종속변수도 정규분포 형태면 학습이 아주 잘됩니다. 그런데 정규분포 형태가 아니면 학습이 원만하지 않을거다라고 예상을 하고 있어야합니다.
그런데 가중치도 마찬가지고 처음부터 정규분포 형태로 구성되어야 학습이 잘됩니다. 학습 시킬 데이터는 정규분포가 아니어도 어쩔수 없지만 가중치는 정규분포로 구성할 수 있습니다.
■ 이론설명1. 생성되는 가중치 숫자들의 표준편차가 너무 큰 경우
그림 6-10
학습이 잘 안되는 가중치 입니다. 랜덤으로 생성된 가중치 숫자들이 앞과 뒤에 다 몰려있습니다. 이런 현상을 다른 예로 들면 어느반 학생들의 시험점수가 있는데 아주 잘하는 학생들과 아주 못하는 학생들로 구성된 경우입니다. 시험문제가 너무 어려우면 이런 현상이 발생합니다. 예: W1 = 100 * np.random.randn( 784 , 100 )
■ 이론설명2. 생성되는 가중치 숫자들의 표준편차가 너무 작은 경우
그림 6-11
가중치의 숫자들이 전부 평균값에 몰려 있습니다. 이렇게 되면 학습이 잘 안됩니다. 시험문제가 너무 쉬우면 학생들의 점수가 전부 평균에 가까워집니다.
예: W1 = 0.00001 * np.random.randn( 784, 100 )
■ 이론설명3. 신경망 학습이 잘되는 가중치 구성의 경우
그림6-13
가중치와 위와 같이 정규분포 형태를 보여야 신경망 학습이 잘됩니다. 학습 시작하기전에 처음부터 위와 같이 만들어줘야합니다.
예: W1 = 0.01 * np.random.randn(784, 100)
※ 텐써 플로우에서는 가중치 초기값이 어떻게 구성될까요 ?
기본값이 이미 정규 분포로 잘 구성되어서 크게 신경안써도 되지만 더 정교하게 초기값을 구성하고 싶다면 다음의 옵션을 쓰면 됩니다.
가중치 초기값을 적당한 값으로 설정하면 정규 분포 형태를 보이는 값으로 처음에는 설정이 되는데 학습을 하다 보면 신경망이 깊어지다 보니까 각 층의 활성화된 값(입력값x가중치) 이 정규 분포형태를 읽어버리는 현상이 발생합니다. 1박 2일 게임처럼 신경망 층 뒤로 갈수록 엉뚱한 해석을 하게 되는 현상이 발생합니다. 이를 해결하는 방법이 "배치 정규화" 입니다.
훈련 데이터의 정확도: 0.99 이고 테스트 데이터의 정확도가 0.97 로 오버피팅이 발생하고 있습니다.
문제1. 위의 코드에 다음층에 dropout 을 더 추가하면 오버피팅이 방지되는지 확인하시오 !
# 1. 필요한 패키지 가져오는 코드
import tensorflow as tf # 텐써 플로우 2.0
from tensorflow.keras.datasets.mnist import load_data # 텐써플로우에 내장되어있는 mnist 데이터를 가져온다.
from tensorflow.keras.models import Sequential # 모델을 구성하기 위한 모듈
from tensorflow.keras.layers import Dense, Flatten, BatchNormalization, Dropout # 완전 연결계층을 구성하기 위한 모듈
from tensorflow.keras.utils import to_categorical # one encoding 하는 모듈
import numpy as np
tf.random.set_seed(777)
(x_train, y_train), (x_test, y_test) = load_data(path='mnist.npz') # mnist 데이터 로드
# 2. 정규화 진행
x_train = (x_train.reshape((60000, 28 * 28))) / 255
x_test = (x_test.reshape((10000, 28 * 28))) / 255
# 3. 정답 데이터를 준비한다.
# 하나의 숫자를 one hot encoding 한다. (예: 4 ---> 0 0 0 0 1 0 0 0 0 0 )
y_train = to_categorical(y_train) # 훈련 데이터의 라벨(정답)을 원핫 인코딩
y_test = to_categorical(y_test) # 테스트 데이터의 라벨(정답)을 원핫 인코딩
# 4. 모델을 구성합니다. 3층 신경망으로 구성
# (100, 784) ◎ ( 784, 100) = (100, 100) ◎ (100, 50 ) = (100, 50) ◎ (50, 10 ) = (100,10)
model = Sequential()
model.add(Flatten(input_shape=(784, ) ) ) # 입력층(0층)
model.add(BatchNormalization() )
model.add(Dropout(0.2) )
model.add(Dense( 100, activation='sigmoid' )) # 은닉층(1층)
model.add(BatchNormalization() )
model.add(Dropout(0.2) )
model.add(Dense( 50, activation ='sigmoid') ) # 은닉층 (2층)
model.add(Dense( 10, activation='softmax') ) # 출력층 (3층)
# 5. 모델을 설정합니다. ( 경사하강법, 오차함수를 정의해줍니다. )
model.compile(optimizer='RMSprop',
loss = 'categorical_crossentropy',
metrics=['acc']) # 학습과정에서 정확도를 보려고
#6. 모델을 훈련시킵니다.
history = model.fit(x_train, y_train,
epochs = 30, # 30에폭
batch_size = 100,
validation_data = (x_test, y_test) )
# 7.모델을 평가합니다. (오차, 정확도가 출력됩니다.)
model.evaluate(x_test, y_test)
# 위의 코드들 밑에 바로 구현
train_acc_list = history.history['acc']
test_acc_list = history.history['val_acc']
print( train_acc_list )
print( test_acc_list )
# 시각화 까지 하세요 ~
import numpy as np
import matplotlib.pyplot as plt
x = np.arange( len(train_acc_list) )
plt.plot( x, train_acc_list, label='train acc')
plt.plot( x, test_acc_list, label='test acc', linestyle='--')
plt.legend(loc='lower right')
plt.xlabel('epochs')
plt.ylabel('accuracy')
plt.show()
▣ 6.5 얼리 스탑 기능 ( early stopping 기능 )
뜻 ? 학습을 일찍 끝내겠다. 더 이상 개선의 여지가 보이지 않는다면 일찍 끝내겠다.
설명: 14 에폭에서 한번 만났고 21에폭에서 또 만났는데 가급적이면 정확도가 더 높은 두번째 21에폭에서 학습을 멈추게 하는게 바람직합니다.
■ 실습1. 얼리 스탑 기능을 추가한 신경망 전체코드를 수행하시오 !
# MNIST 데이터 불러오기 및 전처리
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.callbacks import EarlyStopping
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 차원 축소 및 정규화
x_train = x_train.reshape((x_train.shape[0], 784)).astype('float32') / 255
x_test = x_test.reshape((x_test.shape[0], 784)).astype('float32') / 255
# 레이블을 one-hot encoding으로 변환
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)
# 신경망 구성에 필요한 모듈 불러오기
import tensorflow as tf
tf.random.set_seed(777)
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten, BatchNormalization
from tensorflow.keras.optimizers import SGD
from tensorflow.keras.losses import categorical_crossentropy
# 모델 구성하기
model = Sequential()
model.add(Flatten(input_shape=(784,))) # 입력층
# 은닉층 1층
model.add(Dense(64, kernel_initializer='he_normal'))
model.add(BatchNormalization())
model.add(Dense(64, activation='relu'))
# 은닉층 2층
model.add(Dense(32, kernel_initializer='he_normal'))
model.add(BatchNormalization())
model.add(Dense(32, activation='relu'))
# 은닉층 3층
model.add(Dense(64, kernel_initializer='he_normal'))
model.add(BatchNormalization())
model.add(Dense(64, activation='relu'))
# 출력층
model.add(Dense(10, activation='softmax'))
# 모델 컴파일
model.compile(optimizer=SGD(),
loss=categorical_crossentropy,
metrics=['accuracy'])
# 얼리 스탑 기능 추가
early_stopping = EarlyStopping(monitor='val_loss',
patience=10,
restore_best_weights=True)
# 모델 훈련
history = model.fit(x_train, y_train,
epochs=100,
batch_size=100,
validation_data=(x_test, y_test),
callbacks=[early_stopping])
# 모델 평가
test_loss, test_acc = model.evaluate(x_test, y_test)
print(f"\nTest Loss: {test_loss:.4f}, Test Accuracy: {test_acc:.4f}")
# 학습 결과 시각화
import matplotlib.pyplot as plt
# 훈련 및 테스트 정확도 리스트
train_acc_list = history.history['accuracy']
test_acc_list = history.history['val_accuracy']
# 에포크 수에 따른 x축 데이터 생성
x = np.arange(len(train_acc_list))
# 시각화
plt.figure(figsize=(8, 6))
plt.plot(x, train_acc_list, label='Train Accuracy')
plt.plot(x, test_acc_list, label='Test Accuracy', linestyle='--')
# 그래프의 축, 범위 및 레이블 설정
plt.ylim(0.8, 1.0)
plt.xlabel('Epochs', fontsize=12)
plt.ylabel('Accuracy', fontsize=12)
# 범례 추가 및 위치 설정
plt.legend(loc='lower right', fontsize=12)
# 타이틀 추가
plt.title('Train vs Test Accuracy', fontsize=14)
# 그래프 출력
plt.show()
EarlyStopping 콜백에서 monitor='val_loss'로 설정했기 때문에 **검증 데이터(validation data)**에서의 손실(val_loss)을 모니터링합니다. 즉, 학습 중에 검증 데이터의 손실 값이 더 이상 감소하지 않으면, 그것을 "성능이 개선되지 않는 것"으로 간주합니다.
patience=10은검증 손실 값이 10번 연속으로 개선되지 않으면 학습을 중단한다는 의미입니다. 10번의 에포크 동안 검증 손실이 줄어들지 않으면 그때 학습을 멈추게 됩니다.
restore_best_weights=True로 설정했기 때문에, 학습이 중단되었을 때가장 성능이 좋았던(손실이 가장 낮았던) 시점의 가중치를 복원하게 됩니다. 이를 통해, 학습이 지나치게 진행되어 성능이 나빠지는 것을 방지할 수 있습니다.
따라서, 모델이 10번 연속으로 개선되지 않는다면 그 즉시 학습을 중단하고 최적의 모델 가중치로 복원됩니다.
■ 가중치 감소
"학습하는 과정에서 큰 가중치에 대해서는 그에 상응하는 큰 패널티를 부여하여 오버피팅을 억제하는 방법 "
고양이 사진 입력층 ---------------> 은닉층 -----------------------> □ ------- ---> 뾰족한 귀가 있어요 ----> 아주 큰 가중치로 갱신 □ -------------> 꼬리가 있어요 ----> 가중치 □ -------------> 집계발을 가지고 있어요 --> 가중치 □ -------------> 수염이 있어요 ---> 가중치 : : □ --------------> 장난스럽게 보여요 ----> 가중치
설명: 고양이 귀가 뾰족하다는것을 신경망이 알게되어서 고양이 귀에 대해서는 아주 큰 가중치를 부여하겠금 학습이 되었는데 그러다 보니 뾰족하지 않은 귀를 가진 고양이 사진이 입력되면 고양이가 아니라고 잘못 판단하는 상태가 된 신경망입니다.
이럴때는 '가중치 감소' 를 사용하여 아주 큰 가중치에 대해서는 더 큰 패널티를 부여합니다. 이걸 'L2 정규화' 라고 합니다. 텐써 플로우로는 다음과 같이 하면 됩니다.
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.callbacks import EarlyStopping, Callback
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten, BatchNormalization, Dropout
from tensorflow.keras.optimizers import SGD
from tensorflow.keras.losses import categorical_crossentropy
from tensorflow.keras.regularizers import l2
# 1. MNIST 필기체 데이터 불러오기
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 차원 축소하기
x_train = x_train.reshape((x_train.shape[0], 784)).astype('float32') / 255
x_test = x_test.reshape((x_test.shape[0], 784)).astype('float32') / 255
# 레이블을 one-hot encoding으로 변환하기
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)
# 2. 커스텀 콜백 정의 (훈련 데이터와 테스트 데이터 정확도 동일할 때 멈춤)
class CustomStopCallback(Callback):
def on_epoch_end(self, epoch, logs=None):
train_accuracy = logs.get('accuracy') # 훈련 데이터 정확도
val_accuracy = logs.get('val_accuracy') # 테스트 데이터 정확도
# 훈련과 테스트 정확도가 같아지면 멈춤
if train_accuracy == val_accuracy:
print(f"\nEpoch {epoch + 1}: Train accuracy and validation accuracy are the same. Stopping training.")
self.model.stop_training = True
# 3. 모델 구성하기
model = Sequential()
model.add(Flatten(input_shape=(784,))) # 입력층
# 은닉층(1층): 가중치 초기화 + 배치 정규화 + 활성화 함수
model.add(Dense(64, kernel_initializer='he_normal', kernel_regularizer=l2(0.01)))
model.add(BatchNormalization()) # 배치 정규화
model.add(Dropout(0.2))
model.add(Dense(64, activation='relu'))
# 은닉층(2층): 가중치 초기화 + 배치 정규화 + 활성화 함수
model.add(Dense(32, kernel_initializer='he_normal',kernel_regularizer=l2(0.01)))
model.add(BatchNormalization()) # 배치 정규화
#model.add(Dropout(0.1))
model.add(Dense(32, activation='relu'))
# 은닉층(3층): 가중치 초기화 + 배치 정규화 + 활성화 함수
model.add(Dense(64, kernel_initializer='he_normal',kernel_regularizer=l2(0.01)))
model.add(BatchNormalization()) # 배치 정규화
model.add(Dense(64, activation='relu'))
# 출력층(4층)
model.add(Dense(10, activation='softmax'))
# 모델 준비하기
model.compile(optimizer=SGD(),
loss=categorical_crossentropy, # 분류를 위한 오차함수
metrics=['accuracy']) # 분류 문제에 맞는 평가 지표 사용
# 4. 얼리 스탑 기능 추가 (가장 높은 검증 정확도를 기준으로)
early_stopping = EarlyStopping(monitor='val_accuracy', # 검증 데이터의 정확도를 기준으로 함
patience=10, # 성능 개선이 없을 때 20 에포크를 더 기다림
restore_best_weights=True) # 가장 성능이 좋았던 가중치 복원
# 5. 학습 시키기 (100 에포크로 설정하고 얼리 스탑 및 커스텀 콜백 추가)
history = model.fit(x_train, y_train,
epochs=200,
batch_size=100,
validation_data=(x_test, y_test),
callbacks=[early_stopping, CustomStopCallback()]) # 얼리 스탑 및 커스텀 콜백 추가
# 6. 모델 평가
# model.evaluate(x_test, y_test)
# 학습 결과 시각화
import matplotlib.pyplot as plt
# 예시로 accuracy 리스트 값을 사용
train_acc_list = history.history['accuracy'] # 훈련 데이터의 정확도 리스트
test_acc_list = history.history['val_accuracy'] # 테스트 데이터의 정확도 리스트
# 에포크 수에 따른 x축 데이터 생성
x = np.arange(len(train_acc_list))
# 시각화
plt.figure(figsize=(8, 6)) # 그래프 크기 설정
plt.plot(x, train_acc_list, label='Train Accuracy') # 훈련 데이터 정확도 라인
plt.plot(x, test_acc_list, label='Test Accuracy', linestyle='--') # 테스트 데이터 정확도 라인
# 그래프의 축, 범위 및 레이블 설정
plt.ylim(0.8, 1.0) # y축 범위를 0.8에서 1.0까지로 설정
plt.xlabel('Epochs', fontsize=12) # x축 레이블
plt.ylabel('Accuracy', fontsize=12) # y축 레이블
# 범례 추가 및 위치 설정
plt.legend(loc='lower right', fontsize=12)
# 타이틀 추가 (선택 사항)
plt.title('Train vs Test Accuracy', fontsize=14)
# 그래프 출력
plt.show()
테스트 데이터의 정확도가 훈련 데이터의 정확도 보다 처음부터 높게 나오고 있어서 오버피팅이 전혀 발생하고 있지 않습니다.
훈련 중에 테스트 데이터의 정확도는 0.9702 였는데 저장한 모델을 불러와서 확인한 테스트 데이터의 정확도는 0.7964 입니다. 더 정확도가 좋게 나왔습니다. 이유는 restore_best_weights=True 때문입니다. 학습중에 가장 좋았던 가중치로 모델을 save 시켰기 때문입니다.