삶은 계란

딥러닝 기초 부수기 - 학습 관련 기술들 본문

딥러닝

딥러닝 기초 부수기 - 학습 관련 기술들

삶과계란사이 2022. 3. 14. 20:15

딥러닝 기초 부수기 - 학습 관련 기술들

본 게시글은 한빛미디어 출판사의 '밑바닥부터 시작하는 딥러닝(저자: 사이토 고키)' 도서 내용을 바탕으로 작성하였습니다.

 

1. 서론

신경망 학습에서는 가중치 매개변수의 최적값을 찾는 작업을 수행한다. 이러한 작업에는 여러 최적화 방법이 사용될 수 있다. 이번 게시글에서는 학습과 관련된 기술들에 대해 정리해볼 것이다.

 

2. 매개변수 갱신

매개변수의 최적값을 찾는 것을 최적화(optimization)라고 한다. 가중치 매개변수의 최적값을 탐색하는 다양한 최적화 기법이 존재한다. 최적화 기법마다 장점과 단점이 존재하므로, 풀고자 하는 문제에 맞게 사용하는 것이 좋다.

(1) 확률적 경사 하강법(SGD)

매개변수의 기울기를 구해 기울어진 방향으로 매개변수 값을 갱신하는 기법이다. 확률적 경사 하강법은 최솟값인 (0, 0)까지 지그재그로 이동하므로 비효율적이라는 단점이 있다. 수식은 아래와 같다.

확률적 경사 하강법(SGD)에 의한 최적화 갱신 경로

# 확률적 경사 하강법(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]

 

(2) 모멘텀(Momentum)

기울기 방향으로 힘을 받아 물체가 가속한다는 물리 법칙을 따르는 최적화 기법이다. 수식은 아래와 같다.

아래 그림은 모멘텀의 이미지이다. 공이 곡면(기울기)을 따라 구르듯 움직인다.

모멘텀(Momentum)에 의한 최적화 갱신 경로

# 모멘텀(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] - self.lr * grads[key] 
            params[key] += self.v[key]

 

(3) AdaGrad

개별 매개변수에 적응적으로 학습률을 조정하면서 학습을 진행하는 최적화 기법이다. 각각의 매개변수에 맞춤형 값을 생성한다. 학습률을 정하는 기술로 학습률 감소(learning rate decay)가 있는데, 학습을 진행하면서 학습률을 점차 줄여가는 방법이다. AdaGrad의 수식은 아래와 같다.

AdaGrad에 의한 최적화 갱신 경로

# 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)   # 1e-7: 0으로 나누는 사태 방지

 

(4) Adam

모멘텀(Momentum)과 AdaGrad를 융합한 기법이다. 속도 개념과 학습률 감소를 합쳤다.

Adam에 의한 최적화 갱신 경로

 

3. 가중치의 초기값

(1) 초기값이 0인 경우와 균일한 값인 경우

가중치의 초기값을 0으로 설정하거나 균일한 값으로 설정하면 학습이 올바르게 이루어지지 않는다. 오차역전파법에서 모든 가중치의 값이 똑같이 갱신되기 때문이다. 가중치가 고르게 되어버리는 상황을 막기 위해서 초기값을 무작위로 설정하는 것이 중요하다.

 

(2) 은닉층의 활성화 값 분포

아래 코드는 각 층의 뉴런이 100개씩인 5층 신경망을 구현한 코드이다. 활성화 함수로는 Sigmoid를 사용하였다.

# 은닉층의 활성화값 분포 실험

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))
    
x = np.random.randn(1000, 100)   # 1000개의 데이터
node_num = 100                   # 각 은닉층의 뉴런 수
hidden_layer_size = 5            # 은닉층이 5개
activations = {}                 # 이곳에 활성화 결과를 저장

for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i-1]

    w = np.random.randn(node_num, node_num) * 1   # 1 의미: 가중치 초기값 설정. 숫자를 변경하여 초기값 설정 가능!
    a = np.dot(x, w)
    z = sigmoid(a)
    activations[i] = z

# 히스토그램 그리기
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()

첫 번째로, 가중치의 표준편차를 1로 설정한 코드의 실행결과는 아래와 같다.

활성화 값들이 0과 1에 치우쳐 분포해있는 것을 확인할 수 있다. 이렇게 되면 역전파의 기울기 값이 점점 작아지다가 사라지는 문제가 발생한다. 즉 기울기 소실(gradient vanishing)이 발생한다. 두 번째로, 가중치의 표준편차를 0.01로 설정하고 실행한 결과는 아래와 같다.

활성화 값들이 0.5 부근에 집중적으로 분포해있는 것을 확인할 수 있다. 이렇게 되면 다수의 뉴런이 거의 값은 값을 출력하여 표현력이 제한된다는 문제가 발생한다.

 

(3) Xavier 초기값

