← Day 1 Phase 0 · Week 1 · Day 2 Day 3 →
🎯 오늘의 목표
📖 개념 (수업)
$f:\mathbb{R}^n\to\mathbb{R}^m$이 점 $x$에서 미분가능하다는 것은, 어떤 선형사상 $Df(x):\mathbb{R}^n\to\mathbb{R}^m$가 있어
$$f(x+\delta) = f(x) + Df(x)\,\delta + o(\|\delta\|)$$를 만족한다는 뜻이다. 이 선형사상을 표준기저로 행렬 표현한 것이 야코비안 $J = Df(x)\in\mathbb{R}^{m\times n}$, $J_{ij} = \partial f_i/\partial x_j$. "기울기"는 $m=1$인 특수경우의 행벡터(=gradient의 전치)일 뿐이다. 이 관점이 중요한 이유: 합성의 미분이 선형사상의 합성이라는 깔끔한 구조를 주기 때문이다.
$h = g\circ f$, $f:\mathbb{R}^n\to\mathbb{R}^k$, $g:\mathbb{R}^k\to\mathbb{R}^m$이면
$$J_h(x) = J_g\big(f(x)\big)\, J_f(x) \quad\in \mathbb{R}^{m\times n}$$다층이면 그냥 줄줄이 곱이다: $J = J_L J_{L-1}\cdots J_1$. 스칼라 연쇄법칙 $\frac{dz}{dx}=\frac{dz}{dy}\frac{dy}{dx}$은 $1\times1$ 야코비안의 곱일 뿐. 여기서 핵심 질문이 나온다: 이 행렬곱을 오른쪽부터($J_1$부터) 묶을까, 왼쪽부터($J_L$부터) 묶을까? 행렬곱은 결합법칙이 성립하지만 비용은 결합 순서에 따라 천차만별이다. 이게 AD의 두 모드를 가른다.
실제로는 전체 야코비안을 만들지 않는다. 우리가 필요한 건 야코비안과 벡터의 곱이다:
| JVP (forward-mode) | VJP (reverse-mode) | |
|---|---|---|
| 계산 | $v \mapsto J v$ (열방향) | $u \mapsto u^\top J$ (행방향) |
| 의미 | 입력 방향 $v$의 변화가 출력에 주는 변화 | 출력 가중치 $u$가 입력에 주는 민감도 |
| 전체 $J$ 얻으려면 | $n$번 (입력기저마다 JVP) | $m$번 (출력기저마다 VJP) |
| 전파 방향 | 입력 → 출력 (forward와 같이) | 출력 → 입력 (forward 후 역행) |
딥러닝의 목적함수는 스칼라($m=1$, loss). gradient $\nabla_\theta \mathcal{L} = J^\top$ 는 단 한 번의 VJP($u=1$)로 전부 얻어진다. 반면 forward-mode로 같은 걸 하려면 파라미터 수 $n$번의 JVP가 필요하다. $n$이 수십억인 LLM에서 이 차이가 "학습 가능/불가능"을 가른다.
_backward가 하는 일이 바로
"upstream 벡터 $u$를 받아 $u^\top J_\text{local}$를 부모로 흘리는" VJP다. 덧셈 노드의 $J=I$라 $u$가 그대로 가고,
곱셈 노드는 $J=\mathrm{diag}(\text{상대값})$라 원소별 곱이 된다. Day 3 표의 "덧셈=분배, 곱셈=교환"이 이 VJP의 특수형이다.
계산 그래프의 연산 수를 $T$라 하자(=forward 한 번 비용). 핵심 사실:
이 "gradient를 forward의 상수배에 얻는다"는 결과가 cheap gradient principle (Baur–Strassen 정리, 1983)이고, 딥러닝 전체를 가능케 한 알고리즘적 기적이다. 수치미분이 $O(nT)$인 것과 대조하면, $n\sim10^9$에서 $10^9$배 차이다.
| 연산 | VJP (upstream $\bar y$ 받아 입력 grad) |
|---|---|
| $y = Wx$ | $\bar x = W^\top \bar y,\quad \bar W = \bar y\, x^\top$ |
| $y = x \odot z$ (원소별) | $\bar x = \bar y \odot z,\quad \bar z = \bar y \odot x$ |
| $y = \sigma(x)$ (원소별) | $\bar x = \bar y \odot \sigma'(x)$ |
| $y = \mathrm{softmax}(x)$ | $\bar x = y \odot (\bar y - (y^\top \bar y)\mathbf{1})$ |
| $L = \tfrac12\|x\|^2$ | $\bar x = x$ |
이 표를 외우지 말고, 각 줄을 $f(x+\delta)\approx f(x)+J\delta$에서 $\bar x = J^\top \bar y$로 직접 유도해봐라. 특히 $\bar W = \bar y\, x^\top$ 의 외적(rank-1) 구조는 Phase 1에서 선형층 backward를 짤 때 그대로 쓴다.
유한차분 $\frac{f(x+h)-f(x)}{h}$는 절단오차 $O(h)$와 반올림오차 $O(\epsilon/h)$가 상충해, 최적 $h\sim\sqrt{\epsilon}$에서도 상대오차가 $\sim10^{-8}$ 수준이다. 중심차분 $\frac{f(x+h)-f(x-h)}{2h}$는 절단오차 $O(h^2)$로 더 낫다. 해석함수라면 복소스텝 미분 $f'(x)\approx \frac{\mathrm{Im}\,f(x+ih)}{h}$ 가 빼기 소거가 없어 $h\sim10^{-200}$까지 머신정밀도로 정확하다 — gradient check의 황금표준이다.
import numpy as np def f(x): return np.sin(x**2) # 해석함수 x = 1.3; h = 1e-200 cs = np.imag(f(x + 1j*h)) / h # 복소스텝 print(cs) # 2x cos(x^2) 와 머신정밀도로 일치
✍️ 직접 해보기 (강도 ↑)
✅ 스스로 확인
📝 오늘의 기록
docs/00_day02.md: