학습이 무엇인지, 어떻게 하는지 배웠으니 이제 학습을 더 다듬을 차례이다.
이번 단원에서는 신경망 학습의 효율, 정확도를 높이기 위한 기술들을 알아본다.
6.1 매개변수 갱신
-Neural Network의 손실함수를 낮추기 위해 우리는 parameter를 갱신했다.
이렇게 parameter의 최적값을 찾는 과정을 최적화(optimization)이라고 부른다.
- 이전 단원에서 gradient를 갱신했던 방법은 확률적 경사 하강법(Stochastic Gradient Descent)라 부른다.
그렇다면 SGD 특징과, 이 외의 다른 갱신 방법엔 어떤 것이 있을까?
6.1.2 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]
--> 최적화를 담당하는 optimizer 클래스를 따로 구현해서 모듈화 할 수 있다.
# 예시 pseudo code
network = TwoLayerNet(...)
optimizer = SGD()
for i in range(10000):
..
..
grads = network.gradient(x_batch, t_batch)
params = network.params
optimizer.update(params, grads)
...
SGD의 단점

비등방성 함수(anisotropy, 방향에 따라 성질(기울기)이 달라지는 함수)의 경우 비효율적이다.
어떤 방향에서든 항상 gradient가 최솟값을 향하면 좋겠지만,
각 gradient가 최솟값이 아니라 다른 방향을 가리킬 경우 갱신이 비효율적으로 이루어질 수 있다.
아래와 같은 예시를 보자.


위 함수의 그래프를 보면 등고선이 x축 방향으로 늘인 타원 형태로 되어있다.
즉, x축 방향으로는 완만하고 y축 방향으로는 가파른 모양을 하고있다.

실제로 그라디언트의 그래프를 보면, 최솟값인 (0,0) 지점으로 그라디언트가 향하지 않는다는 사실을 알 수 있다.
6.1.4 모멘텀

여기서의 v는 속도(velocity)를 의미한다.
learning rate만큼 기울기 방향으로 가속을 하고, 알파항은 기존에 진행하던 방향에 대해 관성을 준다.
물체가 아무런 힘을 받지 않을 때에도 알파 값에 의해 속도가 서서히 하강된다.
(책에서는 물리에서의 마찰력, 공기 저항등을 나타내는 마찰 계수의 역할과 비슷하다고 설명하고 있다.)
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, bal 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]

--> SGD에 비해 훨씬 지그재그의 정도가 적고, 매끄러운 것을 볼 수 있다.
6.1.5 AdaGrad
학습률 감소(learning rate decay)
신경망 학습에서 학습이 진행됨에 따라 학습률을 서서히 줄여가는 방법.
Feature가 sparse하다면 더 큰 learning rate를, dense하다면 더 작은 learning rate가 필요하다.
왜냐하면 sparse feature의 경우 occurrence의 빈도가 더 낮기 때문이다.
AdaGrad는 각각의 parameter에 맞춰 adaptive하게 학습률을 조정하는 방법이다.

여기서 첫번째 식의 기호는 행렬의 원소별 곱셈을 의미한다.(즉, 기존 기울기 값을 제곱하여 합한다.)
이 식에 의하면 갱신이 많이 진행된 원소의 learning rate를 더 작게 조정해준다!
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 param.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)
#여기서 h가 0일 때를 방지해 미량의 값을 더해준다.

처음에 비해 갱신 정도가 점차 작아지는 것을 볼 수 있다.
이는 항상 감소하는데, 이전 단계들의 모든 gradient square의 값의 합은 항상 증가하기 때문이다.
따라서 특정 순간에 model은 learning을 멈추게 된다.
이러한 문제를 해결하기 위한 Adadelta라는 optimizer도 존재한다.
6.1.6 Adam
momentum의 개념과, Adadelta의 adaptive learning rate 개념을 모두 적용한 optimizer이다.
- Computationally efficient하며, 구현이 쉽다.
- Hyperparameter가 매우 직관적으로 해석 가능하며 튜닝이 적게 든다.
- noisy, sparse gradient를 가진 문제에 적합하다.