앞 계층의 노드가 n개라면 표준편차가 1/√n인 분포를 사용하는 Xavier 초기값을 가중치 초기값으로 설정하여 실행한 결과는 아래와 같다.

활성화 값들의 형태가 층이 깊어지면서 다소 일그러지는 것을 확인할 수 있다. 하지만 앞선 방식보다 활성화 값들이 넓게 분포하고 있다. Xavier 초기값을 사용하면 Sigmoid 함수의 표현력도 제한받지 않고 학습이 효율적으로 이루어질 것이다.

 

(4) He 초기값

ReLU 함수에 특화된 가중치 초기값도 존재한다. 앞 계층의 노드가 n개라면 표준편차가 √(2/n)인 분포를 사용하는 He 초기값을 가중치 초기값으로 설정하여 실행한 결과는 아래와 같다.

활성화 값들이 상대적으로 모든 층에 균일하게 분포해있는 것을 확인할 수 있다.

 

4. 배치 정규화

배치 정규화 알고리즘은 각 층이 활성화를 적당히 퍼뜨리도록 강제하는 것이다. 배치 정규화가 주목받는 이유는 학습 속도를 개선하고, 초기값에 크게 의존하지 않으며, 오버피팅을 억제(드롭아웃 필요성 감소)할 수 있기 때문이다.

배치 정규화를 사용한 신경망의 예
가중치 초기값에 따른 배치 정규화의 효과. 파란 실선이 배치 정규화 적용한 경우.

 

5. 바른 학습을 위한 기법

(1) 오버피팅

신경망이 훈련 데이터에만 지나치게 적응되어 그 외 데이터에는 제대로 대응하지 못하는 상태를 오버피팅이라고 한다. 오버피팅은 주로 매개변수가 많고 표현력이 높은 모델이나 훈련 데이터가 적은 경우에 발생한다.

 

(2) 가중치 감소

오버피팅을 억제하는 방법으로 가중치 감소(weight decay)가 있다. 가중치 감소는 학습 과정에서 큰 가중치에 대해서는 그에 상응하는 큰 페널티를 부과하는 방법이다. 가중치가 W라면 손실 함숫값에 (λw^2)/2를 더한다. λ는 정규화 세기를 조절하는 하이퍼 파라미터로, 이 값이 클수록 큰 가중치에 대한 페널티가 커진다. 원래 오버피팅은 가중치 매개변수 값이 커서 발생하는데, 이 방법을 사용하여 가중치가 커지는 것을 억제할 수 있다. 가중치 감소를 적용한 훈련 데이터와 시험 데이터에 대한 정확도 그래프는 아래 그림과 같다.

하지만 가중치 감소는 복잡한 신경망 모델에서는 대응하기 어렵다는 단점이 존재한다. 이를 보완하기 위해 드롭아웃(Dropout)이 등장하였다.

 

(3) 드롭아웃

드롭아웃(Dropout)은 신경망의 훈련 때 은닉층의 뉴런을 무작위로 골라 삭제하면서 학습하는 방법이다. 시험 때는 모든 뉴런에 신호를 전달하는데, 훈련 때 삭제 안 한 비율을 곱하여 출력한다.

# 드롭아웃

class Dropout:
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio   # 드롭아웃 비율
        self.mask = None
        
    def forward(self, x, train_flg=True):
        if train_flg:
            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

위 그래프는 드롭아웃을 적용한 신경망의 정확도 그래프이다. 훈련 데이터와 시험 데이터에 대한 정확도 차이가 얼마 나지 않는 것을 알 수 있다.

 

6. 적절한 하이퍼 파라미터 값 찾기

(1) 검증 데이터

검증 데이터는 하이퍼 파라미터 성능 평가로 이용하는 데이터이다. 성능 평가 시 시험 데이터를 사용하면 안 되는데, 하이퍼 파라미터 값이 시험 데이터에 오버피팅 되기 때문이다. 하이퍼 파라미터는 뉴런 수, 배치 크기, 학습률, 가중치 감소처럼 개발자가 직접 설정해야 하는 값이다.

 

(2) 하이퍼 파라미터 최적화

  1. 하이퍼 파라미터 값의 범위 설정
  2. 설정된 범위에서 하이퍼 파라미터 값을 무작위 추출
  3. 1단계에서 샘플링한 하이퍼 파라미터 값을 사용하여 학습 후, 검증 데이터로 정확도 평가(에폭은 작게 설정)
  4. 1단계와 2단계를 특정 횟수 반복, 정확도 결과를 보고 하이퍼 파라미터 범위를 좁힌다!

위 그림은 MNIST 데이터셋을 사용하여 하이퍼 파라미터를 최적화하고, 정확도 그래프를 작성한 것이다. 파란 실선은 검증 데이터에 대한 정확도이고, 주황 점선은 훈련 데이터에 대한 정확도이다. 정확도가 높은 순서로 나열한 모습이다.

 

 

Comments