06.2 가중치의 초깃값

신경망 학습에서 특히 중요한 것이 가중치의 초깃값입니다. 가중치의 초깃값을 무엇으로 설정하느냐가 신경망 학습의 성패를 가르는 일이 실제로 자주 있습니다. 이번 절에서는 권장하는 초깃값에 대해서 설명하고 실험을 통해 실제로 신경망 학습이 신속하게 이루어지는 모습을 확인하겠습니다.

6.2.1 초깃값을 0으로 하면?

이제부터 오버피팅을 억제해 범용 성능을 높이는 테크닉인 가중치 감소${}^{\text{weight decay}}$ 기법을 소개하려 합니다. 가중치 감소는 간단히 말하자면 가중치 매개변수의 값이 작아지도록 학습하는 방법입니다. 가중치 값을 작게 하여 오버피팅이 일어나지 않게 하는 것이죠.

가중치를 작게 만들고 싶으면 초깃값도 최대한 작은 값에서 시작하는 것이 정공법이죠. 사실 지금까지 가중치의 초깃값은 0.01 * np.random.randn(10, 100)처럼 정규분포에서 생성되는 값을 0.01배 한 작은 값(표준편차가 0.01인 정규분포)을 사용했습니다.

그렇다면 가중치의 초깃값을 모두 0으로 설정하면 어떨까요? 답부터 얘기하면, 이는 나쁜 아이디어입니다. 실제로 가중치 초깃값을 0으로 하면 학습이 올바로 이뤄지지 않습니다.

초깃값을 모두 0으로 해서는 안 되는 이유는 뭘까요? (정확히는 가중치를 균일한 값으로 설정해서는 안 됩니다.) 그 이유는 바로 오차역전파법에서 모든 가중치의 값이 똑같이 갱신되기 때문입니다. 예를 들어 2층 신경망에서 첫 번째와 두 번째 층의 가중치가 0이라고 가정하겠습니다. 그럼 순전파 때는 입력층의 가중치가 0이기 때문에 두 번째 층의 뉴런에 모두 같은 값이 전달됩니다. 두 번째 층의 모든 뉴런에 같은 값이 입력된다는 것은 역전파 때 두 번째 층의 가중치가 모두 똑같이 갱신된다는 말이 됩니다(‘곱셈 노드의 역전파’를 떠올려보세요). 그래서 가중치들은 같은 초깃값에서 시작하고 갱신을 거쳐도 여전히 같은 값을 유지하는 것이죠. 이는 가중치를 여러 개 갖는 의미를 사라지게 합니다. 이 ‘가중치가 고르게 되어버리는 상황’을 막으려면(정확히는 가중치의 대칭적인 구조를 무너뜨리려면) 초깃값을 무작위로 설정해야 합니다.

초깃값 0의 대칭성 붕괴 함정 가중치를 모두 0(혹은 동일한 값)으로 일괄 설정하자 저울이 완벽한 대칭을 이룬 채 묶여버려서, 뉴런들의 학습 경로가 대칭 붕괴 없이 획일화되어 굳어버린 함정을 도로시와 지니가 가리키고 있어요.

지니의 대칭성 붕괴 경고 (초깃값 0의 함정)

  • 지니: “도로시, 가중치를 작게 만들고 싶다고 해서 전부 0(혹은 균일한 값)으로 초기화하면 안 돼. 모든 뉴런이 같은 가중치를 가지면 순전파 때 같은 값을 출력하고, 역전파 때도 똑같은 크기로 가중치가 갱신되기 때문에 여러 개의 뉴런을 둔 의미가 완전히 사라져 버린단다!”
  • 도로시: “아! 아무리 뉴런을 100개, 1000개 둬봤자 가중치가 거울처럼 똑같이 움직이니까 결국 뉴런 1개짜리 바보 신경망이 되는 거구나! 그래서 초깃값은 대칭성을 무너뜨리기 위해 꼭 무작위(Random)로 흩뜨려 줘야 하는 거네!”

6.2.2 은닉층의 활성화값 분포

은닉층의 활성화값(활성화 함수의 출력 데이터)*의 분포를 관찰하면 중요한 정보를 얻을 수 있습니다. 이번 절에서는 가중치의 초깃값에 따라 은닉층 활성화값들이 어떻게 변화하는지 간단한 실험을 해보려 합니다. 구체적으로는 활성화 함수로 시그모이드 함수 사용하는 5층 신경망에 무작위로 생성한 입력 데이터를 흘리며 각 층의 활성화값 분포를 히스토그램으로 그려보겠습니다. 이 실험은 스탠퍼드 대학교의 CS231n 수업[5]을 참고했습니다.

