보이는 딥러닝 part 11 of 13

파라미터의 3분의 2가 있는 층

guide / / 6 sections

지금까지 어텐션만 봤다. 가중치를 만들고, 순서를 넣고, 머리를 쪼갰다. 그런데 트랜스포머 블록에는 그 옆에 하나가 더 붙어 있고, 이름이 그냥 피드포워드다.

설명이 짧다고 작은 게 아니다. 세어 보면 반대다.

파라미터의 3분의 2

블록 하나에서 d_model = d, 확장 배수 4일 때 세어 본다.

어텐션      Wq, Wk, Wv, Wo   4개 x d^2   = 4d^2
피드포워드  W1 (d->4d), W2 (4d->d)       = 8d^2
합                                        12d^2

8/12, 정확히 2/3 다. d=512 면 어텐션 1,048,576 개에 피드포워드 2,097,152 개다. 우리가 열 편에 걸쳐 본 것이 파라미터의 3분의 1이었다.

이 층은 토큰끼리 섞지 않는다

피드포워드가 하는 일은 한 줄이다.

FFN(x) = W2 @ relu(W1 @ x + b1) + b2

x 는 토큰 하나의 벡터다. 문장 전체를 넣어도 각 행이 따로 통과한다. 재 보면 그대로다.

np.allclose(F(X), np.vstack([F(X[i:i+1]) for i in range(n)]))   # True
np.allclose(F(X[perm]), F(X)[perm])                             # True

8편에서 어텐션이 순열 등변이라 문제라고 했는데, 피드포워드는 순열 등변인 게 정상이다. 자리를 섞으면 결과도 그대로 섞이면 된다. 토큰 사이를 잇는 일은 어텐션이 전담하고, 이 층은 각 토큰 안에서만 계산한다.

블록을 이렇게 읽으면 깔끔하다. 어텐션은 옮기고, 피드포워드는 처리한다.

비선형을 빼면 확장이 정확히 아무것도 안 산다

d4d 로 넓혔다가 다시 d 로 줄인다. 왜 넓히나. 비선형이 없으면 답은 아무 이유도 없다 이다.

relu 를 빼면 W2(W1 x) = (W2 W1) x 이고, W2 W1 은 그냥 d x d 행렬 하나다. 랭크도 min(d, 4d) = d 를 못 넘는다.

W2 @ W1 은 64x64, 랭크 64  (상한 min(64, 256) = 64)
확장 없는 단일 64x64 로 정확히 대체: 잔차 3.3e-16

파라미터 32,768 개를 써서 4,096 개짜리와 똑같은 함수족을 얻는다. 넓힌 자리가 통째로 낭비다. 4편에서 층을 쌓아도 선형이면 한 층이라고 했던 것과 같은 이야기이고, 여기서는 그 낭비가 파라미터 수로 정확히 보인다.

그러니 이 층의 정체는 확장이 아니라 확장한 자리에 놓인 비선형이다.

폭이 사는 것: 조각 수

그럼 넓히면 정확히 뭐가 늘어나나. relu 망은 조각조각 선형인 함수이고, 뉴런 하나가 꺾임 하나를 만든다. 입력이 1차원이면 뉴런 i 의 꺾임은 x = -b_i / w_i 한 점이다. 폭이 m 이면 꺾임이 최대 m 개, 조각이 최대 m+1 개다.

sin(3x) 를 폭만 바꿔 가며 실제로 맞춰 봤다.

-1 0 1 -2 -1 0 1 2 x 목표 sin(3x) 폭 2 폭 4 폭 16
은닉 폭 2, 4, 16 인 ReLU 망이 sin(3x) 를 맞춘 결과. 점선이 목표다. 폭 2는 꺾임이 둘뿐이라 곡선 흉내를 못 내고, 폭이 늘수록 조각이 촘촘해진다. 뉴런 하나가 꺾임 하나이므로 폭 m 이면 조각이 최대 m+1 개다.
   m     RMSE    구간 안 꺾임
   2   0.6221         2
   4   0.2251         4
   8   0.0989         7
  16   0.0388        16
  32   0.0250        31

폭 2는 꺾임이 둘이라 sin 을 흉내조차 못 낸다. 폭을 열여섯 배로 늘리면 오차가 0.6221 에서 0.0250 으로 떨어진다. 늘어난 건 표현력이 아니라 곡선을 근사할 직선 조각의 개수다.

4d 라는 숫자도 여기서 읽힌다. 넓을수록 조각이 촘촘해지지만 파라미터가 폭에 비례해 늘어난다. 4배는 그 사이의 관행적 타협이고, 실제로 3배나 8/3배를 쓰는 모델도 많다.

키와 값으로 읽기

한 가지 더. W1 의 행 하나를 k_i, W2 의 열 하나를 v_i 라고 보면 이 층은 이렇게 다시 쓸 수 있다.

FFN(x) = Σ relu(k_i · x + b_i) · v_i

k_i · x 는 이 토큰이 i 번 패턴에 얼마나 맞는지 재는 것이고, 맞으면 그만큼 v_i 를 더한다. 찾고, 쓴다. 10편의 헤드가 그랬듯 여기서도 합이다.

np.abs(F(X) - sum(np.outer(relu(X @ W1[i] + b1[i]), W2[:, i]) for i in range(m))).max()
# 3.3e-16

뉴런 4d 개가 각각 “이런 패턴이면 이걸 더해라” 라는 규칙 하나씩인 셈이다. 큰 모델의 피드포워드를 사실상 기억 장치로 보는 해석이 여기서 나온다.

다만 무작위 초기값에서 재 보면 토큰 하나당 뉴런의 0.5030 이 켜진다. 절반이 동시에 반응하는 상태로는 “규칙 하나” 라고 부르기 어렵다. 깔끔하게 갈라지는 것은 학습의 결과이지 구조가 주는 게 아니다.

그래서

  • 블록 파라미터의 2/3 가 피드포워드다. 4d^28d^2
  • 이 층은 토큰끼리 안 섞는다. 어텐션이 옮기고 이 층이 처리한다
  • 비선형이 없으면 4배 확장은 정확히 무의미하다. 32,768 개를 쓰고 4,096 개와 같은 함수족, 잔차 3.3e-16
  • 폭이 사는 것은 조각 수다. 뉴런 하나가 꺾임 하나, 폭 m 이면 조각 m+1 개. 폭 2에서 32로 가며 오차 0.62210.0250
  • 이 층은 Σ relu(k_i · x) v_i 로 정확히 쓸 수 있다. 찾고, 쓴다

다음 편에서는 블록을 실제로 쌓는다. 지금까지 부품을 하나씩 봤는데, 그 부품들을 어떤 순서로 이어야 20층이 학습되는지가 남았다.

Comments