오버피팅 억제법
신경망 학습을 하다보면 오버피팅이 일어날 수 있다. 오버피팅이란 신경망이 훈련 데이터에만 지나치게 적응되어 그 외의 데이터에는 제대로 대응하지 못하는 상태를 말한다.
오버피팅은 주로
- 매개변수가 많고 표현력이 높은 모델
- 훈련 데이터가 적은 경우
- 가중치 감소 가중치 매개변수의 값이 작아지도록 학습하는 방법이다. 오버피팅은 가중치 매개변수의 값이 클 때 발생하는 경우가 많기때문에, 학습 과정에서 큰 가중치에 대해서는 그에 상응하는 페널티를 적용해 오버피팅을 억제시키겠다는 것이다. 그럼 어떻게 작게 만들 수 있을까?
- Xavier초깃값 앞 계층의 노드가 n개 일 때 표준편차는 $\frac{1}{\sqrt{n}}$으로 설정. sigmoid함수나 tanh함수같은 좌우 대칭인, 중앙 부근이 선형인 함수에 적합.
- He 초깃값 앞 계층의 노드가 n개 일 때 표준편차는 $\frac{2}{\sqrt{n}}$으로 설정. ReLU같이 중앙 부근이 선형이 아니고 음의 영역이 0이라 더 넓게 분포시키기 위한 함수에 적합.
- Batch Normalization = 배치 정규화 가중치 감소에서 표준편차를 적절히 설정하지 못하면 활성화값이 분포가 잘 되지 않고 치우칠 수 있다했다. Batch Normalization은 여기서 활성화값이 적당히 퍼지도록 강제하는 방법이다. 이런 배치 정규화를 통해서 다음과 같은 장점을 취할 수 있다.
- 학습을 빨리 진행할 수 있음.(학습 속도 개선)
- 초깃값에 크게 의존하지 않음.(초깃값 선택 고민하지 않아도 됨.)
- 오버피팅 억제함.
- Dropout 가중치 감소는 간단하게 구현할 수 있지만 이것 만으로는 복잡한 신경망 모델에 대해서는 대응하기 어려워진다. 따라서 Dropout이라는 기법을 이용한다.
우선 초기값을 아예 작게 만드는 방법을 생각해 볼 수 있다. 그렇다고 아예 0으로 설정해버리면 학습이 되지 않는다.(정확히는 가중치들을 다 같은 값으로 설정하는 경우) 왜냐하면 오차역전파법에서 가중치의 값들이 똑같이 갱신되기 때문이다.(*연산과 +연산에 있어서 역으로 미분값이 전파될 때를 생각하면 알 수 있다.)
따라서 초깃값을 무작위로 설정해 줘야 하고 이를 위해 다음과 같이 표준편차가 0.1, 0.01과 같은 정규분포를 사용해 초기화 한다. 간단한 가중치 초기화하는 코드를 보자.
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
# 가중치 초기화
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
이 표준편차가 적절한 값이 아니면 활성화 값들이 일정값에 치우치는 현상이 발생할 수 있다. 이를 기울기 소실이라 부른다.이때문에 적절한 표준편차값으로 설정되는 알려진 값들이 있고 그것들은 다음과 같다.
def loss(self, x, t):
"""손실 함수를 구한다.
Parameters
----------
x : 입력 데이터
t : 정답 레이블
Returns
-------
손실 함수의 값
"""
y = self.predict(x)
weight_decay = 0
for idx in range(1, self.hidden_layer_num + 2):
W = self.params['W' + str(idx)]
weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W ** 2)
return self.last_layer.forward(y, t) + weight_decay
위에서 알 수 있듯이 손실 함수를 구하는 과정, 즉 순전파때는 $\frac{1}{2}\lambda W^2$를 더해나감을 볼 수 있다. 그럼 역전파 과정을 보자.def gradient(self, x, t):
"""기울기를 구한다(오차역전파법).
Parameters
----------
x : 입력 데이터
t : 정답 레이블
Returns
-------
각 층의 기울기를 담은 딕셔너리(dictionary) 변수
grads['W1']、grads['W2']、... 각 층의 가중치
grads['b1']、grads['b2']、... 각 층의 편향
"""
# forward
self.loss(x, t)
# backward
dout = 1
dout = self.last_layer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
# 결과 저장
grads = {}
for idx in range(1, self.hidden_layer_num+2):
grads['W' + str(idx)] = self.layers['Affine' + str(idx)].dW + self.weight_decay_lambda * self.layers['Affine' + str(idx)].W
grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db
return grads
self.weight_decay_lambda 값과 W를 곱한값을 역전파과정 때 W미분값에 더해서 갱신해줌을 볼 수 있다.
Dropout은 뉴런을 임의로 삭제하면서 학습한다. 훈련 때 무작위로 삭제된 은닉층의 뉴런은 신호를 전달하지 않게된다. 단, 시험 때는 모든 뉴런에 신호를 전달하고 각 뉴런의 출력에 훈련 때 삭제한 비율을 곱하여 출력한다. 이런 방법은 기계학습에서의 앙상블 학습 = ensemble learning과 일맥상통한다. Dropout이 학습 때 뉴런을 무작위로 삭제하는 행위를 매번 다른 모델을 학습시키는 것으로 해석할 수 있기 때문이다. 그리고 추론 때는 삭제한 비율을 곱함으로써 앙상블 학습에서의 여러모델 평균내는 것과 같은 효과를 얻는다. 따라서 Dropout은 앙상블 학습과 같은 효과를 하나의 네트워크로 구현했다고 생각할 수 있다. 더 자세한 분석은 이 곳을 보면 도움이 될 것 같다.
댓글
댓글 쓰기