이 실험을 위한 전체 소스 코드는 ch06/weight_init_activation_histogram.py에 있으니 참고하시고, 책 본문에서는 필요한 부분만 가져와 설명하겠습니다.

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

층이 5개가 있으며, 각 층의 뉴런은 100개씩입니다. 입력 데이터로서 1,000개의 데이터를 정규분포로 무작위로 생성하여 이 5층 신경망에 흘립니다. 활성화 함수로는 시그모이드 함수를 이용했고, 각 층의 활성화 결과를 activations 변수에 저장합니다. 이 코드에서는 가중치의 분포에 주의해야 합니다. 이번에는 표준편차가 1인 정규분포를 이용했는데, 이 분포된 정도(표준편차)를 바꿔가며 활성화값들의 분포가 어떻게 변화하는지 관찰하는 것이 이 실험의 목적입니다. 그럼 activations에 저장된 각 층의 활성화값 데이터를 히스토그램으로 그려보겠습니다.

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

이 코드를 실행하면 [그림 6-10]의 히스토그램을 얻을 수 있습니다.

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

그림 6-10

각 층의 활성화값들이 0과 1에 치우쳐 분포되어 있군요. 여기에서 사용한 시그모이드 함수는 그 출력이 0에 가까워지자(또는 1에 가까워지자) 그 미분은 0에 다가갑니다. 그래서 데이터가 0과 1에 치우쳐 분포하게 되면 역전파의 기울기 값이 점점 작아지다가 사라집니다. 이것이 기울기 소실${}^{\text{gradient vanishing}}$이라 알려진 문제입니다. 층을 깊게 하는 딥러닝에서는 기울기 소실은 더 심각한 문제가 될 수 있습니다.

이번에는 가중치의 표준편차를 0.01로 바꿔 같은 실험을 반복해보겠습니다. 앞의 코드에서 가중치 초깃값 설정 부분을 다음과 같이 바꾸면 됩니다.

# w = np.random.randn(node_num, node_num) * 1
w = np.random.randn(node_num, node_num) * 0.01

결과를 봅시다. 표준편차를 0.01로 한 정규분포의 경우 각 층의 활성화값 분포는 [그림 6-11]처럼 됩니다.

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

그림 6-11

이번에는 0.5 부근에 집중되었습니다. 앞의 예처럼 0과 1로 치우치진 않았으니 기울기 소실 문제는 일어나지 않습니다만, 활성화값들이 치우쳤다는 것은 표현력 관점에서는 큰 문제가 있는 것이죠. 무슨 이야기냐 하면, 이 상황에서는 다수의 뉴런이 거의 같은 값을 출력하고 있으니 뉴런을 여러 개 둔 의미가 없어진다는 뜻입니다. 예를 들어 뉴런 100개가 거의 같은 값을 출력한다면 뉴런 1개짜리와 별반 다를 게 없는 것이죠. 그래서 활성화값들이 치우치면 표현력을 제한한다는 관점에서 문제가 됩니다.

WARNING_ 각 층의 활성화값은 적당히 고루 분포되어야 합니다. 층과 층 사이에 적당하게 다양한 데이터가 흐르게 해야 신경망 학습이 효율적으로 이뤄지기 때문입니다. 반대로 치우친 데이터가 흐르면 기울기 소실이나 표현력 제한 문제에 빠져서 학습이 잘 이뤄지지 않는 경우가 생깁니다.

이어서 사비에르 글로로트${}^{\text{Xavier Glorot}}$와 요슈아 벤지오${}^{\text{Yoshua Bengio}}$의 논문[9]에서 권장하는 가중치 초깃값인, 일명 Xavier 초깃값을 써보겠습니다. 현재 Xavier 초깃값은 일반적인 딥러닝 프레임워크들이 표준적으로 이용하고 있습니다. 예를 들어 카페${}^{\text{Caffe}}$ 프레임워크는 가중치 초깃값을 설정할 때 인수로 xavier를 지정할 수 있습니다.

이 논문은 각 층의 활성화값들을 광범위하게 분포시킬 목적으로 가중치의 적절한 분포를 찾고자 했습니다. 그리고 앞 계층의 노드가 $n$개라면 표준편차가 $\frac{1}{\sqrt{n}}$인 분포를 사용하면 된다는 결론을 이끌었죠(그림 6-12).*

그림 6-12 Xavier 초깃값: 초깃값의 표준편차가 $\frac{1}{\sqrt{n}}$이 되도록 설정($n$은 앞 층의 노드 수)

