自動微分
Vibe Prompt
「從零實作一個簡單的自動微分引擎,支援 +, -, *, sin, cos,並計算 f(x,y)=sin(x)*cos(y) 的梯度。」
import math
class Var:
def __init__(self, val, children=()):
self.val = val
self.grad = 0
self._backward = lambda: None
self._prev = set(children)
def __add__(self, other):
other = other if isinstance(other, Var) else Var(other)
out = Var(self.val + other.val, (self, other))
def _backward():
self.grad += out.grad
other.grad += out.grad
out._backward = _backward
return out
def __mul__(self, other):
other = other if isinstance(other, Var) else Var(other)
out = Var(self.val * other.val, (self, other))
def _backward():
self.grad += other.val * out.grad
other.grad += self.val * out.grad
out._backward = _backward
return out
def __neg__(self):
return self * Var(-1)
def __sub__(self, other):
return self + (-other)
def sin(self):
out = Var(math.sin(self.val), (self,))
def _backward():
self.grad += math.cos(self.val) * out.grad
out._backward = _backward
return out
def cos(self):
out = Var(math.cos(self.val), (self,))
def _backward():
self.grad += -math.sin(self.val) * out.grad
out._backward = _backward
return out
def backward(self):
topo = []
visited = set()
def build(v):
if v not in visited:
visited.add(v)
for child in v._prev:
build(child)
topo.append(v)
build(self)
self.grad = 1
for v in reversed(topo):
v._backward()
# 測試
x = Var(0.5)
y = Var(1.2)
f = (x.sin() * y.cos())
f.backward()
print(f"f = sin({x.val}) * cos({y.val}) = {f.val:.4f}")
print(f"df/dx = {x.grad:.4f} (理論: cos(x)*cos(y) = {math.cos(0.5)*math.cos(1.2):.4f})")
print(f"df/dy = {y.grad:.4f} (理論: -sin(x)*sin(y) = {-math.sin(0.5)*math.sin(1.2):.4f})")
深入理解:自動微分的運作原理
計算圖 (Computation Graph)
自動微分的核心是建立計算圖——一個有向無環圖 (DAG),其中:
- 節點 = 變數 (Var) 或運算
- 邊 = 資料依賴關係
建立計算圖後,自動微分分為兩個階段:
- Forward Pass(正向傳播):從輸入到輸出,計算每個節點的值
- Backward Pass(反向傳播):從輸出到輸入,利用鏈式法則計算梯度
x(0.5) ── sin ──┐
├── mul ── f
y(1.2) ── cos ──┘
鏈式法則 (Chain Rule)
對於複合函數 $f = \sin(x) \cdot \cos(y)$:
$$\frac{\partial f}{\partial x} = \cos(x) \cdot \cos(y)$$ $$\frac{\partial f}{\partial y} = -\sin(x) \cdot \sin(y)$$
反向傳播時,梯度從輸出端逐步往回傳遞,每個節點根據鏈式法則計算對輸出的貢獻。
三種梯度計算方式的比較
| 方式 | 精度 | 速度 | 實作難度 | 適用場景 | |------|:----:|:----:|:--------:|---------| | 數值微分 | 低(有截斷誤差) | 極慢 $O(n)$ | 低 | 驗證其他方法 | | 解析微分 | 精準 | 快 $O(1)$ | 高(需手推) | 固定公式 | | 自動微分 | 精準 | 快 $O(n)$ | 中 | ML 框架首選 |
延伸:進階運算元
def exp(self):
out = Var(math.exp(self.val), (self,))
def _backward():
self.grad += out.val * out.grad # d(e^x)/dx = e^x
out._backward = _backward
return out
def log(self):
out = Var(math.log(self.val), (self,))
def _backward():
self.grad += (1/self.val) * out.grad # d(ln x)/dx = 1/x
out._backward = _backward
return out
def pow(self, n):
out = Var(self.val**n, (self,))
def _backward():
self.grad += n * (self.val**(n-1)) * out.grad
out._backward = _backward
return out
實戰:訓練一個簡單的模型
# 使用我們的自動微分引擎訓練 y = wx + b
x_data = np.array([1, 2, 3, 4, 5], dtype=float)
y_data = np.array([3, 5, 7, 9, 11], dtype=float) # y = 2x + 1
w = Var(np.random.randn())
b = Var(np.random.randn())
lr = 0.01
for epoch in range(100):
# 前向傳播
preds = [w * Var(xi) + b for xi in x_data]
loss = sum((pred - Var(yi))**2 for pred, yi in zip(preds, y_data)) / len(x_data)
# 反向傳播
loss.backward()
# 更新參數
w.val -= lr * w.grad
b.val -= lr * b.grad
# 重置梯度
w.grad = b.grad = 0
if epoch % 20 == 0:
print(f"Epoch {epoch}: loss = {loss.val:.6f}")
print(f"訓練結果: y = {w.val:.4f}x + {b.val:.4f}")
print(f"真實: y = 2x + 1")
關鍵要點
- ✅ 自動微分 = 計算圖 + 鏈式法則,是現代 ML 框架的核心
- ✅ Forward pass 計算值,Backward pass 計算梯度
- ✅ 自動微分精準且通用,不需要手推導公式
- ✅ PyTorch 的
autograd、TensorFlow 的GradientTape都是基於相同原理 - ✅ 理解 autograd 原理後,就能理解為何 GPU 能加速——計算圖的運算可平行化
梯度下降實戰要點
梯度下降是機器學習中最核心的最佳化演算法。理解它的變體(SGD、Momentum、Adam)對於訓練深度學習模型至關重要。
核心概念
- 梯度方向是函數增加最快的方向
- 學習率決定每一步的大小
- SGD 用隨機樣本近似梯度
- Momentum 加速收斂
- Adam 結合 Momentum + RMSProp
自動微分:前向模式 vs 反向模式
AutoDiff 有兩種模式:
| 模式 | 計算方向 | 適合場景 | 複雜度 | |:----|:-------|:--------|:-----| | 前向模式 | 從輸入到輸出 | f: R → Rⁿ | O(n) | | 反向模式 | 從輸出到輸入 | f: Rⁿ → R | O(n) |
反向模式就是深度學習中的反向傳播。當你有一個 loss 函數要對大量參數微分時,一次 backward 就能算出所有梯度。
import torch
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = (x ** 2).sum()
y.backward()
print(x.grad) # [2, 4, 6]
下一章預告:線性迴歸實戰
有了 AutoDiff,訓練模型就變成循環。下一章用這套流程實作線性迴歸。