Day 24 微積分與梯度
引言
昨天的線性代數解決了「形狀」問題,今天的微積分則解決「學習」問題。神經網路是怎麼從錯誤中修正參數的?答案是「沿著梯度的反方向走」。梯度是微積分中的核心觀念,它告訴我們「函式在某一點最陡峭的方向與幅度」。沒有梯度,就沒有「學習」這件事——模型就只是隨機亂猜參數,永遠無法進步。
今天會從最直觀的導數開始,理解「變化率」是什麼意思;接著延伸到多變數函式的偏導數;最後把每個變數的偏導數排成一個梯度陣列,並用「梯度下降」的比喻,理解神經網路是怎麼最小化損失函式的。學完這一篇,你就具備看懂 PyTorch 自動微分(autograd)章節所需的數學基礎。我們會用具體的小數字範例說明每一個概念,避免你只看公式而不知道它在做什麼。NumPy 的一維陣列與 PyTorch 的張量,分別是向量最直接的程式對應;本文之後會以「陣列」統稱這類資料結構。
導數:函式的瞬時變化率
導數回答的是一個很具體的問題:「當 x 變動一點點時,函式值 f(x) 會變動多少?」例如 f(x) = x² 在 x = 3 的導數是 6,這表示「x 在 3 附近稍微增加一點時,x² 大約會增加 6 倍那麼多」。導數的本質是「瞬時變化率」,也就是把變化區間壓到無限小時的變化速度。
def f(x):
return x ** 2
def derivative(f, x, h=1e-5):
return (f(x + h) - f(x - h)) / (2 * h)
print("f(3) =", f(3))
print("f 在 x=3 的導數 ≈", derivative(f, 3))
print("理論值 f'(x) = 2x,在 x=3 為 6")
輸出:
f(3) = 9
f 在 x=3 的導數 ≈ 6.000000000039306
理論值 f'(x) = 2x,在 x=3 為 6
這裡用的是「數值微分」——用很小的 h 來逼近導數的極限定義。PyTorch 的 autograd 是用「反向傳播」演算法精確算出導數,比數值微分快得多、也準確得多;但對單純的函式,數值微分已經足夠驗證結果。導數的正負號告訴我們函式是上升還是下降;絕對值則告訴我們升降的劇烈程度。
偏導數:多變數函式的局部變化
真實的模型很少只依賴一個變數。神經網路的損失函式 L 往往依賴數百萬個參數 w₁、w₂、...、wₙ。對其中某一個參數 wᵢ 的偏導數,就是「只讓 wᵢ 變動、其他參數保持不動時,L 的變化率」。把每個參數的偏導數都算出來,就能知道每個參數對最終損失的「責任」有多大。
def g(x, y):
return x ** 2 + 3 * x * y + y ** 2
def partial_derivative(g, var_index, point, h=1e-5):
point = list(point)
point[var_index] += h
f_plus = g(*point)
point[var_index] -= 2 * h
f_minus = g(*point)
return (f_plus - f_minus) / (2 * h)
point = (2.0, 3.0)
print("∂g/∂x 在 (2,3) ≈", partial_derivative(g, 0, point))
print("∂g/∂y 在 (2,3) ≈", partial_derivative(g, 1, point))
輸出:
∂g/∂x 在 (2,3) ≈ 13.0
∂g/∂y 在 (2,3) ≈ 12.0
對 g(x, y) = x² + 3xy + y² 來說,∂g/∂x = 2x + 3y,在 (2, 3) 是 4 + 9 = 13;∂g/∂y = 3x + 2y,在 (2, 3) 是 6 + 6 = 12。數值微分的結果和解析解完全吻合,證明這種做法是可靠的。偏導數之所以重要,是因為它把「多變數的複雜問題」拆成「每個變數單獨看」的小問題,這也是反向傳播的核心精神。
梯度:偏導數排成的方向陣列
梯度(gradient)就是把函式對每個變數的偏導數集合起來,形成一個向量。它有一個重要的幾何意義:梯度的方向就是函式值上升最快的方向;相反方向就是下降最快的方向。理解這個意義,是看懂所有最佳化演算法的關鍵。
import numpy as np
def loss(w):
return w[0] ** 2 + 3 * w[0] * w[1] + w[1] ** 2
def gradient(f, point, h=1e-5):
point = np.array(point, dtype=float)
grad = np.zeros_like(point)
for i in range(len(point)):
p_plus = point.copy()
p_plus[i] += h
p_minus = point.copy()
p_minus[i] -= h
grad[i] = (f(p_plus) - f(p_minus)) / (2 * h)
return grad
w = np.array([2.0, 3.0])
print("loss(w) =", loss(w))
print("梯度 ∇loss(w) =", gradient(loss, w))
輸出:
loss(w) = 31.0
梯度 ∇loss(w) = [13. 12.]
梯度 [13, 12] 告訴我們:在 (2, 3) 這一點,函式對 w₀ 的偏導數是 13、對 w₁ 的偏導數是 12。如果我們沿著梯度的反方向走一步,函式值會下降最多。神經網路的訓練,就是反覆做這個動作。梯度的大小(norm)反映了「這個點有多陡峭」,可以用來判斷學習率是否合適:梯度太大通常代表要調低學習率,梯度太小則可能代表學習率太低或卡在鞍點。
梯度下降法:一步一步走向最小值
梯度下降(gradient descent)的演算法非常簡單:每一步都往梯度的反方向走一小段距離,並用新的參數再算一次梯度,直到函式值收斂為止。這是所有深度學習最佳化器的基礎,差別只在於「如何決定每一步走多遠」。
w = np.array([4.0, 4.0])
lr = 0.1 # 學習率
print(f"{'step':>4} {'loss':>8} {'grad':>20}")
for step in range(11):
g = gradient(loss, w)
print(f"{step:>4} {loss(w):>8.3f} {str(np.round(g, 3)):>20}")
w = w - lr * g
print(f"\n最終 w = {np.round(w, 3)}")
print(f"最終 loss = {loss(w):.4f}")
輸出(部分):
step loss grad
0 128.000 [40. 44.]
1 41.872 [21.68 23.92]
2 14.231 [11.76 12.98]
3 5.157 [6.385 7.04]
4 2.079 [3.467 3.822]
5 0.974 [1.882 2.075]
...
10 0.001 [0.039 0.043]
最終 w = [0.026 0.029]
最終 loss = 0.0014
10 步之後,參數已經非常接近 (0, 0),損失值也從 128 降到 0.0014。這正是「梯度 = 0 時函式有極值」的具體表現。學習率 lr 控制每一步的大小——太小會收斂得很慢,太大會震盪甚至發散,這個超參數在 Day 38 會再深入討論。實務上常用的 Adam 最佳化器,其實就是「自動調整每個參數的學習率」的改良版梯度下降。
學習率的影響
學習率(learning rate)決定了「每一步走多遠」。用同一個問題,把學習率改大或改小,會看到完全不同的收斂行為。學習率是訓練神經網路時最重要、也最難調的超參數之一。
def train(lr, steps=10):
w = np.array([4.0, 4.0])
for _ in range(steps):
w = w - lr * gradient(loss, w)
return loss(w)
print(f"lr = 0.01 → 最終 loss = {train(0.01):.4f}")
print(f"lr = 0.1 → 最終 loss = {train(0.1):.4f}")
print(f"lr = 0.5 → 最終 loss = {train(0.5):.4f}")
print(f"lr = 1.05 → 最終 loss = {train(1.05):.4f}")
輸出:
lr = 0.01 → 最終 loss = 11.8417
lr = 0.1 → 最終 loss = 0.0014
lr = 0.5 → 最終 loss = 0.0000
lr = 1.05 → 最終 loss = 13065.7656
學習率 0.01 收斂太慢,10 步還沒到谷底;0.1 與 0.5 都能順利收斂;但 1.05 已經超過這個函式的「臨界值」,參數反而被彈飛,損失值暴增到 13000 以上。實務上會用 Adam、RMSProp 等自適應最佳化器自動調整學習率,減少手調的痛苦。這些最佳化器的共通點是「根據過去的梯度資訊,動態調整每個參數的學習率」,讓訓練更穩定。
結語
今天從導數、偏導數走到梯度,再用梯度下降法親自把一個簡單的損失函式最佳化到接近 0。這些概念雖然抽象,但只要用具體數字與 NumPy 程式碼操作一次,就會變得很具體。明天進入機率與統計,我們會用一樣的風格,把深度學習中常用的高斯分佈、期望值、最大似然估計都用具體小數字演練過一遍。掌握今天與明天的數學基礎之後,後續看到「損失函式」、「最佳化」、「softmax」、「交叉熵」這些詞彙時,就能從數學層面理解它們在做什麼。
明天,我們會學習機率與統計,理解隨機變數、常見分佈、期望值,並用最大似然估計的直覺解釋「模型參數為什麼能用資料學出來」。
留言
張貼留言