보이는 딥러닝 part 4 of 13

20층을 쌓으면 기울기가 사라진다

guide / / 5 sections

앞 편에서 연쇄법칙은 곱셈이라고 했다. 층이 세 개면 세 번 곱하고, 스무 개면 스무 번 곱한다. 그런데 1보다 조금 작은 수를 스무 번 곱하면 0에 붙고, 조금 큰 수를 스무 번 곱하면 폭발한다. 이 뻔한 산수가 딥러닝에서 오래 발목을 잡았던 문제이고, 초기값을 어떻게 뽑느냐가 그 곱셈의 밑을 정한다.

20층을 세워서 재 본다

층마다 가중치를 정규분포에서 뽑고, 표준편차만 바꿔 가며 세운다. 활성함수는 tanh, 폭은 256, 층은 20개다.

import numpy as np
rng = np.random.default_rng(0)
N, D, L = 512, 256, 20

def run(scale):
    x = rng.standard_normal((N, D))         # 입력 표준편차 1
    h, Ws, hs = x, [], [x]
    for _ in range(L):
        W = rng.standard_normal((D, D)) * scale
        h = np.tanh(h @ W)
        Ws.append(W); hs.append(h)

    g = rng.standard_normal(h.shape) / np.sqrt(N)   # 손실에서 온 기울기
    grads = []
    for i in range(L - 1, -1, -1):
        g = g * (1 - hs[i+1] ** 2)                  # tanh 미분
        grads.append((hs[i].T @ g).std())           # 이 층 가중치의 기울기
        g = g @ Ws[i].T
    return [a.std() for a in hs[1:]], grads[::-1]

세 가지 초기값으로 돌린다. 아주 작게(0.01), 아주 크게(1.0), 그리고 1/sqrt(n) 이다. 마지막 것을 Xavier 초기화라고 부른다.

                  활성 1층   활성 20층    기울기 1층   기울기 20층   20층/1층
작게 0.01         1.56e-01   1.14e-16     7.27e-16     7.01e-16      0.96
크게 1.0          9.75e-01   9.74e-01     1.74e+08     1.80e-01      1.0e-09
Xavier 1/sqrt(n)  6.27e-01   1.62e-01     1.92e-01     1.58e-01      0.82
1e-18 1e-12 1e-6 1e0 1e6 1 10 20 기울기 크기 크게 1.0 Xavier 1/sqrt(n) 작게 0.01
층별 가중치 기울기의 크기. 눈금 하나가 100만배다. 작게 두면 전 층이 1e-16 에서 평평하고, 크게 두면 1층과 20층이 1e9 배 벌어지며, Xavier 는 거의 수평이다.

작게 두면: 신호가 먼저 죽는다

0.01 은 층을 지날 때마다 활성을 줄인다. 1층에서 0.156 이던 것이 20층에서 1.14e-16 이다. 열여섯 자리가 사라졌다.

기울기는 전 층에서 7e-16 근처로 평평하다. 폭발도 소멸도 없이 전부 똑같이 0에 가깝다. 학습률을 아무리 올려도 이 숫자로는 가중치가 안 움직인다.

여기서 범인을 tanh 로 오해하기 쉽다. 재 보면 반대다. 활성이 0 근처에 몰려 있으니 tanh 의 미분 1 - h² 는 1층에서 0.9755, 3층부터는 소수점 넷째 자리까지 1.0000 이다. 미분은 가장 클 수 있는 값이다.

범인은 가중치 크기다. 0.01 × sqrt(256) = 0.16 이 층당 배율이고, 순전파는 층마다 0.160배로 줄어든다(측정값). 역전파도 거슬러 가며 같은 0.16배씩 줄어든다. 두 방향이 같은 속도로 줄기 때문에, 어느 층의 가중치 기울기든 |입력| × |역방향 신호|0.16^20 으로 같아진다. 그림의 그 평평한 선이 이 대칭이다.

크게 두면: 층마다 다른 세상이 된다

1.0 은 반대다. 활성이 1층부터 0.975 로 붙어 있고 20층까지 그대로다. tanh 가 포화한 것이다. 입력이 커서 출력이 ±1 에 눌려 있고, 이 구간에서 tanh 의 미분 1 - h² 는 0에 가깝다.

