밑바닥부터 시작하는 딥러닝 <chapter 6 학습 관련 기술들> 01

유현지·2021년 7월 21일
0

ZeroDeep

목록 보기
2/3
post-thumbnail

chapter 6 학습 관련 기술들


<이번 장에서 배울 내용>

  • 매개변수 갱신 방법에는 확률적 경사 하강법(SGD) 외에도 모멘텀, AdaGrad, Adam 등이 있다.
  • 가중치 초기값을 정하는 방법은 올바른 학습을 하는데 매우 중요하다.
  • 가중치의 초기값으로는 'Xavier 초깃값'과 'He 초깃값'이 효과적이다.
  • 배치 정규화를 이용하면 학습을 빠르게 진행할 수 있으며, 초깃값에 영향을 덜 받게 된다.
  • 오버피팅을 억제하는 정규화 기술로는 가중치 감소와 드롭아웃이 있다.
  • 하이퍼파라미터 값 탐색은 최적 값이 존재할 법한 범위를 점차 좁히면서 하는 것이 효과적이다.

6.1 매개변수 갱신

신경망 학습의 목적은 손실 함수의 값을 가능한 낮추는 매개변수를 찾는 것
이는 매개변수의 최적값을 찾는 문제이며, 이러한 문제를 푸는 것을 최적화(optimization)라고 함
확률적 경사 하강법(SGD)에 대해 알아보며, SGD의 단점과 더불어 다른 최적화 기법을 알아보자

6.1.1 모험가 이야기

가장 깊고 낮은 골짜기를 찾는 모험가🤠
그에게는 2가지 제약 '지도를 보지 않는 것'과 '눈가리개로 앞을 보지 않는 것'을 지키며 모험을 해야만 한다.
어떻게 이 모험가는 깊은 곳을 찾을 수 있을까?
➡️ 앞을 볼 수 없지만 발로 느껴지는 기울기를 느껴 더 깊은 곳으로 가보자!

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


SGD의 수식:
W는 갱신할 가중치 매개변수고 학습률과 W에 대한 손실 함수의 기울기 값을 곱한 우변 값으로 좌변 값을 갱신하겠음(<-)
하기 코드로 표현

class SGD:
    def __init__(self, lr=0.01): # lr은 학습률
        self.lr = lr
        
    def update(self, params, grads): # params, grads은 가중치 매개변수와 기울기를 저장한 딕셔너리
        for key in params.key():
            params[key] -= self.lr * grads[key] # SGD 구현

SGD를 사용한 신경망 매개변수의 진행

network =Two LayerNet(...)
optimizer =SGD()

for i in range(10000):
    ...
    x_batch, t_batch = get_mini_batch(...) # 미니 배치
    grads = network.gradient(x_batch, t_batch)
    params = network.params
    optimizer.update(params, grads)
    ...

옵티마이저(optimizer)은 '최적화를 행하는 자'
클래스를 구분하여 구현할 시 기능을 모듈화하기 좋음
Lasagne: 다양한 최적화 기법 구현

6.1.3 SGD의 단점

SGD는 단순하고 구현도 쉽지만 문제에 따라 비효율적일 수 있다
f(x,y) = 1/20*x^2+y^2
위 함수는 밥그릇을 x축 방향으로 늘인 모습이고, 등고선은 x축 방향으로 늘인 타원모양임[p192]

함수의 기울기는 [그림6-2]임

y축 방향은 크고 x축 방향은 작은 특징이 있다
위 식의 최소값이 되는 장소는 (0,0)이지만 그림에서 보여주는 기울기 대부분은 (0,0)을 가리키지 않음

SGD는 비등방성 함수(방향에따라 성질이 달라지는 함수)에서는 탐색 경로가 비효율적임
또한 기울어진 방향이 본래 최솟값과 다른 방향을 가르키기 때문
위 SGD의 단점을 개선하는 방식을 확인해보자

6.1.4 모멘텀

모멘텀(momentum)은 '운동량'을 듯하는 단어로 물리와 관계있음

v <- αv - η*∂L/∂W
W <- W + v
여기서 W는 갱신할 가중치의 매개변수, ∂L/∂W는 W에 대한 손실함수의 기울기, η는 학습률
v는 물리에서 말하는 속도에 해당됨
α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]
    

모멘텀의 갱신경로는 공이 그릇 바닥을 구르듯 움직임
SGD와 비교시 지그재그 정도가 덜함

x축의 힘은 아주 작지만 방향은 변하지 않아서 한 방향으로 일정하게 가속
y축의 힘은 크지만 위아래로 번갈아 받아서 상충하여 속도가 안정적이지 않음

6.1.5 AdaGrad

신경망 학습에는 학습률 값이 중요함
값이 너무 작으면 학습시간이 길어지고 너무 크면 발산하여 학습이 제대로 이뤄지지 않음
학습률을 정하는 효과적인 기술로 학습률 감소(learning rate decay)가 있음
이는 학습을 진행하며 학습률을 점차 줄여가는 방법
매개변수 '전체'의 학습률 값을 일괄적으로 낮추는 것을 더욱 발전 시킨 것이 AdaGrad임
각각 매개변수에 맞춤형 값을 만들어줌

