6. 학습 관련 기술들
이번장에서 배운 내용
- 매개변수 갱신 방법 - SGD, Momentum, AdaGrad, Adam
- 가중치 초깃값을 정하는 방법은 올바른 학습을 하는 데 매우 중요하다.
- 가중치의 초깃값으로는 Xavier 초깃값 과 He 초깃값 이 효과적이다.
- 배치 정규화를 이용하면 학습을 빠르게 진행할 수 있으며, 초깃값에 영향을 덜 받게 된다.
- 오버피팅을 억제하는 정규화 기술로는 가중치 감소 와 드롭아웃 이 있다.
- 하이퍼파라미터 값 탐색은 최적 값이 존재할 법한 범위를 점차 좁히면서 하는 것이 효과적이다.
매개변수 갱신
- 신경망 학습의 목적은 손실 함수의 값을 가능한 한 낮추는 매개변수를 찾는 것 → 매개변수의 최적화
- SGD 방법 이외에 Momentum, AdaGrad, Adam 등의 방법이 있다.
- 사용 기법에 따라 갱신 경로가 다르다.
- 일반적으로 SGD보다 다른 세 기법이 빠르게 학습하고, 때로는 최종 정확도도 높게 나타난다.
확률적 경사 하강법(SGD)
- 매개변수의 기울기를 구해, 기울어진 방향으로 매개변수 값을 갱신하는 일을 반복해서 최적의 값을 구하는 것
- SGD의 단점
- 비등방성 함수(anisotropy 함수: 방향에 따라 성질, 즉 기울기가 달라지는 함수)에서는 탐색 경로가 비효율적
- 이러한 단점을 개선해주는 Momentum, AdaGrad, Adam 방법
모멘텀(Momentum)
- Momentum은 운동향을 뜻하는 단어로 물리와 관계가 있음
- 모멘텀의 갱신 경로는 공이 그릇 바닥을 구르듯 움직인다.
- SGD와 비교하면, ‘지그재그 정도’가 덜한 것을 알 수 있다.
- 이는 x축의 힘은 아주 작지만 방향을 변하지 않아서 한 방향으로 일정하게 가속하기 때문
- 거꾸로 y축의 힘은 크지만 위아래로 번갈아 받아서 상충하여 y축 방향의 속도는 안정적이지 않다.
- SGD보다 x축 방향으로 빠르게 다가가 지그재그 움직임이 들어든다.
AdaGrad
- 신경망 학습에서는 학습률(learning rate) 값이 중요하다.
- 값이 너무 작으면 학습 시간이 너무 길어지고, 반대로 너무 크면 발산하여 학습이 제대로 이뤄지지 않는다.
- 학습률을 정하는 효과적 기술로 학습률 감소(learning rate decay)가 있다.
- 학습을 진행하면서 학습률을 점차 줄여가는 방법으로, 처음에는 크게 학습하다가 조금씩 작게 학습하는 것
-
학습률을 서서히 낮추는 가장 간단한 방법은 매개변수 전체 의 학습률 값을 일괄적으로 낮추는 것이다.
- AdaGrad 는 이를 더욱 발전시켜서, 각각의 매개변수에 맞춤형 값을 만들어준다.
- AdaGrad는 개별 매개변수에 적응적으로 학습률을 조정하면서 학습을 진행한다.
- AdaGrad는 과거의 기울기를 제곱하여 계속 더해간다.
- 학습을 진행할수록 갱신 강도가 약해진다.
- 실제로 무한히 계속 학습한다면 어느 순간 갱신량이 0이 되어 전혀 갱신되지 않게 된다.
- 이를 갱신한 방법으로 RMSProp이라는 방법이 있다.
- RMSProp은 과거의 모든 기울기를 균일하게 더해가는 것이 아니라, 먼 과거의 기울기는 서서히 잊고 새로운 기울기 정보를 크게 반영한다.
- 이를 지수이동평균(Exponential Moving Average, EMA)라고 하며, 과거 기울기의 반영 규모를 기하급수적으로 감소시킨다.
Adam
- 모멘텀은 공이 그릇 바닥을 구르는 듯한 움직임을 보였고, AdaGrad는 매개변수의 원소마다 적응적으로 갱신 정도를 조정했다.
- 이 두 기법을 융합한 것이 Adam이다.
- 매개변수 공간을 효율적으로 탐색해주며, 하이퍼파라미터의 편향 보정 이 진행된다.
- Adam은 하이퍼파라미터를 3개 설정한다.
- 학습률 a
- 일차 모멘텀용 계수 b1
- 이차 모멘텀용 계수 b2
- 논문에 따르면 기본 설정값은 b1=0.9, b2=0.999
가중치의 초깃값
- 오버피팅을 억제해 범용 성능을 높이는 테크닉인 가중치 감소(weight decay)
- 가중치 매개변수의 값이 작아지도록 학습하는 방법으로, 가중치를 작게 하여 오버피팅이 일어나지 않게 하는 것
- 그러나 초깃값을 모두 0으로 해서는 안된다. 정확히는 가중치를 균일한 값으로 설정해서는 안된다.
- 오차역전파법에서 모든 가중치의 값이 똑같이 갱신되기 때문이다.
- 이는 가중치를 여러 개 갖는 의미를 사라지게 한다.
- 그러므로 초깃값을 무작위로 설정 해야 한다.
은닉층의 활성화값 분포
- 가중치의 초깃값 에 따라 은닉층 활성확밧들이 어떻게 변화하는지 체크
### 가중치를 표준편차가 1인 정규분포로 초기화할 때의 각 층의 활성화값 분포 ###
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
def sigmoid(x):
return 1/(1+np.exp(-x))
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이 곳에 활성화 결과(활성화 값)를 저장
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
w = np.random.randn(node_num, node_num) * 1
a = np.dot(x, w)
z = sigmoid(a)
activations[i] = z
# 히스토그램 그리기
for i, a in activations.items():
plt.subplot(1, len(activations), i+1)
plt.title(str(i+1) + '-layer')
plt.hist(a.flatten(), 30, range=(0,1))
plt.show()
- 각 층의 활성화 값들이 0과 1에 치우쳐 분포되어 있다.
- 시그모이드 함수는 그 출력이 0에 가까워지자 그 미분은 0에 다가간다.
- 그래서 데이터가 0과 1에 치우쳐 분포하게 되면 역전파의 기울기 값이 점점 작아지다가 사라진다.
- 이것을 기울기 소실(gradient vanishing) 문제라고 한다.
- 층을 깊게하는 딥러닝에서는 기울기 소실은 더 심각한 문제가 될 수 있다.
### 가중치를 표준편차가 0.01인 정규분포로 초기화할 때의 각 층의 활성화값 분포 ###
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
def sigmoid(x):
return 1/(1+np.exp(-x))
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이 곳에 활성화 결과(활성화 값)를 저장
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
w = np.random.randn(node_num, node_num) * 0.01
a = np.dot(x, w)
z = sigmoid(a)
activations[i] = z
# 히스토그램 그리기
for i, a in activations.items():
plt.subplot(1, len(activations), i+1)
plt.title(str(i+1) + '-layer')
plt.hist(a.flatten(), 30, range=(0,1))
plt.show()
- 이번에는 0.5에 집중되어 있다.
- 활성화값들이 치우쳤다는 것은 표현력 관점에서는 큰 문제이다.
- 다수의 뉴런이 거의 값은 값을 출력하고 있으니 뉴런을 여러 개 둔 의미가 없어진다는 것이다.
- 그래서 활성화값들이 치우치면 표현력을 제한 한다는 관점에서 문제가 된다.
- 각 층의 활성화 값은 적당히 고루 분포되어야 한다.
Xavier 초깃값
- Xavier Glorot, Yoshua Bengio의 논문에서 권장하는 가중치 초깃값
- 이 논문에서는 각 층의 활성화값들을 광범위하게 분포시킬 목적으로 가중치의 적절한 분포를 찾고자 함
($n$ : 이전 layer(input)의 노드 수)
- 앞 층에 노드가 많을수록 대상 노드의 초깃값으로 설정하는 가중치가 좁게 퍼진다.
- Xavier의 논문은 앞 층의 입력 노드 수 외에 다음 층의 출력 노드 수도 고려한 설정값을 제안한다.
($n_{in}$ : 이전 layer(input)의 노드 수, $n_{out}$ : 다음 layer의 노드 수)
### 가중치를 초깃값으로 Xavier Initialization을 이용할 때의 각 층의 활성화값 분포 ###
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
def sigmoid(x):
return 1/(1+np.exp(-x))
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이 곳에 활성화 결과(활성화 값)를 저장
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
w = np.random.randn(node_num, node_num) / np.sqrt(node_num)
a = np.dot(x, w)
z = sigmoid(a)
activations[i] = z
# 히스토그램 그리기
for i, a in activations.items():
plt.subplot(1, len(activations), i+1)
plt.title(str(i+1) + '-layer')
plt.hist(a.flatten(), 30, range=(0,1))
plt.show()
- 층이 깊어지면서 형태가 다소 일그러지지만, 앞에서 본 방식보다는 확실히 넓게 분포됨을 알 수 있다.
- 오른쪽으로 갈수록 약간씩 일그러지고 있는데, 이 일그러짐은 sigmoid 함수 대신 tanh 함수(쌍곡선 함수)를 이용하면 개선된다.
- 실제로 tanh 함수를 이용하면 말끔한 종 모양으로 분포된다.
- tanh 함수가 원점(0,0)에서 대칭인 S 곡선인 반면, sigmoid 함수는 (x,y)=(0,0.5)에서 대칭인 S 곡선이다.
- 활성화 함수용으로는 원점에서 대칭인 함수가 바람직하다고 알려져있다.
He 초깃값
- Xavier 초깃값은 활성화 함수가 선형인 것을 전제로 이끈 결과다.
- sigmoid 함수와 tanh 함수는 좌우 대칭이라 중앙 부근이 선형인 함수로 볼 수 있다.
- 그래서 Xavier 초깃값이 적당하다
- 반면, ReLU를 이용할 때는 이에 특화된 초깃값을 이용해야 한다.
- 그것이 바로 He 초깃값이다.
- ReLU는 음의 영역이 0이라서 더 넓게 분포시키기 위해 2배의 계수가 필요하다고 직감적으로 해석할 수 있다.
($n_{in}$ : 이전 layer(input)의 노드 수)
활성화 함수로 ReLU를 사용한 경우의 가중치 초깃값에 따른 활성화값 분포 변화
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이 곳에 활성화 결과(활성화 값)를 저장
## 표준편차가 0.01인 정규분포를 가중치 초깃값으로 사용한 경우 ##
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
w = np.random.randn(node_num, node_num) * 0.01
a = np.dot(x, w)
z = relu(a)
activations[i] = z
# 히스토그램 그리기
for i, a in activations.items():
plt.subplot(1, len(activations), i+1)
plt.title(str(i+1) + '-layer')
plt.hist(a.flatten(), 30, range=(0,1))
plt.ylim((0,7001))
plt.show()
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이 곳에 활성화 결과(활성화 값)를 저장
## Xavier 초깃값을 사용한 경우 ##
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
w = np.random.randn(node_num, node_num) / np.sqrt(node_num)
a = np.dot(x, w)
z = relu(a)
activations[i] = z
# 히스토그램 그리기
for i, a in activations.items():
plt.subplot(1, len(activations), i+1)
plt.title(str(i+1) + '-layer')
plt.hist(a.flatten(), 30, range=(0,1))
plt.ylim((0,7001))
plt.show()
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이 곳에 활성화 결과(활성화 값)를 저장
## He 초깃값을 사용한 경우 ##
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
w = np.random.randn(node_num, node_num) / np.sqrt(node_num) * 2
a = np.dot(x, w)
z = relu(a)
activations[i] = z
# 히스토그램 그리기
for i, a in activations.items():
plt.subplot(1, len(activations), i+1)
plt.title(str(i+1) + '-layer')
plt.hist(a.flatten(), 30, range=(0,1))
plt.ylim((0,7001))
plt.show()
- std = 0.01일 때의 각 층의 활성확밧들은 아주 작은 값
- 역전파 때 가중치의 기울기 역시 작아진다는 뜻
- 실제로도 핛브이 거의 이뤄지지 않을 것
- Xavier 초깃값 결과를 보면 층이 깊어지면서 치우침이 조금씩 커진다.
- 학습할 때, 기울기 소실 문제을 일으킴
- He 초깃값은 모든 층에서 균일하게 분포됨
- 층이 깊어져도 분포가 균일하게 유지되기에 역전파 때도 적절한 값이 나올 것으로 기대할 수 있음
- 활성화 함수로 ReLU__를 사용할 때는 __He 초깃값 을, sigmoid 나 tanh 등의 S자 모양 곡선일 때는 Xavier 초깃값 사용
배치 정규화
- 가중치 초깃값을 적절히 설정하면 각 층의 활성화값 분포가 적당히 퍼지면서 학습이 원활하게 수행됨
- 각 층이 활성화를 적당히 퍼뜨리도록 ‘강제’하는 것 → 배치 정규화
- 배치 정규화의 장점
- 학습 속도 개선
- 초깃값에 크게 의존하지 않는다.
- 오버피팅 억제 로 드롭아웃 등의 필요성 감소
오버 피팅 방지
- 오버피팅이란, 신경망이 훈련 데이터에만 지나치게 적응되어 그 외의 데이터에는 제대로 대응하지 못하는 상태를 말한다.
- 주로 다음의 두 경우에 일어난다.
- 매개변수가 많고 표현력이 높은 모델
- 훈련 데이터가 적음
- 오버피팅 억제를 위해 사용하는 방법은 가중치 감소, 드롭아웃
가중치 감소(weight decay)
- 큰 가중치에 대해서는 그에 상응하는 큰 페널티를 부과하여 오버피팅을 억제하는 방법
- 가중치 감소는 모든 가중치 각각의 손실함수에 $\frac 12{\lambda}W^2$ 을 더한다.
- 가중치의 기울기를 구하는 계산에서는 그동안의 오차역전파법에 따르 결과에 정규화 항을 미분한 ${\lambda}W$을 더한다.
드롭아웃(dropout)
- 드롭아웃이란 뉴런을 임의로 삭제하면서 학습하는 방법이다.
- 훈련 때 은닉층의 뉴런을 무작위로 골라 삭제한다. 삭제된 뉴런은 신호를 전달하지 않게 된다.
- 훈련 때는 데이터를 흘릴 때마다 삭제할 뉴런을 무작위로 선택하고, 시험 때는 모든 뉴런에 신호를 전달한다.
- 단, 시험 때는 각 뉴런의 출력에 훈련 때 삭제 안한 비율을 곱하여 출력한다.
class Dropout:
def __init__(self, dropout_ratio=0.5):
self.dropout_ratio = dropout_ratio
self.mask = None
def forward(self, x, train_flg=True):
if train_flg:
self.mask = np.random.rand(*x.shape) > self.dropout_ratio
return x * self.mask
else:
return x * (1.0 - self.dropout_ratio)
def backward(self, dout):
return dout * self.mask
- 앙상블 학습은 드롭아웃과 밀접하다.
- 드롭아웃이 학습 떄 뉴런을 무작위로 삭제하는 행위를 매번 다른 모델을 학습시키는 것으로 해석할 수 있기 때문이다.
- 드롭아웃은 앙상블 학습과 같은 효과를 대략 하나의 네트워크로 구현했다고 생각할 수 있다.
적절한 하이퍼파라미터 값 찾기
검증 데이터
- 하이퍼파라미터의 성능을 평가할 때는 시험 데이터(test data)를 사용해서 안된다.
- 하이퍼파라미터 값이 시험 데이터에 오버피팅 되기 때문이다.
- 하이퍼파라미터 조정용 데이터를 일반적으로 검증 데이터(validation data) 라고 한다.
- 훈련 데이터(train data): 매개변수 학습
- 검증 데이터(validation data): 하이퍼파라미터 성능 평가
- 시험 데이터(test data): 신경망의 범용 성능 평가
하이퍼파라미터 최적화
- 하이퍼파라미터의 최적 값 이 존재하는 범위를 조금씩 줄여가는 것이 핵심이다.
- grid search같은 규칙적인 탐색보다는 무작위로 샘플링해 탐색하는 편이 좋은 결과를 낸다고 알려져 있다.
- 이는 최종 정확도에 미치는 영향력이 하이퍼파라미터마다 다르기 때문이다
- 하이퍼파라미터의 범위는 대략적으로 지정하는 것이 효과적인데, 실제로도 0.001 ~ 1000 사이와 같이 10의 거듭제곱 단위로 범위를 지정한다.
- 이를 로그 스케일 로 지정한다고 한다.
- 하이퍼파라미터를 최적화할 때는 딥러닝 학습에는 오랜 시간이 걸리므로, 학습을 위한 에폭을 작게 하여 1회 평가에 걸리는 시간을 단축하는 것이 효과적이다.
- 베이즈 최적화(bayesian optimization)이라는 최적화 방법도 존재한다.
프로세스
- 하이퍼파라미터 값의 범위를 설정한다.
- 설정된 범위에서 하이퍼파라미터의 값을 무작위로 추출한다.
- 2.에서 샘플링한 하이퍼파라미터 값을 사용하여 학습하고, 검증 데이터로 정확도를 평가한다(단, 에폭은 작게 설정한다.)
- 2., 3.를 특정 횟수(100회 등) 반복하며, 그 정확도의 결과를 보고 하이퍼파라미터의 범위를 좁힌다.