[밑바닥부터시작하는딥러닝1] Chapter 6. 학습관련기술들
6.1. 매개변수 갱신
✔️ 확률적 경사 하강법(SGD)
최적화(optimization) : 손실함수의 값을 가능한 낮추는 매개변수의 최적값을 찾는 것
이를 위해 매개변수의 기울기(미분)을 이용했다. 기울어진 방향으로 매개변수 값을 갱신하는 일을 반복해서 최적의 값에 다가갔는데 이를 확률적 경사 하강법(SGD)라고 하였다.
W는 갱신할 가중치 매개변수, 미분값은 W에 대한 손실함수의 기울기, η는 학습률
class SGD:
def __init__(self, lr=0.01):
self.lr = lr # 학습률
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr * grads[key]
✔️ SGD의 단점
SGD는 단순하고 구현이 쉽지만 문제에 따라서 비효율적일 때가 있다.
식 6-2의 기울기는 y축 방향은 크고 x축 방향은 작다는 것이 특징이다.
최솟값이 되는 점은 (x, y) = (0, 0)이지만 그림 6-2는 대부분 (0, 0)을 가리키지 않는다.
이를 SGD에 적용할 경우 그림 6-3과 같이 비효율적인 모습을 볼 수 있다.
이처럼 SGD는 비등방성함수(방향에 따라 성질, 즉 기울기가 달라지는 함수)에서 탐색 경로가 비효율적이다.
이런 SGD의 단점을 보완해주는 방법들을 알아보자.
✔️ 모멘텀(Momentum)
모멘텀은 운동량을 뜻하는 단어
W는 갱신할 가중치 매개변수, 미분값은 W에 대한 손실함수의 기울기, η는 학습률, v는 물리에서 말하는 속도
식 6-3은 기울기방향으로 힘을 받아 물체가 가속된다는 물리 법칙을 나타낸다.
αv는 물체가 아무런 힘을 받지 않을 때 서서히 하강시키는 역할로 물리에서의 지면 마찰이나 공기 저항에 해당한다.
class Momentum:
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]
params[key] += self.v[key]
그림 6-5와 같이 모멘텀의 갱신경로는 SGD보다 지그재그 정도가 덜한 것을 알 수 있다.
x축의 힘은 아주 작지만 방향은 변하지 않아서 한 방향으로 일정하게 가속하기 때문이다.
y축의 힘은 크지만 위아래로 번갈아 받아서 상충하여 y축 방향의 속도는 안정적이지 않다.
✔️ AdaGrad
학습률을 정하는 기술로 학습률 감소(learning rate decay)가 있다. 처음에는 크게 학습하다가 조금씩 작게 학습하는 방법이다.
가장 간단한 방법은 매개변수 전체의 학습률 값을 일괄적으로 낮추는 것인데, 이를 더 발전시킨 것이 AdaGrad이다.
AdaGrad는 각각의 매개변수에 맞춤형값을 만들어준다.
W는 갱신할 가중치 매개변수, 미분값은 W에 대한 손실함수의 기울기, η는 학습률
h는 기존 기울기값을 제곱하여 계속 더해준다. 그리고 매개변수를 갱신할 때 1/sqrt(h) 곱해 학습률을 조정한다.
매개변수의 원소 중 크게 갱신된 원소는 학습률이 낮아진다는 의미로, 학습률 감소를 매개변수 각각 다르게 적용된다는 뜻이다.
이 때 AdaGrad는 과거의 기울기를 제곱하여 계속 더해가기 때문에 학습을 진행할수록 갱신강도가 약해진다.
실제로 무한히 계속 학습하면 갱신량이 0이 되어 갱신되지 않게되는데 이를 해결하기 위한 방법으로 RMSProp가 있다.
RMSProp는 과거의 모든 기울기를 균일하게 더해가는 것이 아니라 먼 과거의 기울기는 서서히 잊고 새로운 기울기 정보를 크게 반영한다.
이를 지수이동평균(Exponential Moving Average, EMA)라고 한다.
class AdaGrad:
def __init__(self, lr=0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
작은 값 1e-7을 더하는 이유는 self.h[key]에 0이 담겨 0으로 나누는 사태를 막기 위함이다.
y축의 방향은 기울기가 커서 처음에는 크게 반영하지만 큰 움직임에 비례해 갱신 정도도 큰 폭으로 작아지도록 조정된다.
따라서 y축 방향으로 갱신 강도가 빠르게 약해지고, 지그재그 움직임이 줄어든다.
✔️ Adam
모멘텀과 AdaGrad를 융합한 방법
class Adam:
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.iter = 0
self.m = None
self.v = None
def update(self, params, grads):
if self.m is None:
self.m, self.v = {}, {}
for key, val in params.items():
self.m[key] = np.zeros_like(val)
self.v[key] = np.zeros_like(val)
self.iter += 1
lr_t = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)
for key in params.keys():
#self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]
#self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)
self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
모멘텀과 비슷한 패턴이나 공의 좌우 흔들림이 적다. 이는 학습의 갱신 강도를 적응적으로 조정했기 때문이다.
✔️ 갱신 방법 선택
앞선 이 4가지 방법 중 하나를 채택하는 방법은 풀어야 할 문제에 따라 달라진다.
6.2. 가중치의 초깃값
가중치 감소(weight decay) : 가중치 매개변수의 값이 작아지도록 학습하는 방법, 가중치 값을 작게 하여 오버피팅이 일어나지 않게 한다.
가중치를 작게 하고 싶다면 초기값을 최대한 작은 값에 시작하는 것이 정공법이다. 하지만 초기값을 0으로 설정해선 안되는데 오차역전파법에서 모든 가중치의 값이 똑같이 갱신되기 때문이다. 따라서 초깃값은 무작위로 설정해야 한다.
가중치의 초깃값에 따라 은닉층 활성화값(활성화 함수의 출력 데이터)이 어떻게 변화하는지 알아보자.
가중치의 분포를 표준편차가 1인 정규분포로 사용했을 때 히스토그램을 살펴보면 각 층의 활성화값들이 0과 1에 치우쳐져 있는 그림 6-10과 같다.
활성화함수로 시그모이드함수로 하였을 때 그 출력이 0이나 1에 가까워지면 그 미분이 0에 다가가게 된다. 그래서 데이터가 0과 1에 치우쳐 분포하게 되면 역전파의 기울기 값이 점점 작아지다 사라져 기울기 소실(gradient vanishing)이 일어난다. 층을 깊게 하는 딥러닝에서 기울기 소실은 심각한 문제가 될 수 있다.
표준편차를 0.01로 한 정규분포의 경우 각 층의 활성화값 분포는 그림 6-11과 같다.
이번에는 0.5 부근에 집중되어 기울기 소실 문제는 없지만 활성화값들이 치우쳤다는 것은 표현력을 제한한다.
즉, 다수의 뉴런이 거의 같은 값을 출력하고 있으니 뉴런을 여러개 둔 의미가 없어진다는 뜻이다.
활성화값을 고르게 분포시키기 위해 Xavier 초깃값을 사용해보자.
앞 계층의 노드가 n개라면 표준편차가 1/sqrt(n)인 분포를 사용하면 된다.
Xavier 초깃값을 사용하면 앞 층에 노드가 많을수록 대상 노드의 초깃값으로 설정하는 가중치가 좁게 퍼진다.
Xavier 초깃값을 사용한 결과는 그림 6-13과 같다.
층이 깊어질수록 형태가 일그러지지만 앞선 방식들보다는 넓게 분포됨을 알 수 있다.
(sigmoid함수 대신 tanh함수를 사용하면 개선될 것이다.tanh함수도 sigmoid함수와 같은 S자 모양 곡선 함수이지만 tanh함수는 원점(0, 0)에서 대칭인 반면 sigmoid함수는 (0, 0.5)에서 대칭인 S 곡선이다. 활성화 함수용으로 원점에서 대칭인 함수가 바람직하다고 알려져 있다.)
Xavier초깃값은 활성화 함수가 선형인 것을 전제로 이끌 결과이다.
sigmoid, tanh함수는 좌우대칭이라 중앙 부근이 선형인 함수로 볼 수 있다.
ReLU를 사용할 때는 ReLU에 특화된 초깃값인 He초깃값을 사용할 것을 권장한다.
He초깃값은 앞 계층의 노드가 n개일 때, 표준편차가 sqrt(2/n)인 정규분포를 사용한다.
ReLU는 음의 영역이 0이라서 더 넓게 분포시키기 위해 2배의 계수가 필요하다고 해석할 수 있다.
std=0.01일 때 각 층의 활성화값들은 아주 작은 값들이므로 역전파 때 가중치의 기울기 역시 작아진다.
따라서 학습이 거의 이뤄지지 않을 것이다.
Xavier 초깃값 결과를 보면 층이 깊어지면서 치우침이 조금씩 커진다.
이로 인해 기울기 소실 문제를 일으킬 것이다.
He 초깃값 결과는 모든 층에서 균일하게 분포되어 있는 것을 확인할 수 있다.
✔️ ReLU를 사용할 때 가중치 초깃값
따라서 활성화함수로 ReLU를 사용할 때는 He 초깃값,
sigmoid나 tanh 등의 S자 모양 곡선일 때는 Xavier 초깃값을 권장한다.
6.3. 배치 정규화
가중치의 초깃값을 적절히 설정하면 각 층의 활성화값 분포가 적당히 퍼지면서 학습이 원활하게 이루어진다.
그렇다면 각 층이 활성화를 적당히 퍼지도록 '강제'할 수는 없을까?
배치 정규화는 이러한 아이디어에서 나온 방법이다.
배치 정규화는
- 학습을 빨리 진행할 수 있다.(학습 속도 개선)
- 초깃값에 크게 의존하지 않는다.
- 오버피팅을 억제한다.(dropout 등의 필요성 감소)
배치정규화는 각 층에서의 활성화값이 적당히 분포되도록 조정하는 것이다.
따라서 그림 6-16과 같이 데이터 분포를 정규화하는 배치정규화(Batch Normalization)계층을 신경망에 삽입한다.
배치 정규화는 미니배치를 단위로 정규화한다. 데이터분포의 평균이 0, 분산이 1이 되도록 정규화한다.
미니배치 B={x1, x2, ..., xm} 이라는 m개의 입력 데이터의 집합에 대해 평균 μB와 분산 σB^2을 구한다.
그리고 입력데이터를 평균이 0, 분산이 1이 되게 정규화한다.
(ϵ는 작은 값으로 0으로 나누는 사태를 예방하는 역할)
이러한 배치 정규화를 활성화함수의 앞(or 뒤)에 삽입함으로써 데이터 분포가 덜 치우치게 할 수 있다.
✔️ 배치 정규화 효과
배치 정규화는 그림 6-18과 같이 학습 속도를 높인다는 것을 알 수 있다.
6.4. 바른 학습을 위해
✔️ 오버 피팅
오버피팅은 신경망이 훈련데이터에만 지나치게 적응되어 그 외의 데이터에는 제대로 대응하지 못하는 상태로 아래의 두 경우에 주로 나타난다.
- 매개변수가 많고 표현력이 높은 모델
- 훈련데이터가 적음
그림 6-20과 같이 훈련 데이터에서는 정확도가 거의 100%이지만 테스트 데이터에서는 정확도가 크게 벌어지는 것을 볼 수 있다.
✔️ 가중치 감소(weight decay)
오버피팅 억제용으로 가중치 감소(weight decay)를 많이 이용해왔다.
학습 과정에서 큰 가중치에 대해 그에 상응하는 큰 패널티를 부과하여 오버피팅을 억제하는 방법이다.
오버피팅은 가중치 매개변수의 값이 커서 발생하는 경우가 많기 때문이다.
가중치의 제곱 법칙(L2 법칙)을 손실 함수에 더한다. 그러면 가중치가 커지는 것을 억제할 수 있다.
가중치를 W라고 하면 L2법칙에 따른 가중치 감소는 1/2 λ (W**2)가 되고 이를 손실함수에 더한다.
여기서 λ는 정규화의 세기를 조절하는 하이퍼파라미터로 크게 설정할수록 큰 가중치에 대해 페널티가 커진다.
1/2는 미분 결과인 λW를 조정하는 역할의 상수이다.
따라서 가중치 감소는 모든 가중치 각각의 손실 함수에 1/2 λ (W**2)를 더한다.
가중치의 기울기를 구하는 계산에서는 오차역전파법에 따른 결과에 정규화 항을 미분한 λW를 더한다.
훈련 데이터와 시험 데이터에 대한 정확도 차이가 있긴 하지만 그림 6-20과 비교하면 오버피팅 문제가 개선되었다는 것을 알 수 있다.
✔️ 드롭아웃(dropout)
신경망 모델이 복잡해지면 가중치 감소만으로 대응하기 어려워 드롭아웃(dropout)이라는 기법을 이용한다.
드롭아웃은 뉴런을 임의로 삭제하면서 학습하는 방법으로 훈련 때 은닉층의 뉴런을 무작위로 골라 삭제한다.
코드에서 핵심은 훈련 시에 순전파 때마다 self.mask에 삭제할 뉴런을 False로 표시한다는 것이다. self.mask는 x와 형상이 같은 배열을 무작위로 생성하고, 그 값이 dropout_ratio보다 큰 원소만 True로 설정한다. 역전파 때의 동작은 ReLU와 같다.
즉 순전파 때 신호를 통과시키는 뉴런은 역전파 때도 신호를 그대로 통과시키고, 순전파 때 통과시키지 않은 뉴런은 역전파 때도 신호를 차단한다.
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
그림 6-23과 같이 훈련 데이터와 시험 데이터에 대한 정확도 차이가 줄어든 것을 확인할 수 있다.
6.5. 적절한 하이퍼파라미터 값 찾기
✔️ 검증 데이터
하이퍼파라미터의 성능을 평가할 때는 시험 데이터를 사용해서는 안된다.
시험 데이터를 사용해서 조정하면 하이퍼파라미터값이 시험 데이터에 오버피팅되기 때문이다.
따라서 하이퍼파라미터 전용 확인 데이터가 필요한데 이를 검증 데이터(validation data)라고 한다.
from dataset.mnist import load_mnist
from common.util import shuffle_dataset
(x_train, t_train), (x_test, t_test) = load_mnist()
# 훈련 데이터를 뒤섞는다.
x_train, t_train = shuffle_dataset(x_train, t_train)
# 20%를 검증 데이터로 분할
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]
✔️ 하이퍼파라미터 최적화
하이퍼파라미터를 최적화할 때는 하이퍼파라미터의 최적값이 존재하는 범위를 조금씩 줄여간다는 것이다.
대략적인 범위를 설정하고 그 범위에서 무작위로 하이퍼파라미터 값을 골라낸(샘플링) 후 그 값으로 정확도를 평가하며 최적값의 범위를 좁혀간다.
하이퍼파라미터의 범위는 '대략적으로' 지정하는 것이 효과적이다.
보통 10의 거듭제곱단위로 범위를 지정하는데 이를 '로그 스케일(log scale)로 지정'한다고 한다.
하이퍼파라미터의 범위를 좁히고 어느 정도 좁아졌을 때 그 압축한 범위에서 값을 하나 골라내면 이것이 하이퍼파라미터를 최적화하는 방법 중 하나이다.
검증 데이터의 학습 추이를 정확도가 높은 순서로 나열했을 때 그림 6-24와 같다.
학습이 잘 진행될 때의 학습률은 0.001~0.01, 가중치 감소계수는 10^-8~10^-6정도이다.
이처럼 잘될 것 같은 값의 범위를 관찰하고 범위를 좁혀 최종 하이퍼파라미터 값을 하나 선택한다.
6.6. 정리
- 매개변수 갱신 방법에는 호가률적 경사 하강법(SGD) 외에도 모멘텀, AdaGrad, Adam 등이 있다.
- 가중치 초깃값을 정하는 방법은 올바른 학습을 하는 데 매우 중요하다.
- 가중치의 초깃값으로는 Xavier 초깃값과 He 초깃값이 효과적이다.
- 배치 정규화를 이용하면 학습을 빠르게 진행할 수 있으며, 초깃값에 영향을 덜 받게 된다.
- 오버피팅을 억제하느 정규화 기술로는 가중치 감소와 드롭아웃이 있다.
- 하이퍼파라미터값 탐색은 최적 값이 존재할 법한 범위를 점차 좁히면서 하는 것이 효과적이다.
참고 git : https://github.com/geonsangyoo/DeepLearning/tree/master