이전 포스팅에서는 back propagation에 대해서 간략하게 설명을 해보았다. 이번 장에서는 신경망 학습에서의 핵심 개념들을 설명한다. 가중치 매개변수의 최적값을 탐색하는 최적화 방법, 가중치 매개변수 초깃값, 하이퍼파라미터 설정 방법 등등을 알려준다. 그리고 오버피팅, 드롭아웃, 정규화도 간략하게 알아보자.
신경망 학습의 목적은 손실 함수의 값을 가능한 한 낮추는 매개변수를 찾는 것이다. 이는 곧 매개변수의 최적값을 찾는 문제이며, 그러한 문제를 푸는 것이 최적화(optimization)이다. 우리는 지금까지 최적의 매개변수 값을 찾는 단서로 매개변수의 기울기(미분)를 이용하였다. 매개변수 갱신을 몇번이고 반복해 점점 최적의 값에 다가갔다.
SGD는 수식으로 다음과 같다.
여기서 는 학습률을 의미하는데, 실제로는 0.01이나 0.001과 같은 값을 미리 정해서 사용한다.
식에서 보듯 SGD는 기울어진 방향으로 일정 거리만 가겠다는 단순한 방법이다.
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]
초기화 때 받은 lr은 learning rate(학습률)을 뜻한다.
network = TwoLayerNet(...)
optimizer = SGD()
for i in range(10000):
...
x_batch, t_batch = get_mini_batch(...) #미니배치
grads = network.gradient(x_batch, t_batch)
params = network.params
optimizer.update(params, grads)
...
위 코드에서 optimizer가 매개변수 갱신을 책임지고 수행한다. 그러므로, 우리는 optimizer에 매개변수 기울기 정보만 넘겨주면 된다.
모멘텀의 수식은 다음과 같다.
여기서 v라는 변수가 새로 나오는데 이는 물리에서 말하는 속도에 해당한다. 기울기 방향으로 힘을 받아 물체가 가속된다는 물리 법칙을 나타낸다. 항은 물체가 아무런 힘을 받지 않을 때 서서히 하강시키는 역할을 한다. (는 0.9 로 보통 설정을 한다.) 이는 물리에서 지면 마찰이나 공기 저항에 해당한다.
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]
인스턴스 변수 v가 물체의 속도이다. v는 초기화 때는 아무 값도 담지 않고, 대신 update()가 처음 호출될 때 매개변수와 같은 구조의 데이터를 딕셔너리 변수로 저장한다. 아래 그림을 보면 sgd보다 지그재그가 정도가 덜하다.
신경망 학습에서는 학습률() 값이 중요하다. 이 값이 너무 작으면 학습 시간이 너무 길어지고, 반대로 너무 크면 발산하여 학습이 제대로 이뤄지지 않는다. 이 학습률을 정하는 효과적 기술로 학습률 감소(learning rate decay)가 있다. 학습을 진행하면서 학습률을 점차 줄여가는 방법이다. 처음에는 크게 학습하다가 조금씩 작게 학습을 한다는 말이다.
학습률을 서서히 낮추는 가장 간단한 방법은 매개변수 '전체'의 학습률 값을 일괄적으로 낮추는 것이다. 이를 더욱 발전시킨 것이 AdaGrad 이다. 수식은 아래와 같다.
마찬가지로 는 갱신할 가중치 매개변수, 은 에 대한 손실 함수의 기울기, 는 학습률을 의미한다. 여기서는 새로운 변수 가 등장한다. 기존 기울기 값을 제곱하여 계속 더해준다. 이 과정을 통해 학습률을 조정한다. AdaGrad는 과거의 기울기를 제곱하여 계속 더한다. 그래서 학습을 진행할수록 갱신 강도가 약해진다. 실제로 무한히 계쏙학습한다면 어느 순간 갱신량이 0이 되어 전혀 갱신되지 않게 된다. 이 문제를 개선한 기법으로는 RMSProp이라는 방법이 있다. 이 방법은 과거의 모든 기울기를 균일하게 더해가는 것이 아니라, 먼 과거의 기울기는 서서히 잊고 새로운 기울기 정보를 크게 반영한다.
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으로 나누는 사태를 막아준다.
모멘텀은 공이 그릇 바닥을 구르는 듯한 움직임을 보였고, AdaGrad는 매개변수의 원소마다 적응적으로 갱신 정도를 조정했다. 이 두 기법을 융합한게 Adam이다.
두 방법의 이점을 조합했으므로, 매개변수 공간을 효율적으로 탐색해줄 것이고, 하이퍼파라미터의 '편향 보정'이 진행되는 것이 이 기법의 특징이다.
풀어야 할 문제가 무엇이냐에 따라 사용해야할 기법은 달라진다. 또한, 하이퍼파라미터를 어떻게 설정하는냐에 따라서도 결과가 바뀐다. 모든 문제에서 항상 뛰어나 기법이라는 것은 없다. 각자의 장단이 있기 때문이다.
신경망에서 특히 중요한 것은 가중치의 초깃값이다. 가중치의 초깃값을 무엇으로 설정하느냐가 신경망 학습의 성패를 가르는 일이 실제로 자주 있다.
오버피팅을 억제하기 위해 가중치 감소(weight decay)를 사용한다. 가중치를 작게 만들어 오버피팅을 일어나지 않게 한다는 것이다. 그럼 0으로 하면 어떻게 될까?
일단 결론부터 말하자면 가중치는 0으로 초기화 하면 안된다. 그 이유는 오차역전파법에서 모든 가중치의 값이 똑같이 갱신되기 때문이다. 예를 들어, 2층 신경망에서 첫 번째와 두 번째층의 가중치가 0이라고 가정하면, 순전파 때는 입력층의 가중치가 0이기 때문에 두 번째 층의 뉴런에 모두 같은 값이 전달된다. 두 번째 층의 모든 뉴런에 같은 값이 입력된다는 것은 역전파 때 두 번째 층의 가중치가 모두 똑같이 갱신된다는 말이다. 이렇게 되므로 가중치들은 같은 초깃값에서 시작하고 갱신을 거쳐도 여전히 같은 값을 유지한다. 가중치가 고르게 되어버리는 상황을 막으려면 초깃값을 무작위로 설정을 해야한다.
Xavier 초깃값
표준편차 :
He 초깃값 (Relu)에 특화됨
표준편차 :
배치 정규화의 장점
배치 정규화는 미니배치 단위로 정규화를 하고 평균이 0, 분산이 1이 되도록 정규화 한다.
위의 경우에 주로 오버피팅이 일어난다. 오버피팅이란 신경망이 훈련데이터에만 지나치게 적응되어 그 외의 데이터에는 제대로 대응하지 못하는 상태를 말한다. 기계학습은 범용 성능을 지향하기에, 아직 보지 못한 데이터가 주어져도 바르게 식별해내는 모델이 바람직하다.
드롭아웃은 뉴런을 임의로 삭제하면서 삭제하는 방법이다. 훈련 때 은닉층의 뉴런을 무작위로 골라 삭제한다.
앙상블 학습 : 앙상블 학습은 개별적으로 학습시킨 여러 모델의 출력을 평균 내어 추론하는 방식이다. 신경망 기준으로는, 비슷한 구조의 네트워크 5개를 준비하여 따로따로 학습시키고, 시험 때는 그 5개의 출력을 평균내어 답하는 것이다. 앙상블을 하면 신경망의 정확도가 조금 개선된다는 것이 실험적으로 알려져 있다. 드롭아웃도 이와 비슷하다.매번 무작위로 삭제하는 행위가 매번 다른 모델을 학습시키는 것으로 해석할 수 있다.그러므로, 앙상블 학습과 같은 효과를 하나의 네트워크로 했다고 생각을 할 수가 있다.
class Dropout:
def __init__(self, dropout_ratio=0.5)
self.dropout_ratio = dropout_ratio
self.mask = None
def forward(self, x, train_fig=True)
if train_fig:
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
신경망에는 하이퍼 파라미터가 등장한다. 예를 들어, 각 층의 뉴런 수, 배치 크기, 매개변수 갱신 시 학습률과 가중치 감소 등이다.
지금까지는 데이터셋을 훈련 데이터와 시험 데이터 두 가지로 분리 했었다. 여기서 주의할 점은 하이퍼파라미터의 성능을 평가할 때는 시험 데이터를 사용해서는 안된다. 하이퍼파라미터 조정용 데이터를 일반적으로 validation data라고 한다.