h <- h + ∂L/∂W ⊙ ∂L/∂W
W <- W + η * 1/h^^0.5 * ∂L/∂W

W는 가중치의 매개변수, ∂L/∂W은 W에 대한 손실 함수의 기울기, η는 학습률
h는 기존 기울기 값을 제곱하여 계속 더해줌(⊙는 행렬의 원소별 곱셈)
매개변수를 갱신할 때 1/루트(h)를 곱해 학습률을 조정함
많이 움직인 원소는 학습률이 낮아지며 학습률 감소가 원소별로 다르게 적용됨

❗NOTE
AdaGrad는 과거 기울기를 제곱하여 계속 더해가기에 학습을 진행할 수록 갱신 강도가 약해짐
실제로 무한히 학습하면 어느 순간 갱신량이 0이 되어 전혀 갱신되지 않음
이를 개선한 기법이 RMRProp이며 과거 기울기는 잊고 새로운 기울기 정보를 크게 반영함
이를 지수이동평균이라 하며, 과거 기울기 반영 규모를 기하급수적으로 감소하게 함

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(−4.281718172)이라는 작은 값을 더함
self.h[key]에 0이 담겨있어도 0으로 나눈 사태를 막아줌


최솟값을 향해 효율적으로 움직이는 것을 볼 수 있음

6.1.6 Adam

모멘텀과 AdaGrad를 융합하여 나온 기법 Adam
매개변수 공간을 효율적으로 탐색하며 하이퍼파라미터의 '편향보정'이 진행됨

모멘텀과 비슷한 패턴이나 공의 좌우흔들림이 적음
이는 학습 갱신 강도를 적응적으로 조정하여 얻는 혜택임

Adam은 학습률,모멘텀용 계수와 이차 모멘텀용 계수 3개의 하이퍼파라미터를 설정함

-> 깃허브에 코드가 없어 검색해봄 코드 출처

6.1.7 어느 갱신 방법을 이용할 것인가?


[그림6-8]과 같이 사용한 기법에 따라 갱신 경로가 다름
풀어야 할 문제에 따라, 하이퍼파라미터 설정에 따라 결과가 바뀜
각자 장단이 있어 잘푸는 문제와 서툰 문제가 있음
책에서는 SGD와 Adam을 주로 사용하나 상황을 고려하여 시도해보자

6.1.8 MNIST 데이터셋으로 본 갱신 방법 비교

손글씨 숫자인식을 대상으로 위 네가지 기법 비교
[그림6-9] 확인시 SGD의 학습 진도가 가장 느리며, AdaGrad가 조금 빠름
다만 학습률과 신경망 구조(층 깊이 등)에 따라 결과가 달라짐
일반적으로는 SGD보다는 다른 세 기법이 빠르게 학습하고 최종 정확도도 높게 나옴

6.2 가중치의 초깃값

6.2.1 초깃값을 0으로 하면?

가중치 감소(weight decay)란 가중치 매개변수 값이 작아지도록 학습하는 방법
가중치의 값을 모두 0으로 설정하면 학습이 올바르게 이뤄지지 않음
🙋왜 그럴까?
오차역전파법에서는 모든 가중치의 값이 똑같이 갱신되기 때문이다
(가중치를 균일한 값으로 설정하면 안된다)

오차역전파법에서 2층 신경망에서 첫번째와 두번째 층 가중치가 0이라고 가정한다
순전파 때 입력층 가중치가 0이기때문에 두번째층 뉴런에 모두 같은 값이 전달된다
두번째층 모든 뉴런에 같은 값이 입력된다는 것은 역전파 때 두번째 층 가중치가 모두 똑같이 갱신된다
그래서 같은 초깃값에서 시작하고 갱신을 거쳐도 여전히 같은 값을 유지하기에 가중치를 여러개 갖는 의미가 사라짐
가중치의 대칭적인 구조를 무너뜨리려면 초깃값을 무작위로 설정해야함

6.2.2 은닉층의 활성화값 분포

은닉층의 활성화값(활성화 함수의 출력 데이터)의 분포를 관찰
가중치 초깃값에 따른 은닉층 활성화 값 변화를 알아보자

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

    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()

각 뉴런이 100개인 층이 5개가 있음
1000개의 데이터를 정규분포로 무작위 생성하여 위 신경망에 흘림
활성화 함수는 시그모이드이며
각 층의 활성화 결과를 activations 변수에 저장
위에서는 표준편차가 1인 정규분포를 이용했는데 분포된 정도 (표준편차)를 바꿔가며
활성화 값들의 분포가 어떻게 변화하는 지 관찰하는 것이 실험의 목적임

[그림6-10] 가중치를 표준편차가 1인 정규분포로 초기화 할때 각 층 활성화 값 분포

  • 각 층의 값이 0과 1에 치우쳐져 분포됨
  • 위 경우 역전파의 기울기 값이 점점 작아지다가 사라지는 기울기 소실이 발생됨
  • 층을 깊게 하는 딥러닝에서는 기울기 소실은 심가간 문제가 될 수 있음