그림 6-12

Xavier 초깃값을 사용하면 앞 층에 노드가 많을수록 대상 노드의 초깃값으로 설정하는 가중치가 좁게 퍼집니다. 이제 Xavier 초깃값을 써서 실험해봅시다. 코드에서는 가중치 초깃값 설정 부분을 다음과 같이 고쳐주기만 하면 됩니다(모든 층의 노드 수가 100개라고 단순화했습니다).

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

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

그림 6-13

* 사비에르의 논문은 앞 층의 입력 노드 수 외에 다음 층의 출력 노드 수도 고려한 설정값을 제안합니다. 다만 카페 등의 프레임워크는 앞 층의 입력 노드만으로 계산하도록 단순화했습니다.

Xavier 초깃값을 사용한 결과는 [그림 6-13]처럼 됩니다. 이 결과를 보면 층이 깊어지면서 형태가 다소 일그러지지만, 앞에서 본 방식보다는 확실히 넓게 분포됨을 알 수 있습니다. 각 층에 흐르는 데이터는 적당히 퍼져 있으므로, 시그모이드 함수의 표현력도 제한받지 않고 학습이 효율적으로 이뤄질 것으로 기대됩니다.

사비에르 초깃값 균일 분포 도로시가 시그모이드 모양의 밭고랑에 가중치 분포 표준편차 $\frac{1}{\sqrt{n}}$을 곱한 사비에르 초깃값 씨앗들을 가지런히 뿌리고 있고, 싹들이 적당한 높이로 균일하게 자라나고 있어요.

도로시의 씨앗 심기 깨달음 (Xavier 초깃값)

  • 도로시: “와! 이전 노드 수 $n$의 제곱근인 $\frac{1}{\sqrt{n}}$ 비율로 초깃값 분포 범위를 조율해 줬더니, 활성화값 분포가 치우치지 않고 넓게 골고루 피어났어! 시그모이드 함수의 미분 값이 0이 되지 않는 구간에 딱 안전하게 걸친 거야!”
  • 지니: “맞아! 노드 수가 많아질수록 가중치 스케일을 알아서 좁혀서 활성화값 출력 분포의 분산 크기를 유지해 주는 원리지. 덕분에 깊은 신경망에서도 표현력이 제한받지 않고 정보가 원활하게 퍼져나간단다.”

NOTE_ [그림 6-13]은 오른쪽으로 갈수록 약간씩 일그러지고 있습니다. 이 일그러짐은 sigmoid 함수 대신 tanh 함수(쌍곡선 함수)를 이용하면 개선됩니다. 실제로 tanh 함수를 이용하면 말끔한 종 모양으로 분포됩니다. tanh 함수도 sigmoid 함수와 같은 ‘S’자 모양 곡선 함수입니다. 다만 tanh 함수가 원점 $(0, 0)$에서 대칭인 S 곡선인 반면, sigmoid 함수는 $(x, y) = (0, 0.5)$에서 대칭인 S 곡선이죠. 활성화 함수용으로는 원점에서 대칭인 함수가 바람직하다고 알려져 있습니다.

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

Xavier 초깃값은 활성화 함수가 선형인 것을 전제로 이끈 결과입니다. sigmoid 함수와 tanh 함수는 좌우 대칭이라 중앙 부근이 선형인 함수로 볼 수 있죠. 그래서 Xavier 초깃값이 적당합니다. 반면 ReLU를 이용할 때는 ReLU에 특화된 초깃값을 이용하라고 권장합니다. 이 특화된 초깃값을 찾아낸 카이밍 히${}^{\text{Kaiming He}}$의 이름을 따 He 초깃값[10]이라 합니다. He 초깃값은 앞 계층의 노드가 $n$개일 때, 표준편차가 $\sqrt{\frac{2}{n}}$인 정규분포를 사용합니다. Xavier 초깃값이 $\sqrt{\frac{1}{n}}$이었죠? ReLU는 음의 영역이 0이라서 더 넓게 분포시키기 위해 2배의 계수가 필요하다고 (직관적으로) 해석할 수 있겠습니다.

그러면 활성화 함수로 ReLU를 이용한 경우의 활성화값 분포를 함께 보시죠. [그림 6-14]는 표준편차가 0.01인 정규분포($\text{std} = 0.01$), Xavier 초깃값, ReLU 전용 He 초깃값일 때의 실험 결과를 차례로 보여줍니다.

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

그림 6-14

표준편차가 0.01인 정규분포를 가중치 초깃값으로 사용한 경우

Xavier 초깃값을 사용한 경우

