신경망 학습의 목적은 손실 함수의 값을 최소화 할 수 있는 매개변수를 찾는 것. == 매개변수의 최적값을 찾는 것
정리하면 신경망 학습의 목적은 곧 최적화이다. ( optimization )
지금까지 최적화의 방법으로 매개변수의 기울기를 이용했었다. (미분)
매개변수의 기울기를 구해서 기울어진 방향으로 매개변수의 값을 갱신하는 일을 계속 반복해서 최적의 값을 찾아나갔다.
이를 SGD (확률적 경사 하강법)이라고 한다. 이 챕터에서는 SGD의외의 다양한 최적화 방법들을 소개한다.
우선 SGD를 포함한 다양한 옵티마이저를 그림으로 보고 넘어가자.
6.1.2 확률적 경사 하강법 (SGD)
SGD를 다시 복습해보자.
위 수식에서 W는 가중치, ∂L/∂W는 W에 대한 손실 함수의 기울기이다. n(에타)은 학습률이다. (보통 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]
SGD클래스를 이용하면 신경망 매개변수의 진행을 다음과 같이 수행할 수 있다.
network = TwoLayerNet(...)
optimizer = SGD()
for i in range(1000):
...
x_batch, t_batch = get_mini_batch(...) #미니배치
grads = network.gradient(x_batch, t_batch)
params = network.params
optimizer.update(params, grads)
...
위 코드처럼 optimizer = '...'로 지정하여 SGD의외 기법도 호출하여 편하게 사용할 수 있다.
6.1.3 SGD의 단점
6.1.4 모멘텀 ( Momentum )
모멘텀이란 운동량을 뜻하는 말로, 물리와 관계가 있다. 또한 수식으로는 다음과 같다.
n(에타) = 학습, v = 속도(기울기를 따라 이동하는 속도라고 생각)
∂L/∂W = 손실함수의 기울기, W = 가중치
av 항은 물체가 아무런 힘을 받지 않을 때 서서히 하강시키는 역할을 한다고 한다.
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는 물체의 속도
모멘텀을 생각할 때는 공이 경사면을 따라 굴러간다고 생각하면 이해하기 쉽다.
또한 SGD와 비교했을 때 위아래 흔들림이 적다.
6.1.5 Adagrad
신경망 학습에서는 학습률 값이 중요하다. 너무 크거나 작으면 학습이 효과적으로 이루어지지 않는다.
이 학습률을 올바르게 설정하기 위한 몇 가지 기술 中, 학습률 감소가 있다.
학습률 감소란 처음에는 크게 학습하다가 진행하면서조금씩 학습률을 점차 줄여나가는 방법이다.
학습률을 서서히 낮추는 가장 간단한 방법은 "전체"의 학습률을 값을 일괄적으로 낮추는 것이다.
이를 더욱 발전시킨 것이 AdaGrad이다.
Adagrad는 전체에서 발전해 "각각"의 매개변수에 맞게 갱신해준다.
'적응적 학슙를'을 기반으로 한 옵티마이저이다.
Adagrad, RMSprop = 적응적 학습률 기반
momentum이라는 개념을 이용하여 gradient를 조절
어떻게 개별 매개변수마다 갱신을 해줄까? 자세히 알아보자.
위 수식에서 h는 기존 기울기값을 제곱하여 계속 더해준다.
그리고 매개변수를 갱신할 때 1/ √h를 곱해서 학습률을 조정한다.!!
이 뜻은 매개변수 원소 중에서 많이 움직이는 원소는 학습률이 낮아진다는 뜻이라고 볼 수 있다.
(다시 말해 학습률 감소가 매개변수 원소마다 다르게 작용됨.)
손코딩
class AdaGrad:
def __init__(self, lr=0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h in None:
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]
parmas[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
주의) 마지막 줄에서 1e-7를 더해주는 부분. 1e-7은 self.h[key]에 0이 담겨 있어도 0으로 나누는 것을 막아줌.
Adagrad의 갱신경로를 그림으로 보자.
최소값을 향해 효율적인 움직임을 보여준다.!!
6.1.6 아담 ( Adam )
모멘텀은 공을 구르는 듯한 움직임을 보여준다. Adagrad는 원소 개별적으로 갱신해주었다.
이 둘을 합치면 Adam이라고 할 수 있다.
그림을 먼저 보자.
모멘텀과 비슷한 패턴이지만, 좌우 흔들림이 적은 것을 볼 수 있다.
이는 학습의 갱신 강도를 적응적으로 조정해서 얻는 benefit이라고 한다.
마지막으로 정리해서 봐보자.
무엇이 제일 좋다!! 라는 것은 데이터, 목적, 그 밖의 하이퍼 파라미터에 따라 다르기 때문에 정해진 것이 없다.
하지만 통상적으로 SGD보다 momentum,adam,adagrad 3개가 더 좋고, 일반적으로 Adam을 많이 쓴다고 한다.
6.2 가중치의 초기값
신경망 학습에서 가중치 초기값이 매우 중요하다고 한다.
2절에서는 권장 초기값을 배우고 실제로 학습이 잘 이루어지는지 확인해보자.
6.2.1 초기값을 0으로 하면?
이제부터 오버피팅을 억제해 범용 성능을 높이는 테크닉인 가중치 감소 기법을 소개해주겠다.
가중치 감소는 가중치 매개변수의 값이 작아지도록 학습하는 방법이다.
가중치 값을 작게 해서 오버피팅을 일어나지 않도록 하게 하는 것이다.
가중치 값을 작게 만들고 싶으면 초기값도 최대한 작은 값에서 시작하는 것이 정공법이기 때문에,,!
사실 지금까지 가중치 초기값은 0.01 * np.random.randn(10,100)처럼 정규분포에 생성된 값에 0.01배를 한 작은값을 사용
했었다.
그렇다면 가중치 초기값을 모두 0으로 설정하면 어떨까??
결론부터 말하자면, 아주 안좋은 생각이다.
실제로 가중치 초기값을 0으로 하면 학습이 올바로 이루어지지 않는다.
오차역전파법에서 모든 가중치의 값이 똑같이 갱신되기 때문이다.
예를 들어 2층 신경망에서 첫 번째, 두 번째 층의 가중치가 0이라고 가정해보자.
그럼 순전파 때는 입력층의 가중치가 0이기 때문에 두 번째 층의 뉴런에 모두 같은 값이 전달된다.
두 번째 층의 모든 뉴런에 같은 값이 입력된다는 것은 역전파 두 번 층의 가중치가 모두 똑같이 갱신된다는 말과 같다.
(곱셈 노드의 역전파를 기억해보자.)
그래서 가중치들을 같은 초기값에서 시작하고 갱신을 거쳐도 여전히 같은 값을 유지하는 것이다.
이는 가중치를 여러 개 갖는 의미를 사라지게 한다.
이 '가중치가 고르게 되어버리는 상황'을 막으려면 초기값을 무작위로 설정해야 한다.
6.2.2 은닉층의 활성화값 분포
은닉층의 활성화값(활성화 함수의 출력 데이터)의 분포를 관찰하면 중요한 정보를 얻을 수 있었다.
이번 절에서는 가중치의 초기값에 따라 은닉층 활성화 값들이 어떤 분포를 갖는지 시각화 해보려한다.
예시로 5층의 신경망을 가정, 시그모이드 함수 사용, 입력데이터 무작위로 선정
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-1))
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이 곳에 활성화 결과(활성화값)를 저장
fro 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
층은 5개, 각 층의 뉴런은 100개씩이다.
활성화 값들의 분포를 그려보자.
# 히스토그램 그리기
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또는 1에 가까워지면 미분값은 0에 수렴한다.
따라서 데이터가 0과 1에 치우쳐 분포하게 되면 역전파 기울기 값이 점점 작아지다가 사라진다.
이를 기울기 소실 현상 ( gradient vanishing )이라고 한다.
이제 표준편차를 0.01로 바꿔서 다시 그려보면 다음과 같은 그래프를 얻을 수 있다.
아까와 같은 기울기 소실 문제는 보이지 않지만, 모두 중간값에 몰빵되있는 것을 볼 수 있다.
이는 표현력 관점에서 문제라고 볼 수 있다.
Xavier 초기값
사비에르 초기값이란 사비에르가 작성한 논문에서 발췌한 것으로, 현재까지 딥러닝 프레임워크에서 많이 사용한다.
.신경망의 순전파에서는 가중치 총합의 신호를 계산하기 위해 행렬 곱을 사용했다 ( np.dot )
그리고 행렬 곱의 핵심은 원소 개수 (차원 개수)를 일치시키는 것이다.
예시를 통해 그림으로 이해해보자. (계산 그래프)
X,W,B가 있다고 하자. 각각 입력, 가중치, 편향이고 shape은 각각(2,) , (2,3), (3,)이다.
X와 W가 들어와 np.dot을 통해 행렬 곱 수행 -> x * W -> ( x * W ) +B = Y
이제 역전파를 알아보자.
아래 식이 도출되는것을 확인하자.
WT는 T의 전치행렬을 말함.
(전치행렬 = Wij 를 Wji로 바꾼것. 아래 그림 참조)
위 식을 통해서 계산 그래프의 역전파를 구해보자.
위 그림의 네모 박스 1,2를 유심히 살펴보자. (덧셈노드는 그대로 흐르고, 곱셈 노드는 교차하므로 !!)
X,W,B의 shape은 각각(2,) , (2,3), (3,)이다.
∂L/∂Y는 (3,) 인데 W인 (2,3)과 차원이 달라 곱셈이 되지 않는다.
따라서 WT를 통해 (3,2)로 바꿔 곱셈을 수행할 수 있도록 변환 (+X도 마찬가지)
5.6.2 배치용 Affine 계층
지금까지 예시로 든것은 X (입력 데이터)가 하나일 때만 고려한 상황이였다.
하지만 현실에서는 하나일 확률이 매우 적으므로 실제 Affine 계층을 생각해보자.
입력 데이터가 N개일 경우이다.
5.6.3 Softmax-with-Loss 계층
마지막으로 배울 것은 출력층에서 사용하는 소프트맥스 함수이다.
딥러닝에서는 학습과 추론 두 가지가 있다.
일반적으로 추론일 때는 Softmax 계층(layer)을 사용하지 않는다. Softmax 계층 앞의 Affine 계층의 출력을 점수(score)라고 하는데, 딥러닝의 추론에서는 답을 하나만 예측하는 경우에는 가장 높은 점수만 알면 되므로 Softmax 계층이 필요없다. 반면, 딥러닝을 학습할 때는 Softmax 계층이 필요하다.
이제 소프트맥스 계층을 구현할텐데, 손실 함수인 엔트로피 오차도 포함하여 계산 그래프를 살펴보자.
너무 복잡하다. 간소화 해보자.
여기서는 3클래스 분류를 가정하고 이전 계층에서 3개의 입력(점수)를 받는다.
그림과 같이 Softmax 계층은 입력 a1,a2,a3를 정규화하여 y1,y2,y3를 출력함