본문 바로가기
Deep Learning Basic

2. 가중치 초깃값

by Keep It Simple, Stupid! 2018. 11. 17.

가중치의 초깃값


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


초깃값을 0으로 하면?

오버피팅을 억제해 범용 성능을 높이는 기술을 "가중치 감소(weight decay)"기법이 있다.

가중치 감소는 말 그대로 가중치 매개변수의 값이 작아지도록 학습하는 방법, 가중치를 작게 하여 오버피팅을 방지할 수 있다

가중치 초기값을 0으로 하면(균일한 값으로 설정하면)?

- Backpropagation에서 모든 가중치의 값이 똑같이 갱신됨 

- 가중치들은 같은 초기값에서 시작하고 갱신을 거쳐도 여전히 같은 값을 유지

- 가중치가 고르게 되는 상황을 피하려면 초기값을 무작위로 설정


은닉층의 활성화값 분포

은닉층의 활성화값(활성화 함수의 출력 데이터)의 분포를 관찰하면 중요한 정보를 얻을 수 있다.

가중치의 초깃값에 따라 은닉층 활성화값들이 어떻게 변화하는지 간단한 실험을 해보자. 구체적으로는 활성화 함수로 시그모이드 함수를 사용하는 5층 신경망에 무작위로 생성한 입력 데이터를 흘리며 각 층의 활성화값 분포를 히스토그램으로 그려보았다.

In [47]:
# 모델링 및 변수 초기화
import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1/(1 + np.exp(-x))

x = np.random.randn(1000, 100) # mini batch : 1000, input : 100
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
  • Layer 5 층
  • 각 층 뉴런은 100 개
  • 입력 데이터로서 1,000개의 데이터를 정규분포로 무작위로 생성함
  • 활성화함수는 Sigmoid
  • 각 층의 활성화 결과를 activations 변수에 저장
In [49]:
# 히스토그램 그리기
plt.figure(figsize=(20,5))

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

Untitled

각 활성화값들이 모두 0과 1에 치우쳐 분포되어 있다. 

여기에 사용한 시그모이드 함수는 그 출력이 0 또는 1에 가까워지자 그 미분은 0에 다가간다. 

시그모이드 함수에 대한 이미지 검색결과

그래서 데이터가 0과 1에 치우쳐 분포하게 되면 역전파의 기울기 값이 점점 작아지다가 사라지게 된다. 이러한 현상을 기울기 소실(gradient vanishing)이라고 한다. 층을 깊게 하는 딥러닝에서 기울기 소실은 더 심각한 문제가 될 수 있다.


이번에는 가중치의 표준편차를 0.01로 바꿔같은 실험을 반복해보았다. 

In [13]:
# 모델링 및 변수 초기화
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import font_manager, rc
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)

def sigmoid(x):
    return 1/(1 + np.exp(-x))

x = np.random.randn(1000, 100) # mini batch : 1000, input : 100
node_num = 100                 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5          # 은닉층이 5개
activations = {}               # 이곳에 활성화 결과(활성화값)를 저장
In [14]:
for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i - 1]
        
    w = np.random.randn(node_num, node_num) * 0.01
    a = np.dot(x, w)
    z = sigmoid(a)
    activations[i] = z

# 히스토그램 그리기
plt.figure(figsize=(20,5))
plt.suptitle("가중치를 표준편차가 `0.01`로 한 정규분포로 초기화할 때의 각 층의 활성화값 분포", fontsize=16)
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()

Untitled1

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

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


이어서 Xavier Gloror와 Yoshua Bengio의 논문에서 권장하는 가중치의 초깃값인, 일명 Xavier 초깃값을 써보겠다. 현재 Xavier 초깃값은 일반적인 딥러닝 프레임워크들이 표준으로 사용하고 있다. 이 논문은 각 층의 활성화값들을 광범위하게 분포시킬 목적으로 가중치의 적절한 분포를 찾고자 했다. 그리고 앞 계층의 노드가 n개라면 표준편차가 $\frac{1}{\sqrt{n}}$ 인 분포를 사용하면 된다는 걸론을 이끌었다.

Xavier 초깃값을 사용하면 앞 층에 노드가 많을수록 대상 노드의 초깃값으로 설정하는 가중치가 좁게 퍼진다. 

- Xavier 초깃값을 써서 실험

코드에서는 가중치 초깃값을 설정 부분을 다음과 고쳐주기만 하면 된다. (모든 층의 노드 수가 100개라고 단순화)

In [3]:
# 모델링 및 변수 초기화
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import font_manager, rc
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)
In [4]:
def sigmoid(x):
    return 1/(1 + np.exp(-x))

x = np.random.randn(1000, 100) # mini batch : 1000, input : 100
node_num = 100                 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5          # 은닉층이 5개
activations = {}               # 이곳에 활성화 결과(활성화값)를 저장
In [6]:
for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i - 1]
        
    w = np.random.randn(node_num, node_num) / np.sqrt(node_num)
    a = np.dot(x, w)
    z = sigmoid(a)
    activations[i] = z

# 히스토그램 그리기
plt.figure(figsize=(20,5))
plt.suptitle("가중치를 초깃값으로 'Xavier 초깃값'을 이용할 때의 각 층의 활성화값 분포", fontsize=16)
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()

Untitled2

이 결괄르 보면 층이 깊어지면서 혀ㅇ태가 다소 일그러지지만, 앞에서 본 방식보다는 확실히 넓게 분포됨을 알 수 있다. 각 층에 흐르는 데이터는 적당히 펴져 있으므로, 시그모이드 함수의 표현력도 제한받지 않고 학습이 효율적으로 이뤄질 것으로 기대된다.

