← Day 1 Phase 0 · Week 1 · Day 2 Day 3 →

Day 2 — 미분, 야코비안, 자동미분의 두 모드

미분을 "기울기"가 아니라 최선의 국소 선형근사(linear map)로 재정의한다. 그러면 연쇄법칙은 야코비안의 합성이 되고, 자동미분은 그 행렬곱을 어느 방향으로 결합하느냐의 문제가 된다. forward-mode(JVP)reverse-mode(VJP)를 복잡도로 비교해, 왜 ML이 거의 항상 reverse-mode를 쓰는지($d_\text{out}\ll d_\text{in}$)를 정량적으로 못박는다. 이게 Day 4~5에서 만들 micrograd의 이론적 토대다.

🎯 오늘의 목표

📖 개념 (수업)

1. 미분 = 최선의 선형근사

$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의 전치)일 뿐이다. 이 관점이 중요한 이유: 합성의 미분이 선형사상의 합성이라는 깔끔한 구조를 주기 때문이다.

2. 연쇄법칙 = 야코비안 합성

$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의 두 모드를 가른다.

3. JVP vs VJP — 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에서 이 차이가 "학습 가능/불가능"을 가른다.

VJP가 micrograd의 진짜 정체. Day 5에서 각 노드의 _backward가 하는 일이 바로 "upstream 벡터 $u$를 받아 $u^\top J_\text{local}$를 부모로 흘리는" VJP다. 덧셈 노드의 $J=I$라 $u$가 그대로 가고, 곱셈 노드는 $J=\mathrm{diag}(\text{상대값})$라 원소별 곱이 된다. Day 3 표의 "덧셈=분배, 곱셈=교환"이 이 VJP의 특수형이다.

4. 복잡도 — 왜 reverse-mode인가

계산 그래프의 연산 수를 $T$라 하자(=forward 한 번 비용). 핵심 사실:

이 "gradient를 forward의 상수배에 얻는다"는 결과가 cheap gradient principle (Baur–Strassen 정리, 1983)이고, 딥러닝 전체를 가능케 한 알고리즘적 기적이다. 수치미분이 $O(nT)$인 것과 대조하면, $n\sim10^9$에서 $10^9$배 차이다.

심화 메모리 $O(T)$가 부담이라 등장한 게 gradient checkpointing: 중간값을 일부만 저장하고 backward 때 필요분을 재계산해 메모리를 $O(\sqrt{T})$로 줄인다(Chen et al. 2016). Phase 3에서 큰 모델을 Colab에 욱여넣을 때 직접 켜게 된다. forward/reverse의 비대칭이 이 모든 엔지니어링의 뿌리다.

5. 두 모드를 그림으로

forward-mode (JVP): 입력 섭동을 앞으로 밀기 — 한 번에 입력 1방향 x h₁ h₂ L reverse-mode (VJP): 출력 가중치를 뒤로 끌기 — 한 번에 모든 입력 gradient x h₁ h₂ L

6. 자주 쓰는 행렬미분 (외우지 말고 VJP로 유도)

연산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를 짤 때 그대로 쓴다.

7. 수치 검증을 제대로 — 유한차분의 함정과 복소스텝

유한차분 $\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) 와 머신정밀도로 일치

✍️ 직접 해보기 (강도 ↑)

과제 1 — VJP 유도 모음. 위 표의 다섯 줄을 전부 $\bar x = J^\top \bar y$ 정의에서 유도해라. softmax는 야코비안 $J = \mathrm{diag}(y) - yy^\top$ 임을 먼저 보이고 $J^\top\bar y$ 를 정리해 표의 형태가 나오는지 확인.
과제 2 — 결합 순서 비용 측정. 차원 $d_0{=}1000,\ d_1{=}1000,\ \dots,\ d_L{=}1$인 야코비안들 $J_\ell$을 랜덤 생성하고, 전체 곱 $J_L\cdots J_1$을 (a) 왼쪽부터(reverse)(b) 오른쪽부터(forward) 결합해 FLOP/시간을 측정·비교해라. 출력 차원이 1일 때 reverse가 압도적임을 수치로 보여라.
과제 3 — gradient check 유틸. 임의의 $f:\mathbb{R}^n\to\mathbb{R}$과 해석적 gradient를 받아, 중심차분과 복소스텝으로 각각 검증하고 상대오차 $\frac{\|g_\text{num}-g_\text{ana}\|}{\|g_\text{num}\|+\|g_\text{ana}\|}$를 반환하는 함수를 짜라. 두 방법의 달성 정밀도 차이를 표로.
과제 4 — 복잡도 논증(글). "스칼라 손실의 전체 gradient를 reverse-mode가 $O(T)$에 구한다"를 계산 그래프 위에서 한 단락으로 논증해라(각 엣지가 backward에서 상수번 방문됨을 근거로). 이것이 Baur–Strassen의 핵심.
도전 forward-mode가 reverse보다 유리한 경우는 언제인가? $d_\text{out}\gg d_\text{in}$ 예시를 들고, Jacobian-free Hessian-vector product를 forward-over-reverse로 $O(T)$에 얻는 방법(Pearlmutter 1994)을 조사해 요약해라.

✅ 스스로 확인

📝 오늘의 기록

docs/00_day02.md:
다음 (Day 3): 이 VJP들을 계산 그래프(Wengert list) 위에 배치해 reverse-mode를 알고리즘으로 적는다. 노드 공유 시 gradient 누적(=fan-out에서의 합)을 VJP 선형성으로 정당화하고, 메모리/재계산 트레이드오프를 손으로 분석한다. Day 4의 코드가 이 알고리즘의 직역이 된다.

← Day 1 Day 2 끝 Day 3 — 계산 그래프 & reverse-mode →