Weight initialization

iissaacc·2021년 7월 7일
1

deep learning

목록 보기
3/12

Prologue

인공신경망 학습기법에 관한 연구가 활발해지면서 학습속도를 개선하고 100개 넘는 레이어를 쌓는 일도 쉽게 할 수 있게 되었다. 가중치를 어떻게 초기화하냐에 따라 gradient vanishing/exploding을 막으면서 학습을 진행할 수 있다.

AlexNet에서

그랬던 것처럼 평균이 0, 분산을 0.01으로 가중치를 초기화를 하고 레이어를 5보다 좀더 늘려서 한 번만 돌려보자.

dims = [4096] * 10
tanh = []
x = np.random.randn(16, dims[0])
for Din, Dout in zip(dims[:-1], dims[0:]):
    W = 0.01 * np.random.randn(Din, Dout)
    x = np.tanh(x.dot(W))
    tanh.append(x)
    
plt.figure(figsize = (15, 3))   
for i, H in enumerate(tanh):
    plt.subplot(1, len(tanh), i+1)
    plt.hist(H.ravel(), 40, (-1, 1))
    if i != 0 : plt.yticks([],[])
    plt.xlabel('layer ' + str(i + 1))
plt.suptitle('Tanh with AlexNet initialization')

풍성하던 데이터분포가 평균 근처로 쪼그라들었다. tanh는 너무 크거나 너무 작은 값은 1과 -1에 가까운 값으로 수렴하기 때문이다.

Scale up

그래서 간단히 입력값을 스케일업하면 되지 않을까하는 아이디어에서 다시 실험을 진행해봤다.

오막깟! 이러면 지난 글에서 봤듯이 가중치 초기값이 크면 일어나는 문제가 생긴다 activation function이 S자 모양이면 gradient vanishing이 학습을 방해하고, relu계열이면 gradient exploding때문에 모델이 학습을 할 수가 없다. 학습률을 낮춰서 어떻게든 되게 한다고 하더라도 학습이 느리다. 우리 시간은 한정되어 있고 그렇게 많지 않다.

Xavier initialization

복잡한 문제는 직관적으로 풀면 오히려 쉽게 풀릴 때가 있다. 레이어 중간을 똑 떼와서 si=wixis_{i}=w_i x_i라고 할 때 xix_i의 분산을 nn이라고 하자.

cs213n에서는 일반적으로 weight의 분산은 입력값의 갯수가 많아지면 함께 커진다는 점이 안정적인 학습을 막는다고 하고 있다.

우리가 하고 싶은 건 ss의 분산을 일정하게 유지하는 건데 간단히 wiw_i의 분산이 1/n1/ n이라면 ss의 분산을 첫번째 레이어부터 마지막 레이어까지 1로 고정할 수 있을 거다. 그러면 0.01 대신에 1/n\sqrt{1/ n}을 곱하면 잘 되지 않을까? 하는 직관이 논문에 녹아있고 cs231n에서 이렇게 증명하고 있는데 3번째 줄이 왜 그런지 지금은 잘 이해가 안 된다(는 여기에서 그렇게 된다고 증명했다).

Var(si)=Var(inwixi)  =inVar(wixi)  =in[E(wi)]2Var(xi)+[E(xi)]2Var(wi)+Var(xi)Var(wi)  =inVar(xi)Var(wi)  =n(Var(w))Var(x)\begin{array}{lc} Var(s_{i})=Var(\sum_i^n w_i x_i)\\ \\ \qquad\quad \ \ =\sum_i^nVar(w_i x_i)\\ \\ \qquad\quad \ \ =\sum_i^n[E(w_i)]^2 Var(x_i)+[E(x_i)]^2 Var(w_i)+Var(x_i)Var(w_i)\\ \\ \qquad\quad \ \ =\sum_i^n Var(x_i)Var(w_i)\\ \\ \qquad\quad \ \ =n(Var(w))Var(x) \end{array}

scaled = []
for Din, Dout in zip(dims[:-1], dims[1:]):
    Ws = np.sqrt(1/Din)* np.random.randn(Din, Dout)
    x = np.tanh(x.dot(Ws))
    scaled.append(x)
plt.figure(figsize = (15, 3))
for i, H1 in enumerate(scaled):
    plt.subplot(1, len(scaled), i+1)
    plt.hist(H1.ravel(), 40, (-1, 1))
    if i != 0 : plt.yticks([],[])
    plt.xlabel('layer ' + str(i + 1))
plt.suptitle('Tanh with Xavier initialization')

오! 아까보다 훨씬 좋다. 그러면 relu에서도 잘 될까?

...잘 안된다. 음수는 싹 없어지고 뒤에 있던 꼬리마저 짧아진다. Xavier initialization으로 S자 함수는 잘 쓸 수 있게 됐지만 relu를 쓰려면 실험을 많이 해야 했을 거다.

He initialization

relu layer를 지나면서 원본 데이터의 절반을 잃어버리는 샘인데 그래서 분산을 Xavier initialization에서 2배 늘려보기로 한다.

he = []
x = np.random.randn(16, dims[0])
for Din, Dout in zip(dims[:-1], dims[1:]):
    Wm = np.random.randn(Din, Dout) * np.sqrt(2/Din)
    x = np.maximum(0, x.dot(Wm))
    he.append(x)
plt.figure(figsize = (15, 3))
for i, H in enumerate(he):
    plt.subplot(1, len(he), i+1)
    plt.hist(H.ravel(), 40, (-1, 1))
    if i != 0 : plt.yticks([],[])
    plt.xlabel('layer ' + str(i + 1))
plt.suptitle('ReLU with He initialization')


여전히 데이터의 절반은 없어지지만 양수 부분은 잘 가지고 있다.

Epilogue

학습을 잘 하기 위한 가중치 초기화 기법을 짚어봤다. 가중치 초기화를 하면 무조건 학습이 더 잘 된다고 하기는 어렵다. 레이어를 많이 쌓으면 데이터분포가 다시 쪼그라들 거라고 짐작할 수 있다. 이것이 Batch normalization 연구의 배경 중 일부지만 최근들어 Batch normalization이 잘 되는 이유에 대해서는 의견이 분분하다. 구체적인 내용은 다음에 알아보자.

0개의 댓글