본문 바로가기
Data Science/문과생도 이해하는 딥러닝

문과생도 이해하는 딥러닝 (8) - 신경망 학습 최적화

by 싸코 2018. 1. 7.


2017/09/27 - 문과생도 이해하는 딥러닝 (1) - 퍼셉트론 Perceptron

2017/10/18 - 문과생도 이해하는 딥러닝 (2) - 신경망 Neural Network

2017/10/25 - 문과생도 이해하는 딥러닝 (3) - 오차 역전파, 경사하강법

2017/12/24 - 문과생도 이해하는 딥러닝 (4) - 신경망구현, 활성화함수, 배치

2017/12/26 - 문과생도 이해하는 딥러닝 (5) - 신경망 학습 실습

2018/01/05 - 문과생도 이해하는 딥러닝 (6) - 오차역전파법 실습 1

2018/01/05 - 문과생도 이해하는 딥러닝 (7) - 오차역전파법 실습 2




지금까지 신경망이 이론적으로 어떻게 구성되고 이를 코드로 어떻게 구현하는지 살펴보았다. 딥러닝은 깊은 신경망 계층을 말하며 수 많은 모듈의 조합으로 이루어진 것이라고 볼 수 있다. 따라서 모듈과 함수가 많은 만큼 각각에 대한 하이퍼파라미터(hyper-parameter)가 많다. 하이퍼파라미터를 어떻게 설정하느냐에 따라서 학습의 결과가 천차만별로 달라질 수 있다.



딥러닝 모델 학습에서 많이들 중요성을 간과하는 부분이 이 최적화 부분이다. 신경망을 생성하면서 이 최적화 문제에 대해서 끊임 없이 고민해야 한다. 이는 Input으로 넣는 데이터와의 직접적인 연결성도 있으며 하이퍼파라미터에 따른 모델의 변화를 어느정도 이해하고 예측하여 튜닝 과정을 할 수 있어야 하기 때문이다. 눈가리고 모래에서 진주찾는 식으로 적절한 하이퍼파라미터 값을 찾는 것은 좋지 못한 방법이다.



신경망 학습 최적화 Optimization

문과생도 이해하는 딥러닝 (8)


신경망 모델의 학습과 그 결과에 따른 손실함수의 값을 최소화하는 방향으로 하이퍼파라미터의 값을 찾는 것이 최적화의 목표라고 할 수 있다. 하이퍼파라미터 매개변수의 수 n 만큼 가능한 매개변수의 조합이 n x n 만큼 증가하므로 이중에서 최적의 조합을 찾는 것은 쉽지 않다. 다양한 방법으로 자동으로 찾고자 하지만 여전히 이는 연구자의 주관이나 직관, 경험 등에 의존하고 있다.


최적의 가중치 값을 구하기 위해서 앞에서는 미분을 통해 기울기를 구하여 가중치 값을 갱신하는 방법인 확률적 경사하강법(Stochastic Gradient Descent; SGD) 방법을 사용하였다. 이는 무작정 가중치 값을 랜덤으로 콕콕콕 찍어서 찾는 방식보다는 훨씬 스마트한 방법이다.


확률적 경사하강법 이외에도 다양한 최적화 기법을 통해 최적의 하이퍼파라미터 조합을 찾을 수 있다.



산내려오는작은오솔길잘찾기(Optimizer)의발달계보
SGD
Momentum
NAG
Adagrad
RMSProp
AdaDelta
Adam
Nadam
스텝계산해서움직인후,

아까내려오던관성방향또가자
일단관성방향먼저움직이고...

출처: 하용호, 자습해도 모르겠던 딥러닝, 머리속에 인스톨 시켜드립니다



1. 확률적 경사하강법 복습


확률적 경사하강법 Stochastic Gradient Descent

SGD는 손실함수의 기울기를 계산하여서 이 기울기 값에 학습률(Learning Rate)를 계산하여 이 결과 값으로 기존의 가중치 값을 갱신한다.

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]

위에서 update 함수를 통해 모듈화되어 가중치를 갱신할 수 있다.



경사하강법의 단점

경사하강법은 무작정 기울어진 방향으로 이동하는 방식이기 때문에 탐색경로가 비효율적이어서 한참을 탐색하게 된다. 또한 SGD는 방향에 따라서 기울기 값이 달라지는 경우에 적합하지 않은데 최소값의 방향으로만 움직이기 때문에 본래의 최저점으로 기울기의 방향이 좀처럼 향하지 않게 되고 다른 방향을 가리키게 되어 비효율이게 되기도 한다.


2. 모멘텀 Momentum

