본문 바로가기
Computer Science/Deep Learning

[비전공자용] [Python] Xavier Initialization (Xavier 초기화) & He Initialization (He 초기화)

by 롱일스 2020. 7. 10.
반응형

신경망 학습에서 가중치 초깃값을 어떻게 정하느냐에 따라 신경망 학습이 성공할 수도 있고 실패할 수도 있습니다. 

은닉층 Hidden Layer의 활성화값(활성화 함수의 출력 데이터, 계층 사이를 흐르는 데이터)의 분포를 잘 관찰하면 가중치를 어떻게 설정해야 좋을지 알 수 있습니다.

은닉층이 5개이고, 각 층의 뉴런이 100개씩 존재한다고 합시다. 입력 데이터로서 1,000개의 데이터를 표준정규분포로 랜덤하게 생성하여 5층 신경망에 흘리고 각 층의 활성화값을 비교해 볼 것 입니다.
활성화 함수로는 시그모이드 Sigmoid 함수를 사용하고, 각 층의 활성화 결과를 activations 변수에 저장합니다.
가중치 초깃값을 표준정규분포로 설정하고 표준편차를 변화시키면서 활성화 결과를 히스토그램으로 보겠습니다.

파이썬 코드로 구현하면 아래와 같습니다.

# coding: utf-8
import numpy as np
import matplotlib.pyplot as plt


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


def ReLU(x):
    return np.maximum(0, x)


def tanh(x):
    return np.tanh(x)
    
input_data = np.random.randn(1000, 100)  # 1000개의 데이터, 표준정규분포로 1000행 100열 행렬생성
node_num = 100  # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5  # 은닉층이 5개
activations = {}  # 이곳에 활성화 결과를 저장

x = input_data

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

    # 초깃값을 다양하게 바꿔가며 실험해보자!
    w = np.random.randn(node_num, node_num) * 1     # 1. 표준편차 1
    # w = np.random.randn(node_num, node_num) * 0.01    # 2. 표준편차 0.01
    # w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)   # 3. Xavier,표준편차 1/√n
    # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num)   # 4. He,표준편차 √(2/n)


    a = np.dot(x, w)


    # 활성화 함수도 바꿔가며 실험해보자!
    z = sigmoid(a)
    # z = ReLU(a)
    # z = tanh(a)

    activations[i] = z

# 히스토그램 그리기
for i, a in activations.items():
    plt.subplot(1, len(activations), i+1)
    plt.title(str(i+1) + "-layer")
    if i != 0: plt.yticks([], [])
    plt.xlim(0.0, 1.0)
    # plt.ylim(0, 7000)
    plt.hist(a.flatten(), 30, range=(0,1))
plt.show()

 

# 활성화 함수 Sigmoid 함수

1. 가중치 초깃값 정규분포 표준편차 = 1  

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

각 층의 활성화값이 0과 1에 치우쳐 분포되어 있습니다. 데이터가 0과 1에 치우쳐 분포하면 역전파의 기울기 값이 점점 작아지다가 사라져서 기울기 손실 Gradient vanishing 문제가 발생합니다. 층이 더 깊은 신경망에서는 더 심각한 결과를 초래합니다.

2. 가중치 초깃값 정규분포 표준편차 = 0.01  

가중치를 표준편차가 0.01인 정규분포의 경우 각 층의 활성화값 분포

이번엔 0.5 근처에 활성화값이 집중되어 있습니다. 기울기 소실 문제는 일어나지 않겠지만, 활성화값들이 치우쳤다는 것은 다수의 뉴런이 거의 같은 값을 출력하고 있어서 뉴런을 여러 개 둔 의미가 없다는 의미여서 큰 문제가 됩니다. 즉, 뉴런 1개나 뉴런 100개나 거의 같은 값을 출력한다면 표현력이 제한되었다고 볼 수 있습니다. 

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

 

3. 가중치 초깃값으로 Xavier 초깃값 이용  

일반적인 딥러닝 프레임워크에서 표준적으로 이용하고 있는 Xavier 초깃값을 가중치 초깃값으로 설정한 결과를 보려고 합니다. 각 층의 활성화값들을 광범위하게 분포시키기 위해 가중치의 적절한 분포를 찾고자,
앞 계층의 노드가 n 개일 때 가중치 초깃값을 표준편차가 1/√n 인 정규분포를 갖도록 설정하는 것이 Xavier 초깃값 (Xavier Initialization) 입니다.

Xavier 초깃값

Xavier 초깃값을 사용하면 앞 층에 노드가 많을수록 가중치가 좁게 퍼집니다. Xavier 초깃값을 이용해서 코드를 실행하면 아래와 같은 결과를 얻습니다.

가중치 초깃값으로 'Xavier 초깃값'을 이용할 때의 경우 각 층의 활성화값 분포

결과를 보면, 앞에 두 경우보다 확실히 넓게 분포됨을 알 수 있습니다. 이 경우, 각 층에 흐르는 데이터의 분포가 넓어서 시그모이드 함수의 표현력도 제한받지 않고 학습이 효율적으로 이뤄질 수 있을 것입니다.

