自動微分

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) 或運算
  • = 資料依賴關係

建立計算圖後,自動微分分為兩個階段:

  1. Forward Pass(正向傳播):從輸入到輸出,計算每個節點的值
  2. 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,訓練模型就變成循環。下一章用這套流程實作線性迴歸。

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!