어떤 방법을 사용해야 할까? 실제로는 데이터셋마다 다른 갱신 방법을 사용해야 한다.
일반적으로는 SGD보다 다른 기법이 빠르게 학습하고, 때로는 최종 정확도도 높게 나타난다.
6.2 가중치의 초깃값
신경망 학습에서 가중치의 초깃값은 매우매우매우 중요한 요소이다.
실제로 초깃값에 따라 학습의 결과가 달라지는 일이 자주 존재하기 때문이다.
초깃값이 어떻게 학습에 영향을 줄지 살펴보자.
가중치 감소 Weight Decay
weight 매개변수의 값을 작게하여, 오버피팅이 일어나지 않도록 하는 방법
6.2.1 초깃값을 0으로
초깃값을 0으로(혹은 모든 뉴런에 균일한 값으로) 했을 때의 문제점
한 층에서 다음 층으로 거쳐갈 때 뉴런에 모두 같은 값이 전달된다.
이 때, back propagation을 수행한다면 가중치의 갱신 또한 모두 똑같이 이루어진다.
6.2.2 Hidden Layer(은닉층)의 활성화 값 분포
* 활성화 값 : Activation function의 출력 데이터
가중치의 초깃값에 따라, hidden layer의 활성화 값 변화를 살펴보자.
활성화 함수로 시그모이드를 사용하고, 5층으로 이루어진 Neural Network에서
입력 데이터를 무작위로 주었을 때 각 층의 활성화 값 분포를 살펴볼 것이다.
처음 플롯에는 표준편차 1인 정규 분포를 이용한다.
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1+ np.exp(-x))
#데이터가 1000 개, feature가 100개
x = np.random.randn(1000, 100)
node_num = 100 #각 은닉층의 노드 개수
hidden_layer_size = 5 #은닉층의 개수
activations = {}
for i in range(hidden_layer_size):
if (i != 0):
x = activations[i-1]
# 표준편차가 1인 정규분포.
# 다른 정규분포를 갖고 싶다면 rand * sigma + mean 형태로 사용가능하다.
w = np.random.randn(node_num, node_num) * 1
a = np.dot(x, w)
z = sigmoid(a)
activations[i] = z
for index ,act in activations.items():
#subplot(nrows, ncols, 서브플롯의 위치)
plt.subplot(1, len(activations), index+1)
plt.title(str(index+1)+"-layer")
plt.hist(act.flatten(), 30, range=(0,1))
plt.show()

기울기 소실(Gradient Vanishing)
시그모이드 함수의 경우 출력값(활성화값)이 0 또는 1에 가까워 질수록
그 미분값(gradient)가 0에 가까워진다.
따라서 데이터의 분포가 0 또는 1에 가까워지면
Back propagation에서 gradient값이 점점 사라지게 된다.
특히 Layer가 많아질수록 이는 더 심각한 문제가 될 것이다.
이번에는 표준편차를 0.01로 바꾸어서 plot해보자.

이 경우 대부분의 뉴런이 같은 값을 출력하게 된다.
따라서 표현력을 제한한다는 문제점이 생기게 된다.
Xavier 초깃값
일반적인 딥러닝 프레임워크들이 이용하고 있는 방식.
각 층의 활성화 값이 한쪽으로 치우치는 것을 방지하기 위해, 앞 계층의 노드 수를 반영한다!
*원래 논문은 출력 노드(다음 층의 노드) 수도 고려하지만, 입력 노드만으로 단순화해서 사용하는 경우도 많다.

즉, 앞 계층의 노드 수가 많을 수록 초깃값의 표준편차를 작게 설정한다.(좁게 퍼지도록 한다.)

앞의 예시들보다 분포가 고르게 되었음을 확인할 수 있다.
6.2.3 ReLU를 사용할 때의 가중치 초깃값
앞에서 본 시그모이드 혹은 하이퍼탄젠트(tanh)함수는 좌우 대칭이다. 즉, 중앙부근을 선형으로 볼 수 있다.
그러나 ReLU의 경우는 아니기 때문에 이 함수에 특화된 초깃값을 이용해야 한다.
He 초깃값
앞 계층의 노드 개수가 n개 일 때,

를 표준편차로 갖는다.


Xavier 초깃값의 경우 학습이 진행될 수록, 값들의 치우침이 커진다.
반면 He 초깃값을 사용했을 때 층이 깊어져도 값들이 고르게 나오므로 역전파시 기울기 소실 문제를 예방할 수 있다.
결론적으로, 우리는 곡선형 (tanh or sigmoid ...)함수에는 Xavier를,
ReLU함수에는 He를 이용해 초깃값을 설정할 것이다\ ∵ /
6.3 배치 정규화(Batch Normalization)
배치 정규화란?
각 미니배치를 단위로 특정 평균과 분산을 이용하여 정규화 시키는 것.
이를 위해 신경망의 중간에 배치 정규화 계층을 삽입한다.