모멘텀은 '운동량'을 의미한다. 기울기에서 속도의 개념이 추가된 것으로 고등학교 물리 시간을 떠올려보면 자세히는 아니지만 지상의 마찰력 때문에 물체의 이동속도가 점점 감소한다고 배웠던 기억이 어렴풋이 기억이 난다. 속도가 크게 나올수록 기울기가 크게 업데이트 되어 확률적 경사하강법이 가지는 단점을 보완할 수 있다.

Momentum은 마찰력/공기저항 같은 것에 해당하며 기울기 업데이트 시 이 폭을 조절하는 역할을 한다. 이에 따라 속도 velocity가 변화한다.


모멘텀 Momentum

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] 


3. AdaGrad

신경망 학습에서의 학습률 learning rate의 값은 일종의 보폭으로 생각할 수 있는데 한 번 갱신하는 가중치의 값을 양을 결정한다. 학습률을 너무 작게하면 보폭이 너무 작아서 많은 걸음을 걸어야 하므로 학습 시간을 아주 길게 해야 한다. 반대로 너무 크게 하면 최적의 점을 계속 지나치게 된다.

학습률을 정하는 방법으로 학습률 감소learning rate decay가 있다고 한다. 학습을 진행하면서 점차 학습률을 줄여나가는 방법이다. 하지만 학습이 계속되면서 학습률이 0에 가까워져서 학습이 진행이 안되는 문제가 발생한다.

AdaGrad는 과거의 기울기 값을 제곱해서 계속 더하는 식으로 학습률을 낮추는데 학습이 진행될수록 제곱의 값으로 학습의 정도가 크게 떨어진다. 
이를 개선하기 위해서 RMSProp이 사용된다. RMSProp은 과거의 모든 기울기를 균일하게 더하지 않고 새로운 기울기의 정보만 반영하도록 해서 학습률이 크게 떨어져 0에 가까워지는 것을 방지하는 방법이다.


AdaGrad

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)






4. Adam

잘 모르겠으면 일단 Adam을 사용하라는 말이 있다. 앞에서 언급했던 Momentum과 AdaGrad를 섞은 기법이라고 보면 된다.
따라서 하이퍼파라미터도 그만큼 많다. 모멘텀에서 사용하는 계수와 학습률에 대한 계수가 사용된다.

학습률을 줄여나가고 속도를 계산하여 학습의 갱신강도를 적응적으로 조정해나가는 방법이다.






5. MNIST로 학습 최적화 진도

from scratch.dataset.mnist import load_mnist
from scratch.common.util import smooth_curve
from scratch.common.multi_layer_net import MultiLayerNet
from scratch.common.optimizer import *
import matplotlib.pyplot as plt

# 0. MNIST 데이터 읽기==========
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)

train_size = x_train.shape[0]
batch_size = 128
max_iterations = 2000


# 1. 실험용 설정==========
optimizers = {}
optimizers['SGD'] = SGD()
optimizers['Momentum'] = Momentum()
optimizers['AdaGrad'] = AdaGrad()
optimizers['Adam'] = Adam()
#optimizers['RMSprop'] = RMSprop()

networks = {}
train_loss = {}
for key in optimizers.keys():
    networks[key] = MultiLayerNet(
        input_size=784, hidden_size_list=[100, 100, 100, 100],
        output_size=10)
    train_loss[key] = []    


# 2. 훈련 시작==========
for i in range(max_iterations):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    for key in optimizers.keys():
        grads = networks[key].gradient(x_batch, t_batch)
        optimizers[key].update(networks[key].params, grads)
    
        loss = networks[key].loss(x_batch, t_batch)
        train_loss[key].append(loss)
    
    if i % 100 == 0:
        print( "===========" + "iteration:" + str(i) + "===========")
        for key in optimizers.keys():
            loss = networks[key].loss(x_batch, t_batch)
            print(key + ":" + str(loss))


# 3. 그래프 그리기==========
markers = {"SGD": "o", "Momentum": "x", "AdaGrad": "s", "Adam": "D"}
x = np.arange(max_iterations)
plt.figure(figsize=(20,8))
for key in optimizers.keys():
    plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=100, label=key)
plt.xlabel("iterations")
plt.ylabel("loss")
plt.ylim(0, 1)
plt.legend()
plt.show()

===========iteration:0=========== SGD:2.37670101396 Momentum:2.33816339389 AdaGrad:2.12533763909 Adam:2.15575092649 ===========iteration:1900=========== SGD:0.154444681428 Momentum:0.020718003123 AdaGrad:0.0183070478127 Adam:0.0222197490211







참고

http://seamless.tistory.com/38

https://www.slideshare.net/yongho/ss-79607172

댓글