[Deep Learning from Scratch] chapter 6.학습관련기술들(최적화, 가중치 초기값, 배치정규화)
class SGD:
def __init__(self, lr=0.01):
self.lr =lr
def update(self, params, grads):# params['W1'],grad['W1'] 과 같은 변수를 정장하는 딕셔너리 변수
for key in params.keys():
params[key] -= self.lr * grad[key] #
Momemtum(모멘텀)¶
- momemtum은 운동량을 뜻하는 것
$$ v \leftarrow \alpha v - \eta \frac{\partial L}{\partial W} \\ W \leftarrow W + v $$
- $W$ : 갱신할 가중치 매개변수
- $\frac{\partial L}{\partial W}$ : W에 대한 손실함수 기울기
- $\eta$ :학습률(learning rate)
- $v$ :속도(velocity), 기울기 방향으로 힘을 받아 물체가 가속된다는 뜻
- $\alpha$ : fraction
- $\alpha 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]
AdaGrad(Adaptive Gradient)¶
- 학습을 진행하면서 학습률을 점차 줄여나감(learning rate decay)
- 즉, 처음에는 크게 학습하다가 조금씩 작게 학습함
개별 매개변수에 적응적으로(Adaptive)학습률을 조정하여 학습을 진행
$$h \leftarrow h+\frac{\partial L}{\partial W}\odot \frac{\partial L}{\partial W}\\ W \leftarrow W-\eta\frac{1}{\sqrt{h}} \frac{\partial L}{\partial W}$$
$W$ :갱신할 가중치 매개변수
- $\frac{\partial L}{\partial W}$ : W에 대한 손실함수 기울기
- $\eta$: 학습률
- $h$ :기존 기울기의 값을 제곱하여 더해주며 (행렬의 원소별 곱셈) 매개변수를 갱신할 때 $\frac{1}{\sqrt{h}}$를 곱해 학습률 조정
매개변수의 원소 중 많이 움직인 (많이 갱신된) 원소는 학습률이 낮아지며 학습률의 감소가 매개변수의 원소마다 다르게 적용됨
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[keys]+1e-7)
# 마지막에 self.h[key]에 1e-7을 더하는 것은 self.h[key]에 0이 답겨 있어도 0으로 나누는 사태를 막는다.
RMSProp¶
- 과거의 모든 기울기를 동일하게 더해 가는 것이 아니라 먼 과거의 기울기는 서서히 잊고 새로운 기울기 정보를 크게 반영(지수이동평균, Exponential Moving Average), 과거 기울기의 반영 규모를 기하급수적으로 감소시킴
$$ S_{dw}= \beta S_{dw} +(1-\beta )dW^2\\S_{db}= \beta S_{db} +(1-\beta )db^2\\W \leftarrow W-\alpha\frac{\partial W}{\sqrt{S_{dw}}}\\b \leftarrow b-\alpha\frac{\partial b}{\sqrt{S_{db}}}$$
- reference from Deep Learning AI by Andrew Ng
class RMSProp:
def __init__(self, lr=0.01, decay_rate =0.99):
self.lr = lr
self.decay_rate = decay_rate
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] *= self.decay_rate
self.h[keys] += (1- self.decay_rate)*grads[key]*grads[key]
params[key] -=self.lr *grads[key]/(np.sqrt(self.h[key])+ 1e-7)
Adam(Adaptive Moment Estimation)¶
Momemtom 과 AdaGrad를 융합한 듯한 방법, 하이퍼파라미터의 편향보정이 진행됨
$$ V_{dw}= \beta_1 V_{dw} +(1-\beta_1 )dW\\ V_{db}= \beta_1 V_{db} +(1-\beta_1 )db\\ S_{dw}= \beta_2 S_{dw} +(1-\beta_2 )dW^2\\ S_{db}= \beta_2 S_{db} +(1-\beta_2 )db^2$$
Bias Correction:
$$V_{dw}^{corrected}= \frac{V_{dw}}{(1-\beta_1^t)}\\ V_{db}^{corrected}= \frac{V_{db}}{(1-\beta_1^t)}\\ S_{dw}^{corrected}= \frac{S_{dw}}{(1-\beta_2^t)}\\ S_{db}^{corrected}= \frac{S_{db}}{(1-\beta_2^t)} $$$$W \leftarrow W-\alpha \frac{V_{dw}^{corrected}}{\sqrt{S_{dw}^{corrected}}+\epsilon} \\ b \leftarrow b-\alpha \frac{V_{db}^{corrected}}{\sqrt{S_{db}^{corrected}}+\epsilon}$$
Adam의 하이퍼파라미터는 $\alpha$ 인 learning rate과 모멘텀용 계수 $\beta_1$,$\beta_2$이다. 기본값은 $\beta_1$은 0.9, $\beta_2$는 0.999로 설정된다.
- reference from Deep Learning AI by Andrew Ng
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]
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)
가중치 초깃값¶
- 가중치 감소(weight decay): 오버핏팅을 억제해 범용성능을 높이는 테크닉
- 가중치의 초깃값을 0 으로 설정한다면?
학습이 올바로 이루어 지지 않음, 모든 가중치의 값이 똑같이 갱신되어 역전파 때 도 가중치가 똑같이 갱신됨. 따라서 갱신을 거쳐도 여전히 같은 값을 유지 -> 이러한 상황을 막으려면 (가중치의 대칭적 구조가 되는 것을 막으려면) 초깃값을 무작위로 설정해야 함
import numpy as np
import matplotlib.pyplot as plt
# 비교모형
def sigmoid(x):
return 1/(1+np.exp(-x))
def Relu(x):
return np.maximum(0, x)
def tanh(x):
return np.tanh(x)
input_data = np.random.randn(1000, 100) #1000개의 데이터
node_num = 100 # 은닉층 노드 갯수
hidden_layer_size = 5 # 은닉층 갯수
x = input_data
class HiddenLayerCom:
def __init__(self, x, model, hidden_layer_size, std = 1, node_num=100):
activations = {}
self.x = x
self.model = model
self.std = std
self.hidden_layer_size = hidden_layer_size
self.node_num = node_num
def generate(self, x,model, hidden_layer_size, std, node_num):
activations = {}
for i in range(self.hidden_layer_size):
if i!=0:
x = activations[i-1]
w = np.random.randn(self.node_num, self.node_num) * self.std
z = np.dot(self.x, w)
a = self.model(z)
activations[i] = a
return activations
Sigmoid with 1 std on Weight Initialization¶
sig = HiddenLayerCom(x, sigmoid, hidden_layer_size, 1, 100)
sig1 = sig.generate(x, sigmoid, hidden_layer_size, 1, 100)
for i, a in sig1.items():
plt.subplot(1, len(sig1), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
plt.hist(a.flatten(), 30, range=(0,1))
plt.suptitle('Sigmoid Fuction using 1 standard deviation on Weight')
plt.show()
- 각 층의 활성화 값이 0 과 1에 치우쳐 분포
- 시그모이드 함수는 출력값이 0 과 1에 가까우면 미분값이 0에 다가가게 되고 이는 역전파의 기울기기 점점 작아지다 사라진다는 것을 의미한다. => Gradient Vanishing(기울기 소실)
Sigmoid with 0.01 std on Weight Initialization¶
sig = HiddenLayerCom(x, sigmoid, hidden_layer_size, 0.01, 100)
sig2 = sig.generate(x, sigmoid, hidden_layer_size, 0.01, 100)
for i, a in sig2.items():
plt.subplot(1, len(sig2), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
plt.hist(a.flatten(), 30, range=(0,1))
plt.suptitle('Sigmoid Fuction using 0.01 standard deviation on Weight')
plt.show()
- 활성화 값이 0.5 부근에 치우져 짐 => 다수의 뉴련이 거의 같은 값을 출력하고 있으므로 뉴런을 여러개 둔 의미가 없어짐
- 즉, 활성화 값이 치우치면 표현력을 제한하게 됨
활성화 값은 적당히 골루 분포되어야 함. 층과 층 사이에 다양한 데이터가 흐르게 해야 신경망 학습이 효율적으로 이루어지기 때문
치우친 데이터가 흐르면 기울기 소실이나 표현력 제한 문제에 빠져 학습이 잘 이루어 지지 않는 경우가 발생
Relu with 0.01std¶
relu = HiddenLayerCom(x, Relu, hidden_layer_size, 0.01, 100)
relu1 = relu.generate(x, Relu, hidden_layer_size, 0.01, 100)
for i, a in relu1.items():
plt.subplot(1, len(relu1), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
plt.hist(a.flatten(), 30, range=(0,1))
plt.suptitle('Relu Fuction using 0.01 standard deviation on Weight')
plt.show()
tanh with 0.01std¶
tanh = HiddenLayerCom(x, tanh, hidden_layer_size, 0.01, 100)
tanh1 = tanh.generate(x, tanh, hidden_layer_size, 0.01, 100)
for i, a in tanh1.items():
plt.subplot(1, len(tanh1), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
plt.hist(a.flatten(), 30, range=(0,1))
plt.suptitle('Tanh Fuction using 0.01 standard deviation on Weight')
plt.show()
Xavier and He초깃값¶
Xavier초깃값
- 일반적인 딥러닝 프레임워크들이 표준적으로 이용
- 활성화 값들을 광범위하게 분포시킬 목적으로 가중치의 적절한 분포를 찾고자 함
- 표준편차가 $\frac{1}{\sqrt{n}}$ 인 정규분포로 초기화 (n은 앞층의 노드수)
- 앞 층에 노드가 많을수록 대상 노드의 초깃값으로 설정하는 가중치가 좁게 퍼짐
He 초깃값
- 표준편차가 $\frac{2}{\sqrt{n}}$ 인 정규분포로 초기화 (n은 앞층의 노드수)
- Relu는 음의 영역이 0 이라서 더 넓게 분포시키기 위해 2의 계수가 필요하다고 해석할 수 있음
Xavier초깃값은 Sigmoid나 tanh와 같은 함수에 적당
- He초깃값은 Relu에 적합
Sigmoid with Xavier weight initiation¶
node_num =100 # 앞층 노드 수
Xavierstd = np.sqrt(1/node_num)
sigmoid = HiddenLayerCom(x, sigmoid, hidden_layer_size, Xavierstd, node_num)
sigmoid_x = sigmoid.generate(x, sigmoid, hidden_layer_size, Xavierstd, node_num)
for i, a in sigmoid_x.items():
plt.subplot(1, len(sigmoid_x), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
plt.hist(a.flatten(), 30, range=(0,1))
plt.suptitle('Sigmoid Fuction using Xavier on Weight initiation')
plt.show()
Relu with Xavier weight initiation¶
책과 다른 양상의 그래프가 그려짐... 확인이 필요할듯
relu = HiddenLayerCom(x, Relu, hidden_layer_size, Xavierstd, node_num)
relu2 = relu.generate(x, Relu, hidden_layer_size, Xavierstd, node_num)
for i, a in relu2.items():
plt.subplot(1, len(relu2), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
plt.hist(a.flatten(), 30, range=(0,1))
plt.suptitle('Relu Fuction using Xavier on Weight initiation')
plt.show()
Relu with He weight initiation¶
node_num =100 # 앞층 노드 수
Hestd = np.sqrt(2/node_num)
reluh = HiddenLayerCom(x, Relu, hidden_layer_size, Hestd, node_num)
relu3 = reluh.generate(x, Relu, hidden_layer_size, Hestd, node_num)
for i, a in relu3.items():
plt.subplot(1, len(relu3), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
plt.hist(a.flatten(), 30, range=(0,1))
plt.suptitle('Relu Fuction using Xavier on Weight initiation')
plt.show()
배치 정규화 ( Batch Normalization)¶
각 층이 원활하게 학습할 수 있도록 활성화를 강제함.
Batch Normalization이 주목받는 이유
- 학습을 빨리 진행할 수 있다.(학습속도 개선)
- 초깃값에 크게 의존하지 않는다.
- 오버피팅을 억제한다(드롭아웃등의 필요성이 감소)
데이터 분포를 정규화 하는 배치 정규화 계층을 신경망에 삽입함
input data $\Rightarrow$ Affine $\Rightarrow$ Batch Norm $\Rightarrow$ ReLU $\Rightarrow$ Affine $\Rightarrow$ Batch Norm $\Rightarrow$ ReLU $\Rightarrow$ Affine $\Rightarrow$ Softmax
배치 정규화는 미니배치를 단위로 정규화 함, 데이터 분포가 평균이 0 분산이 1이 되도록 정규화
$$\mu \leftarrow \frac{1}{m} \sum_{i=1}^{m} x_i \\ \sigma_b^2 \leftarrow \frac{1}{m} \sum_{i=1}^{m} (x_i-\mu_B)^2 \\ \hat{x_i} \leftarrow \frac{x_i -\mu_B}{\sqrt{\sigma_B^2 +\epsilon }}$$
$\epsilon$은 0으로 나누는 사태를 예방하는 역할
- 배치 정규화 계층마다 정규화된 데이터에 고유한 확대(scale)와 이동(shift)변화을 수행
$$y\leftarrow \gamma \hat{x_i}+\beta$$
$\gamma$가 확대(scale), $\beta$가 이동을 담당
오버피팅( overfitting)¶
- 신경망이 훈련데이터에만 지나치게 적응되어 그외의 데이터에 제대로 적응하지 못하는 상태
- 오버피팅이 주로 일어나는 경우
- 매개변수가 많고 표현력이 높은 모델
- 훈련데이터가 적음
가중치 감소(Weight Decay)¶
- 오버피팅 억제용으로 많이 이용해온 방법
- 오버피팅은 가중치 매개변수의 값이 커서 발생하는 경우가 많기 때문
- $L2-Norm$: Loss에 L2Norm을 더한다. $L2-Norm = \lambda \sqrt{W^2}$ -$L1-Norm$: Loss에 L1Norm을 더한다. $L1-Norm = \lambda \left|W \right|$
드롭아웃(Dropout)¶
- 뉴런을 임의로 삭제하면서 학습하는 방법
- Training때 은닉층의 뉴런을 무작위로 골라 삭제함
적절한 하이퍼파라미터 찾기¶
하이퍼파라미터의 성능을 평가할 때는 하이퍼파라미터 조정용 데이터 즉 검증 데이터(validation data)를 이용
- 훈련데이터(Train data): 매개변수 학습
- 검증데이터(Validation data): 하이퍼파라미터 성능 평가
- 시험데이터(Test data): 신경망의 범용성능 평가
하이퍼파라미터의 최적값이 존재하는 범위를 조금씩 줄어나간다. $\rightarrow$ Grid Search같은 규칙적인 탐색보다 무작위로 샘플링하여 탐색하는 것이 더 좋은 결과를 낸다고 알려져 있다. 최종 정확도에 미치는 영향력이 하이퍼파라미터마다 다르기 때문
실제로 0.001에서 1000사이 ($10^-3 \sim 10^3)$ 와 같이 10의 거듭제곱 단위로 범위를 지정한다(로그 스케일,log scale)
하이퍼파라미터 최적화 단계
- 0 단계 : 하이퍼파라미터 값의 범위를 설정
- 1 단계 : 설정된 범위에서 하이퍼파라미터의 값을 무작위로 추출
- 2 단계 : 1단계에서 샘플링한 하이퍼파라미터 값을 사용하여 학습하고, 검증데이터로 정확도 평가(에폭을 작게 설정)
- 3 단계 : 1단계와 2단계를 특정횟수 반복하여 그 정확도의 결과를 보고 하이퍼파라미터의 범위를 좁힘
Key Takeaway
- 매개변수 갱신 방법에는 확률적 경사하강법(SGD)외에도 모멤텀, AdaGrad, Adam등이 있다.
- 가중치 초깃값을 정하는 방법은 올바른 학습을 하는데 매우 중요하다
- 가중치의 초깃값을 Xavier초깃값과 He초깃값이 효과적이다
- 배치 정규화를 이용하면 학습을 빠르게 진행할 수 있으며 초깃값에 영향을 덜 받게 된다.
- 오버피팅을 억제하는 정규화 기술로는 가중치 감소와 드롭아웃이 있다.
- 하이퍼파라미터 값 탐색을 최적값이 존재할 법한 범위를 점차 좁히면서 하는 것이 효과적이다.