오른쪽으로 갈수록 약간씩 분포가 일그러지는데 이는 sigmoid함수 대신에 tanh함수를 이용하면 개선됩니다. 

Xavier 초깃값은 활성화 함수가 선형인 것을 전제로 이끌어낸 경우입니다. Sigmoid 함수와 tanh 함수가 좌우 대칭이라 중앙 부근이 선형인 함수라고 볼 수 있습니다. 그래서 Xavier 초깃값을 이용하기에 적당합니다.

반면에 활성화함수로 ReLU를 사용할 경우에는 Xavier 초깃값을 사용하는 것이 적절하지 않습니다. 이 경우에는 가중치 초깃값을 He 초깃값을 사용합니다.

 

# 활성화 함수 ReLU 함수

He 초깃값은 활성화함수로 ReLU를 사용할 때 특화된 가중치 초기값입니다. 앞 계층의 노드가 n개일 때, 표준편차가 √(2/n) 인 정규분포를 사용합니다. ReLU는 음의 영역이 0이라 더 넓게 분포시키기 위해 Xavier 에 비해 2배의 계수가 필요하다고 직감적으로 해석할 수 있습니다. 그러면 먼저 활성화 함수가 ReLU인 경우 He 초기화를 통해 가중치를 초기화하는 경우부터 결과가 어떻게 되는지 살펴보겠습니다.

1. 가중치 초깃값으로 He 초깃값 이용 

He 초깃값을 사용한 경우 각 층의 활성화값 분포(좌), 세로축의 범위가 0-6000인 경우의 분포(우) 

ReLU 함수의 특성상 0에서 큰 분포를 가진 것 외에, 모든 층에서 균일하게 분포된 것을 확인 할 수 있습니다. 
층이 깊어져도 분포가 균일하게 유지되어 역전파 때도 적절한 값이 나올 것으로 기대할 수 있습니다.

2. 가중치 초깃값으로 Xavier 초깃값 이용

Xavier 초깃값을 사용한 경우 각 층의 활성화값 분포(좌), 세로축의 범위가 0-6000인 경우의 분포(우) 

Xavier 초깃값 결과를 보면 층이 깊어지면서 활성화 결과 분포의 치우침이 조금씩 커집니다. 실제로 ReLU를 쓰는 경우에 Xavier 초기화를 이용하면 층이 깊어지면서 활성화값들의 치우침이 커지고 학습할 때 기울기 소실 문제를 일으킵니다.

 

3. 가중치 초깃값 정규분포 표준편차 = 0.01

표준편차가 0.01인 정규분포의 가중치 초깃값을 사용한 경우 각 층의 활성화값 분포(좌), 세로축의 범위가 0-6000인 경우의 분포(우) 

각 층의 활성화값들이 아주 작은 값이고 신경망에 아주 작은 데이터가 흐른다는 건 역전파 때 가중치의 기울기 역시 작아진다는 뜻입니다. 이런 경우에는 실제로 학습이 거의 이뤄지지 않습니다.

 

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

실제 데이터를 가지고 지금까지 배운 가중치 초깃값이 주는 영향을 비교해보려고 합니다.
파이썬 코드는 아래와 같습니다.

# coding: utf-8
import os
import sys

sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.util import smooth_curve
from common.multi_layer_net import MultiLayerNet
from common.optimizer import SGD


# 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. 실험용 설정==========
weight_init_types = {'std=0.01': 0.01, 'Xavier': 'sigmoid', 'He': 'relu'}
optimizer = SGD(lr=0.01)

networks = {}
train_loss = {}
for key, weight_type in weight_init_types.items():
    networks[key] = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100],
                                  output_size=10, weight_init_std=weight_type)
    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 weight_init_types.keys():
        grads = networks[key].gradient(x_batch, t_batch)
        optimizer.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 weight_init_types.keys():
            loss = networks[key].loss(x_batch, t_batch)
            print(key + ":" + str(loss))


# 3. 그래프 그리기==========
markers = {'std=0.01': 'o', 'Xavier': 's', 'He': 'D'}
x = np.arange(max_iterations)
for key in weight_init_types.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, 2.5)
plt.legend()
plt.show()

MNIST 데이터셋으로 살펴본 '가중치의 초깃값'에 따른 비교

결과를 확인해보면 표준편차가 0.01인 정규분포인 경우에는 학습이 전혀 이뤄지지 않는 것을 확인할 수 있습니다. 가중치가 0으로 수렴하면서 역전파를 할 때 기울기가 너무 작아져 가중치가 거의 갱신되지 않아서 이런 결과를 나타낸 것입니다. 이 예제의 경우에는 He 초깃값 쪽이 학습 진도가 더 빠른 것을 확인할 수 있습니다.

위 결과를 종합해보면,
활성화 함수로 ReLU를 사용할 때는 He 초깃값을,
sigmoid나 tanh 등의 S자 모양 곡선을 사용할 때는 Xavier 초깃값을
사용하는 게 좋습니다.

 

[출처] Deep Learning from Scratch, ゼロ から作る

728x90
반응형