He 초깃값을 사용한 경우

결과를 보면 $\text{std} = 0.01$일 때의 각 층의 활성화값들은 아주 작은 값들입니다.* 신경망에 아주 작은 데이터가 흐른다는 것은 역전파 때 가중치의 기울기 역시 작아진다는 뜻입니다. 이는 중대한 문제이며, 실제로도 학습이 거의 이뤄지지 않을 겁니다.

이어서 Xavier 초깃값 결과를 보면 이쪽은 층이 깊어지면서 치우침이 조금씩 커집니다. 실제로 층이 깊어지면 활성화값들의 치우침도 커지고, 학습할 때 ‘기울기 소실’ 문제를 일으킵니다.

마지막으로 He 초깃값은 모든 층에서 균일하게 분포되었습니다. 층이 깊어져도 분포가 균일하게 유지되기에 역전파 때도 적절한 값이 나올 것으로 기대할 수 있습니다.

He 초깃값 ReLU 네트워크 적용 도로시가 ReLU의 깊은 절벽 고랑에 물뿌리개(He 초깃값 표준편차 $\sqrt{\frac{2}{n}}$)를 사용하여 생명수를 골고루 주고 있고, 신호 분포가 메마르지 않고 싱싱하게 살아 흐르고 있어요.

도로시의 에너지가 넘치는 활성화 (He 초깃값)

  • 도로시: “ReLU 활성화 함수는 음수 영역이 다 잘려서 0이 되잖아. 그래서 사비에르 초깃값으로는 층이 깊어질수록 분포가 점점 메말라 쪼그라들었어. 그런데 He 초깃값($\sqrt{\frac{2}{n}}$)은 계수를 2배로 넓혀 줘서 활성화 값들이 마지막 5층까지 생생하게 흘러넘쳐!”
  • 지니: “정확해! ReLU의 특성상 절반의 출력 신호가 소멸하기 때문에, Xavier보다 분산을 2배 키워야 깊은 층에서도 신호 강도가 죽지 않고 균일하게 유지되는 거지. ReLU 짝꿍은 언제나 He 초깃값이라는 점, 꼭 기억해 두렴!”

* 각 층의 활성화값 분포의 평균은 1층부터 차례로 0.0396, 0.00290, 0.000197, 1.32e-5, 9.46e-7입니다.

이상의 실험 결과를 바탕으로, 활성화 함수로 ReLU를 사용할 때는 He 초깃값을, sigmoid나 tanh 등의 S자 모양 곡선일 때는 Xavier 초깃값을 쓰겠습니다. 이것이 현재의 모범 사례라고 할 수 있습니다.

6.2.4 MNIST 데이터셋으로 본 가중치 초깃값 비교

이번에는 ‘실제’ 데이터를 가지고 가중치의 초깃값을 주는 방법이 신경망 학습에 얼마나 영향을 주는지 보겠습니다. 지금까지 살펴본 세 경우(std = 0.01, Xavier 초깃값, He 초깃값) 모두를 실험해보죠(소스 코드는 ch06/weight_init_compare.py에 있습니다). 결과부터 보겠습니다(그림 6-15).

그림 6-15 MNIST 데이터셋으로 살펴본 ‘가중치의 초깃값’에 따른 비교

그림 6-15

이 실험은 층별 뉴런 수가 100개인 5층 신경망에서 활성화 함수로 ReLU를 사용했습니다. [그림 6-15]에서 보듯 $\text{std} = 0.01$일 때는 학습이 전혀 이뤄지지 않습니다. 앞에서 활성화값의 분포에서 본 것처럼 순전파 때 너무 작은 값(0 근처로 밀집한 데이터)이 흐르기 때문입니다. 그로 인해 역전파 때의 기울기도 작아져 가중치가 거의 갱신되지 않는 것이죠. 반대로 Xavier와 He 초깃값의 경우는 학습이 순조롭게 이뤄지고 있습니다. 다만 학습 진도는 He 초깃값 쪽이 더 빠르군요.

지금까지 살펴보았듯 가중치의 초깃값은 신경망 학습에 아주 중요한 포인트입니다. 가중치의 초깃값에 따라 신경망 학습의 성패가 갈리는 경우가 많습니다. 초깃값의 중요성은 간과하기 쉬운 부분입니다만, 어떤 일이든 시작(초깃값)이 중요한 법이죠. 가중치 초깃값의 중요성을 다시 한번 강조하면서 이번 절을 마칩니다.


* 다른 문헌에서는 계층 사이를 흐르는 데이터를 ‘활성화값’이라 하기도 합니다.

서브목차