# w = np.random.randn(node_num, node_num) * 1
w = np.random.randn(node_num, node_num) * 0.01

위 코드의 가중치 표준편차를 0.01로 바꿔 같은 실험은 반복
(표준편차가 작을수록 평균값에서 변량들의 거리가 가깝다.)

[그림6-11] 가중치를 표준편차가 0.01인 정규분포로 초기화 할때 각 층 활성화 값 분포

  • 각 층의 값이 0.5 부근에 집중됨
  • 기울기 소실은 발생되지 않으나 활성화 값들이 치우쳐진 것은 표현력 관점에서 문제가 됨
  • 다수의 뉴런이 거의 같은 값들을 출력하여 뉴런을 여러개 둔 의미가 없어짐
  • 활성화 값들이 치우치면 표현력을 제한하는 관점의 문제가 발생

⚠️ WORNING
각 층의 활성화값은 적당히 고루 분포되어야함
층과 층 사이 적당하게 다양한 데이터가 흘러야 신경망 학습이 효율적으로 이뤄짐
반대로 치우친 데이터의 경우 기울기 소실이나 표현력 제한 문제에 빠져 학습이 잘 이뤄지지 않을 수 있음


Xavier(사비에르) 초깃값
일반적인 딥러닝 프레임 워크들이 표준적으로 사용하는 초깃값
사비에르 글로로트와 요수아 벤지오의 논문에서 권장하는 초깃값이며,
각층 활성화값들을 광범위하게 분포시키기 위해 가중치의 적절한 분포로 앞 계층의 노드가 n개라면 표준편차가 1/n^^0.5인 분포를 사용하라는 결론을 이끌어냄

Xavier 초깃값을 이용하면 앞 층 노드들이 많을수록 대상 노드의 초깃값으로 설정하는 가중치가 좁게 퍼짐

node_num = 100 # 앞 층의 노드 수
w = np.random.randn(node_num, node_num) / np.sqrt(node_num)

[그림6-13] 가중치의 초깃값으로 'Xavier 초깃값'을 이용할 때의 각 층의 활성화값 분포

  • 층이 깊어지며 형태가 일그러지나, 앞서 본 방식보다 넓게 분포됨
  • 데이터가 적당히 퍼져 있어 시그모이드 함수의 표현력에 제한 받지 않고 효율적으로 학습될 것이 기대됨

❗NOTE
[그림6-13]은 오른쪽으로 갈수록(각 층을 거칠때마다) 일그러짐
이는 sigmoid 함수 대신 tanh 함수(쌍곡선 함수)를 이용하면 개선됨
tanh 함수는 원점에서 대칭인 S 곡선이나 sigmoid 함수는 (x,y) = (0. 0.5)에서 대칭임
활성화 함수용으로는 대칭인 함수가 바람직함

6.2.3 ReLU를 사용할 때의 가중치 초깃값

Xavier 초깃값은 활성화 함수가 선형인 것을 전제로 이끈 결과
sigmoid 함수와 tanh 함수는 좌우 대칭이라 중앙 부근이 선형인 함수임
그래서 Xavier 초깃값이 적당함

ReLU를 이용할 때는 ReLU에 특화된 초깃값인 He 초깃값 이용하라고 권장함
He 초깃값은 앞 계층 노드가 n개일 때 표준편차 2/n^^0.5인 정규분포를 사용
(Xavier 초깃값은 1/n^^0.5)
ReLU는 음의 영역이 0이라 더 넓게 분포시키기 위해 2배의 계수가 필요함

[그림6-14]는 활성화 함수로 ReLU를 사용한 경우의 가중치 초깃값에 따른 활성화값 분포 변화

결과를 보면 std = 0.01일 때 각 층의 활성화 값들은 아주 작은 값들임
신경망에 아주 작은 데이터가 흐른 다는 것은 역전파 때 가중치의 역시 작아진다는 뜻으로 학습이 거의 이뤄지지 않음

Xavier 초깃값 치우침이 커지며 '기울기 소실' 문제가 발생됨

He 초깃값은 모든 층에서 균일하게 분포됨
층이 깊어져도 분포가 균일하게 유지되기에 역전파가 적절한 값이 나올 것으로 기대됨

ReLU일 때 He 초깃값을 쓰고 sigmoid 함수와 tanh 함수 등 S자 모양 곡선일 때 Xavier 초깃값을 쓰는 게 현재의 모범사례


정리

<이번 장에서 배운 내용>

  • 매개변수 갱신 방법에는 확률적 경사 하강법(SGD) 외에도 모멘텀, AdaGrad, Adam 등이 있다.
  • 가중치 초기값을 정하는 방법은 올바른 학습을 하는데 매우 중요하다.
  • 가중치의 초기값으로는 'Xavier 초깃값'과 'He 초깃값'이 효과적이다.

가중치의 초깃값에 대한 부분애서 왜 그런지에 대해 자세하게 설명되어 있는 점이 좋았지만 그 부분을 이해는 것이 어려웠다.
발표를 해야하는데 반대로 사람들에게 질문을 해서 이해하는 시간이 될 것 같다.

profile
굴러가는 토마토

0개의 댓글