NOTE 위 그림은 오른쪽으로 깊어질수록 약간씩 일그러지고 있다. 이 일그러짐은 Sigmoid 함수 대신 tanh 함수(쌍곡선)를 이용하면 개선된다. 실제로 tanh 함수를 이용하면 말끔한 종 모양으로 분포된다. tanh 함수도 sigmoid 함수와 같은 'S'자 모양 곡선 함수이다. 다만 tanh 함수가 원점(0,0)에서 대칭인 S 곡선인 반면, Sigmoid 함수는 (x, y) = (0, 0.5)에서 대칭인 S 곡선이다. 활성화 함수용으로는 원점에서 대칭인 함수가 바람직하다고 알려져 있다


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


Xavier 초깃값은 활성화 함수가 선형인 것을 전제로 이끈 결과다. sigmoid 함수와 tanh 함수는 좌우 대칭이라 중앙 부근이 선형인 함수로 볼 수 있다. 그래서 Xavier 초깃값이 적당하다. 반면 ReLU를 이용할 때는 ReLU에 특화된 초깃값을 이용하라고 권장한다. 이 특화된 초깃값을 찾아낸 Kaiming He의 이름을 따 He 초깃값이라고 한다. 

He 초깃값은 앞층의 노드가 n개 일 때, 표준편차가 $\sqrt{\frac{2}{n}}$ 인 정규분포를 사용한다. Xavier 초깃값은 $\frac{1}{\sqrt{n}}$ 이었죠? ReLU는 음의 영역이 0이라서 더 넓게 분포시키기 위해 2배의 계수가 필요하다고(직감적으로) 해석할 수 있다.


활성화 함수로 ReLU를 이용한 경우의 활성화값 분포를 확인해보자.


In [1]:
# 모델링 및 변수 초기화
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import font_manager, rc
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)
In [76]:
def relu(x):
    return np.maximum(0, x)

x = np.random.randn(1000, 100) # mini batch : 1000, input : 100
node_num = 100                 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5          # 은닉층이 5개
activations = {}               # 이곳에 활성화 결과(활성화값)를 저장
In [71]:
for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i - 1]
        
    w = np.random.randn(node_num, node_num) * 0.01
    a = np.dot(x, w)
    z = relu(a)
    activations[i] = z

# 히스토그램 그리기
plt.figure(figsize=(20,5))
plt.suptitle("표준편차가 0.01인 정규분포를 가중치 초깃값으로 사용한 경우", fontsize=16)
for i, a in activations.items():
    plt.subplot(1, len(activations), i + 1)
    plt.title(str(i+1) + "-layer")
    plt.ylim(0, 7000)
    plt.hist(a.flatten(), 30, range = (0,1))


plt.show()
In [72]:
def relu(x):
    return np.maximum(0, x)

x = np.random.randn(1000, 100) # mini batch : 1000, input : 100
node_num = 100                 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5          # 은닉층이 5개
activations = {}               # 이곳에 활성화 결과(활성화값)를 저장
In [73]:
for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i - 1]
    # Xavier 초깃값 적용          
    w = np.random.randn(node_num, node_num) / np.sqrt(node_num)
    a = np.dot(x, w)
    z = relu(a)
    activations[i] = z

plt.figure(figsize=(20,5))
plt.suptitle("Xavier 초깃값을 사용한 경우", fontsize=16)
for i, a in activations.items():
    plt.subplot(1, len(activations), i + 1)
    plt.title(str(i+1) + "-layer")
    plt.ylim(0, 7000)    
    plt.hist(a.flatten(), 30, range = (0,1))


plt.show()
In [77]:
def relu(x):
    return np.maximum(0, x)

x = np.random.randn(1000, 100) # mini batch : 1000, input : 100
node_num = 100                 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5          # 은닉층이 5개
activations = {}               # 이곳에 활성화 결과(활성화값)를 저장
In [78]:
for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i - 1]
    # he 초깃값 적용  
    w = np.random.randn(node_num, node_num) * (np.sqrt(2/node_num))
    a = np.dot(x, w)
    z = relu(a)
    activations[i] = z

plt.figure(figsize=(20,5))
plt.suptitle("He 초깃값을 사용한 경우", fontsize=16)
for i, a in activations.items():
    plt.subplot(1, len(activations), i + 1)
    plt.title(str(i+1) + "-layer")
    plt.ylim(0, 7000)    
    plt.hist(a.flatten(), 30, range = (0,1))


plt.show()

ReLU



해석 결과

1. 표준편차가 0.01인 정규분포를 가중치 초깃값으로 사용한 경우 : 학습이 거의 이루어지지 않을 것으로 예상

2. Xavier 초깃값을 사용한 경우 : 층이 깊어지면서 치우침이 조금씩 커진다. 실제로 층이 깊어지면 활성화값들의 치우침도 커지고, 학습할 때 '기울기 소실'문제를 일으킨다.

3. He 초깃값을 사용한 경우 : 모든 층에서 균일하게 분포, 층이 깊어져도 분포가 균일하게 유지되기에 역전파 때도 적절한 값이 나올 것으로 기대할 수 있다.


실험 결과 (현재의 모범 사례)


1. ReLU를 사용할 때는 He 초깃값을 사용하자!

2. Sigmoid ,tanh 등의 S자 모양 곡선일 때는 Xavier 초깃값을 사용하자




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


'실제' 데이터를 가지고 가중치의 초깃값을 주는 방법이 신경망 학습에  얼마나 영향을 주는지 확인해보자. 지금까지 살펴본 세 경우(std = 0.01, Xavier 초깃값, He 초깃값) 모두를 실험해보면 다음과 같은 결과가 나온다.


뉴런 수가 100개인 5층 신경망에서 활성화 함수로 ReLU 사용함




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



'Deep Learning Basic' 카테고리의 다른 글

4. 바른 학습을 위해  (0) 2018.11.18

댓글