배치 정규화의 장점
- 많은 경우에서 학습을 빠르게 진행할 수 있음
- 초깃값에 의존하지 않음
- 오버피팅을 억제함 (드롭아웃의 필요성 감소)
6.3.1 배치 정규화 알고리즘
배치 정규화는 미니 배치의 평균이 0, 분산이 1이 되도록 정규화 한다.
각 미니 배치에 m개의 입력 데이터가 있다고 가정하자.
이 때 m개의 데이터의 평균, 분산을 구한후 정규화 한다.

정규화만 하는 것이 아니라, 정규화 된 데이터에 확대(scale)와 이동(shift) 변환을 수행한다.

여기에서 감마가 변환, 베타가 이동을 의미한다.
초기값은 각각 1, 0으로 설정(원본 그대로)하고 학습하면서 조정한다!

6.4 오버피팅(Overfitting)
오버피팅이란 neural network가 특정 데이터에만 맞춰 학습되어
다른 데이터에는 제대로 성능을 내지 못하는 상태를 말한다.
오버피팅을 억제하는 것은 기계학습에서 매우 중요한 기술이다.
오버피팅의 주 원인
-매개변수가 많고, 표현력이 높은 모델
-훈련 데이터가 적은 상황
6.4.2 가중치 감소(Weight Decay) / L2 Regularization
학습 과정에서 큰 가중치에 패널티를 부과하여 오버피팅을 억제하는 방법.
f(x)=w⊤x 라는 선형함수가 존재할 때, W라는 가중치가 작을 수록 함수가 간단하다고 간주한다.
( 이 때, f = 0을 가장 간단한 함수로 간주할 수 있다.)
따라서 우리는 복잡한 함수에 패널티를 주기 위해, W의 L2 norm값을 손실함수에 더한다.
L2 norm을 사용했기 때문에 이를 L2 Regularization이라고 부르기도 한다.
*이를 사용하는 회귀 모델을 Ridge Regression이라 하기도 한다.

위 항을 loss 함수에 더해 큰 가중치에 더 큰 패널티를 부과한다.
Back propagation시 위 항이 미분되어, weight의 특정 배수만큼 가중치의 감소가 이뤄지는 것을 알 수 있다.