그런데 기울기는 죽지 않는다. 첫 층에서 1.74e+08 이다. 미분이 작아지는 것보다 가중치 W 를 거슬러 곱하는 쪽이 더 크게 키운다. 문제는 크기 자체가 아니라 층 사이의 격차다. 1층과 20층의 기울기가 1e9 배 차이 난다. 학습률 하나로 두 층을 동시에 감당할 방법이 없다. 20층에 맞추면 1층이 발산하고, 1층에 맞추면 20층은 멈춘다. 2편에서 본 “가장 가파른 방향 하나가 상한을 정한다” 가 층 단위로 벌어진 것이다.

Xavier: 곱셈의 밑을 1로 맞춘다

1/sqrt(n) 이 나오는 자리는 분산 계산 한 줄이다. n 개를 더한 합의 분산은 항 하나의 분산의 n 배이므로, 가중치 분산을 1/n 으로 두면 층을 지나도 분산이 유지된다. 곱셈의 밑을 1 근처로 맞추는 것이다.

정확히 하면 이 계산은 순전파만 맞춘 것이고, 그래서 나오는 값은 1/n_in 이다. 역전파까지 같이 맞추려면 1/n_out 이 필요한데 둘을 동시에 만족할 수는 없다. Xavier 는 그 절충으로 2/(n_in + n_out) 을 쓴다. 여기서는 층 폭이 256으로 전부 같아 두 값이 정확히 일치하지만, 폭이 다른 층에 이 유도를 그대로 가져가면 프레임워크의 xavier_normal_ 과 다른 상수가 나온다.

측정값도 그렇게 나온다. 기울기가 1층 0.192, 20층 0.158 로 20층을 지나며 0.82배가 된다.

비교는 두 축으로 해야 공평하다. 층 사이 균일함만 보면 작게 둔 쪽이 0.96 으로 오히려 더 평평하다. 대신 그 평평한 값이 7e-16 이라 아무 일도 일어나지 않는다. 크기만 보면 크게 둔 쪽이 넉넉하지만 층 사이가 1e9 배 벌어진다. 두 축을 동시에 만족하는 건 Xavier 하나다 - 크기 0.19, 층 사이 0.82배.

정직하게 덧붙이면 완벽하지는 않다. 활성은 0.627 에서 0.162 로 20층 동안 4배쯤 줄어든다. tanh 가 원점 근처에서만 기울기 1이고 그 밖에서는 1보다 작기 때문이다. ReLU 계열에서 2/n 을 쓰는 He 초기화가 나온 이유도 이것으로, 음수 쪽 절반이 0이 되는 만큼 분산을 두 배로 보상한다.

그래서

  • 층을 쌓는다는 건 같은 수를 그만큼 곱한다는 뜻이다. 밑이 1에서 벗어나면 지수적으로 어긋난다
  • 초기값이 작으면 순전파와 역전파가 같은 비율로 함께 줄고, 크면 층 사이 기울기 격차가 벌어진다. 둘 다 학습률로는 못 고친다
  • 1/sqrt(n) 은 마법이 아니라 분산 보존식이다. 활성함수가 바뀌면 상수도 바뀐다
  • 초기화는 튜닝할 하이퍼파라미터라기보다 학습이 시작될 수 있게 하는 조건이다

배치 정규화가 왜 깊은 망을 가능하게 했는지도 같은 그림에서 보인다. 층마다 분산을 다시 맞추니 곱셈의 밑이 1에 고정되고, 초기값 크기가 무엇이든 결과가 같아진다. 실제로 위 세 초기값에 배치 정규화를 붙이면 20층 활성이 셋 다 0.63 으로 같아진다.

잔차 연결은 방식이 다르다. 블록이 h + f(h) 라서 야코비가 I + J 이고, 뒤로 갈 때 곱셈을 건너뛰는 항등 경로가 항상 하나 남는다. 곱을 1에 붙잡는 게 아니라 곱 바깥으로 빠져나가는 길을 내는 것이다. 그래서 잔차만 쓰면 순전파 크기는 오히려 커진다 - 측정해 보면 20블록에서 활성 표준편차가 1.2 에서 3.8 로 자란다. 실제 잔차망이 정규화를 함께 쓰거나 가지를 1/sqrt(L) 로 줄이는 이유가 이것이다.

다음 편에서는 층이 아니라 데이터 쪽을 흔들어 본다. 전체 데이터로 한 걸음 걷는 대신 일부만 보고 걷는 미니배치가, 왜 더 시끄러운데도 더 빨리 도착하는지 재 본다.

Comments