728x90
반응형
과대적합(overfitting)
- 모델이 훈련 세트에서는 좋은 성능을 내지만 검증 세트에서는 낮은 성능을 내는 경우
- 예) 분류 문제에서 훈련 세트의 정확도가 99%이고 검증 세트의 정확도가 80% 수준이라면 과대적합을 의심할 수 있다.
과소적합(underfitthing)
- 훈련 세트와 검증 세트의 성능에는 차이가 크지 않지만 모두 낮은 성능을 내는 경우
훈련 세트의 과대적합, 과소적합 분석
- 학습 곡선(learning curve)
첫 번째 학습 곡선
- 과대적합의 전형적인 모습
- 분산이 크다(high variance)라고도 한다.
- 과대적합 주요 원인 중 하나는 훈련 세트에 충분히 다양한 패턴의 샘플이 포함되지 않은 경우
- 훈련 세트에 다양한 패턴의 샘플이 없으니 검증 세트에 제대로 적응하지 못한 것이다.
- 이런 경우 더 많은 훈련 샘플 모아 검증 세트 성능 향상 가능 - 현실적인 한계로 훈련 샘플 더 많이 모을 수 없는 경우도 있다.
- 이 경우 모델 훈련 세트 집착하지 않도록 가중치 제한 가능 - 모델의 복잡도를 낮춘다.
두 번째 학습 곡선
- 과소적합의 전형적인 모습
- 훈련 세트와 검증 세트에서 측정한 성능 간격 점점 가까워지지만 성능 자체가 낮다.
- 편향이 크다(high bias)
- 훈련 데이터에 있는 패턴 모두 잡아내지 못하는 현상
- 해결 방법 - 복잡도가 더 높은 모델 사용하거나 가중치 규제 완화
세 번째 학습 곡선
- 과대적합과 과소적합 절충점
에포크와 손실 함수의 그래프로 과대적합과 과소적합 분석
왼쪽 그래프
- 검증 세트의 손실과 훈련 세트의 손실 나타냄
- 훈련 세트의 손실은 에포크가 진행될수록 감소, 검증 세트의 손실은 에포크의 횟수가 최적점을 지나면 오히려 상승
- 최적점 이후에도 훈련 세트로 모델을 학습시키면 모델이 훈련 세트의 샘플에 더 밀착해 학습하기 때문 - 과대적합
- 최적점 이전 훈련 세트와 검증 세트의 손실이 비슷한 간격 유지하며 함께 줄어든다. 이 영역에서 중지시 - 과소적합
오른쪽 그래프
- 세로 축 손실 대신 정확도
- 왼쪽 그래프 뒤집힘
에포크 대신 모델 복잡도 사용해 과대적합과 과소적합 분석
- 모델 복잡도란 모델이 가진 학습 가능한 가중치 개수
- 층의 개수나 유닛의 개수가 많아지면 복잡도가 높은 모델이 만들어짐
- 너무 많아지면 과대적합
적절한 편향-분산 트레이드오프
- 과소적합 모델 - 편향되었다.
- 과대적합 모델 - 분산이 크다.
편향 - 분산 트레이드오프(bias-variance tradeoff)
- 과소적합된 모델(편향)과 과대적합된 모델(분산)사이의 관계를 편향-분산 트레이드오프
- '하나를 얻기 위해서 다른 하나를 희생해야 하기 때문'
- 편향을 줄이면(훈련 세트의 성능 높임) 분산이 커짐(검증 세트와 성능 차이가 커짐)
- 반대 분산을 줄이면(검증 세트와 성능 차이 줄임) 편향이 커지는(훈련 세트의 성능이 낮아지는)것을 말한다.
1. 검증 손실을 기록하기 위한 변수 추가
# # 1. 검증 손실을 기록하기 위한 변수 추가
# self.val_losses 인스턴스 추가 - 검증 세트에 대한 손실 기록
def __init__(self, learning_rate=0.1):
self.w = None
self.b = None
self.val_losses = []
self.w_history = []
self.lr = learning_rate
2. fit() 메서드 검증 세트 전달받을 수 있도록 x_val, y_val
# 2. fit() 메서드 검증 세트 전달받을 수 있도록 x_val,y_val 매개변수 추가
def fit(self, x, y, epochs=100, x_val=None, y_val=None):
self.w = np.ones(x.shape[1]) # 가중치를 초기화합니다.
self.b = 0 # 절편을 초기화합니다.
self.w_history.append(self.w.copy()) # 가중치를 기록합니다.
np.random.seed(42) # 랜덤 시드를 지정합니다.
for i in range(epochs): # epochs만큼 반복합니다.
loss = 0
# 인덱스를 섞습니다
indexes = np.random.permutation(np.arange(len(x)))
for i in indexes: # 모든 샘플에 대해 반복합니다
z = self.forpass(x[i]) # 정방향 계산
a = self.activation(z) # 활성화 함수 적용
err = -(y[i] - a) # 오차 계산
w_grad, b_grad = self.backprop(x[i], err) # 역방향 계산
# 그래디언트에서 페널티 항의 미분 값을 더합니다
w_grad += self.l1 * np.sign(self.w) + self.l2 * self.w
self.w -= self.lr * w_grad # 가중치 업데이트
self.b -= b_grad # 절편 업데이트
# 가중치를 기록합니다.
self.w_history.append(self.w.copy())
# 안전한 로그 계산을 위해 클리핑한 후 손실을 누적합니다
a = np.clip(a, 1e-10, 1-1e-10)
loss += -(y[i]*np.log(a)+(1-y[i])*np.log(1-a))
# 에포크마다 평균 손실을 저장합니다
self.losses.append(loss/len(y) + self.reg_loss())
# 검증 세트에 대한 손실을 계산합니다
self.update_val_loss(x_val, y_val)
3. 검증 손실 계산하기
# 3. 검증 손실 계산하기
# fit() 멧더ㅡ에서 훈련 세트의 손실을 계산하는 방식과 동일
# 검증 세트 샘플 정방향 계산 후 활성화 함수 통과시켜 출력값 계산
# 이 값 로지스틱 손실 함수 계산해 val_losses 리스트 추가
# 에포크마다 update_val_loss 호출
def update_val_loss(self, x_val, y_val):
if x_val is None:
return
val_loss = 0
for i in range(len(x_val)):
z = self.forpass(x_val[i]) # 정방향 계산
a = self.activation(z) # 활성화 함수 적용
a = np.clip(a, 1e-10, 1-1e-10)
val_loss += -(y_val[i]*np.log(a)+(1-y_val[i])*np.log(1-a))
self.val_losses.append(val_loss/len(y_val) + self.reg_loss())
4. 모델 훈련
# 4. 모델 훈련하기
layer3 = SingleLayer()
layer3.fit(x_train_scaled, y_train, x_val=x_val_scaled, y_val=y_val)
5. 손실값으로 그래프 그려 에포크 횟수 지정
# 5. 손실값으로 그래프 그려 에포크 횟수 지정
# fit() 메서드 수정해 에포크마다 훈련 세트와 검증 세트의 손실값을 인스턴스 self.val_losses에 저장
plt.ylim(0, 0.3)
plt.plot(layer3.losses)
plt.plot(layer3.val_losses)
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train_loss','val_loss'])
plt.show()
# 검증 손실이 20번째 에포크 이후 훈련 세트보다 높아지는 것
6. 훈련 조기 종료
# 6. 훈련 조기 종료 (early stopping)
# 20번째 까지 훈련 다음 검증 성능 확인
layer4 = SingleLayer()
layer4.fit(x_train_scaled, y_train, epochs= 20)
layer4.score(x_val_scaled, y_val) # 97.8%
# 원래 성능 0.976
728x90
반응형
최근댓글