첫번째 항을 보면, regularization에 의해 regularization term의 미분 값이 w에서 감소되었다.
6.4.3 드롭아웃(Dropout)
신경망 모델이 복잡해지면, 가중치 감소만으로 오버피팅을 억제하기 힘들어진다.
이 때 드롭아웃이라는 기법을 사용한다.
드롭아웃이란 신경망 학습시 hidden layer 중 임의의 뉴런을 비활성화 시키며 학습하는 방법이다.
단, test할 때는 비활성화를 해제해야 하고, 삭제하지 않은 비율을 각 뉴런의 출력에 곱해서 출력한다.
class Dropout:
def __init__(self, dropout_rat=0.5):
self.dropout_rat = dropout_rat
self.mask=None
def forward(self, x, train_flg = True):
if train_flg == True :
self.mask= np.random.rand(*x.shape) > self.dropout_rat #여기서 스타는 튜플형태
retrun x * self.mask
else:
return x*(1.0 - self.dropout_rat)
def backward(self, bin):
return bin * self.mask
6.5 Hyperparameter Tuning
각 층의 뉴런수, 배치의 크기, 매개변수 갱신 시 학습률, 가중치 감소 등은 하이퍼 파라미터이다.
특히, learning rate(학습률)은 딥러닝에서 가장 중요한 파라미터 중 하나로,
학습률이 너무 작으면 업데이트가 너무 느리고,
학습률이 너무 크게 되면 발산하는 형태의 행동을 나타내는 등 많은 문제가 생길 수 있다.
하이퍼 파라미터는 데이터를 통해 이끌어내는 매개변수가 아니기 때문에,
많은 시행착오가 필요하다. 따라서 하이퍼파라미터를 효율적으로 탐색하는 것이 중요하다.
6.5.1 검증 데이터(Validation Data)
하이퍼 파라미터를 대상으로 모델의 성능을 평가할 때는 시험 데이터를 사용하면 안된다.
하이퍼 파라미터가 시험 데이터에 오버피팅 될 위험성이 있기 때문이다!
따라서 따로 하이퍼 파라미터 튜닝을 위해 사용하는 데이터를 검증 데이터(Validation Data)라고 부른다.
정리하자면
Train 훈련 데이터 - 매개변수 학습
Validate 검증 데이터 - 하이퍼 파라미터 성능 평가
Test 시험 데이터 - 범용 성능 평가
데이터를 위와 같은 세 가지 카테고리로 나눌 수 있다.
MNIST 데이터 셋 처럼 훈련/시험 데이터로만 나누어져 있는 데이터셋의 경우 가장 간단하게는
훈련 데이터의 일부 (약 20% 정도)를 검증 데이터로 분리해 사용할 수 있다.
x_train, t_train = shuffle_dataset(x_train, t_train) #np.random.shuffle 이용해 섞음
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_val = x_train[:validation_num]
t_val = t_train[:validatoin_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]
6.5.2 하이퍼 파라미터 최적화(Random Search)
하이퍼 파라미터 최적화의 경우,
Grid search 같은 순차적 규칙적 탐색보다 랜덤 샘플링(Random search)이 좋은 성능을 낸다.
--> 하이퍼 파라미터 마다 정확도에 미치는 영향력이 다르기 때문이당
따라서, 하이퍼 파라미터 최적화는 대략적인 범위를 정하는 것 부터 시작된다.
(일반적으로 로그 스케일 Log scale로 지정하며, 딥러닝 학습시간이 길다는 점을 고려해 에폭을 작게 한다.)
1단계
하이퍼 파라미터의 범위를 설정한다.
2단계
정한 범위 내에서 값을 무작위로 추출한다.
3단계
추출한 값을 사용하여 학습, 데이터의 정확도를 평가한다.
4단계
2-3단계를 특정 횟수만큼 반복하고, 정확도를 통해 하이퍼 파라미터의 범위를 좁혀나간다.
다만, grid search나 random search 모두 일정 부분 사람의 직관이 필요하고(구간 선정 등),
이전에 수행했던 성능 결과를 반영하지 못한다는 단점이 있다.
--> 이를 보완한, 목적 함수를 상정하여 최적화를 시키는 Bayesian optimization이 존재한다.
다만, 이 챕터에서 일단 우리는 Random search를 구현해볼 것이다.
6.5.3 하이퍼 파라미터 최적화 구현
-하이퍼 파라미터의 범위는 앞서 말했 듯이, 로그 스케일 범위 내에서 무작위(Random)으로 추출한다.
-파이썬 함수 중, 균등분포에서 무작위로 표본을 추출하는 함수인 np.random.uniform을 사용한다.
-여기서의 점선은 훈련 데이터, 실선은 검증 데이터에 대한 정확도를 의미한다.
# 랜덤으로 하이퍼 파라미터를 탐색한다.
optimization_trial = 100
results_val = {}
results_train = {}
for _ in range(optimization_trial):
# 탐색 범위 지정
weight_decay = 10 ** np.random.uniform(-8,-4)
lr = 10 ** np.random.uniform(-6, -2)
val_acc_list, train_acc_list = __train(lr, weight_decay)
print("val acc : " + str(val_acc_list[-1]) + "| lr:" + str(lr) + ", weight decay: "+ str(weight_decay))
key = "lr:" + str(lr) + ",weight decay:" + str(weight_decay)
results_val[key] = val_acc_list
results_train[key] = train_acc_list

Best-1(val acc:0.79) | lr:0.007972836660660744,weight decay:4.941752862596633e-07
Best-2(val acc:0.78) | lr:0.007339635875087776,weight decay:2.3906811599569125e-08
Best-3(val acc:0.76) | lr:0.006338407237198491,weight decay:6.409995571863052e-05
Best-4(val acc:0.74) | lr:0.006954720572976707,weight decay:1.2061521095308961e-08
Best-5(val acc:0.73) | lr:0.005814782231766573,weight decay:1.8045671827647365e-05
'Deep Learning' 카테고리의 다른 글
| CH 8 - 딥러닝(Deep Learning) (WIP) (2) | 2023.10.15 |
|---|---|
| CH7 - CNN(Convolutional Neural Network) (0) | 2022.08.12 |
| CH5 - Back Propagation (0) | 2022.07.13 |
| CH4 - 신경망 학습(Training Neural Network) (0) | 2022.07.06 |
| CH3 - 신경망(Neural Network) (0) | 2022.06.30 |