第 47 課 · 進階 · 19 分鐘

Python 梯度下降:一步一步靠近

沿梯度向下走,看看學習率為什麼重要。

梯度下降損失學習率收斂

那條線是用公式算出來的;現在 Maya 想一步步走下山去找它。學習率決定她是走到,還是衝過頭。步子太大時會怎樣?

梯度下降就是一個迴圈:測量損失,求出讓損失增加的方向,然後朝反方向移動。這個想法用幾行 Python 就能實現,而學習率會讓它的成功與失敗都變得可見。 你還會加入動量,並看到特徵尺度為什麼會改變梯度。

先定義損失

損失把模型的錯誤變成一個數字。對於過原點的直線,均方誤差比較每個預測 w * x 與真實 y。只有先定義「更好」是什麼意思,模型才知道往哪裡改。

def loss(w, xs, ys):
    return sum((w * x - y) ** 2 for x, y in zip(xs, ys)) / len(xs)

print(loss(1, [1, 2], [2, 4]))

用一個數字描述目前模型

計算梯度

導數告訴你損失相對於 w 的斜率。梯度為負,說明增大 w 會降低損失,所以更新時減去梯度。係數 2 來自對平方項求導。

def gradient(w, xs, ys):
    return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)

print(gradient(1, [1, 2], [2, 4]))

損失上升最快的方向

走一步,再重複

學習率控制步子大小。每輪都在新的 w 上重新計算梯度,再更新一次。對於這條乾淨的資料線,估計值會逐漸靠近 2,而整個過程不需要提前知道答案。

def gradient(w, xs, ys):
    return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)

xs = [1, 2]
ys = [2, 4]
w = 1.0
for _ in range(3):
    w -= 0.1 * gradient(w, xs, ys)
print(round(w, 3))

小步走向更好的配適

學習率會讓迴圈失控

學習率太小會浪費迭代;太大會衝過谷底甚至發散。始終檢查損失曲線,設定步數上限,並在更新量或改進量可以忽略時停止。

def gradient(w, xs, ys):
    return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)

def train(learning_rate, xs, ys):
    w = 1.0
    for _ in range(5):
        w -= learning_rate * gradient(w, xs, ys)
    return w

xs = [1, 2]
ys = [2, 4]
print(round(train(0.1, xs, ys), 2))
print(abs(train(1.5, xs, ys)) > 1000)

同一組資料,完全不同的結果

動量平滑重複方向

動量保留一個會累積過去梯度的速度。它可以沿著一致的方向走得更快,並抑制雜訊造成的來回擺動,但也可能在最小值附近過衝。動量是另一個需要驗證的超參數,不保證更快收斂。

def gradient(w, xs, ys):
    return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)

xs = [1, 2]
ys = [2, 4]
w = 0.0
velocity = 0.0
for _ in range(10):
    velocity = 0.9 * velocity - 0.1 * gradient(w, xs, ys)
    w += velocity
print(round(w, 3))

先累積方向,再邁步

特徵尺度會改變梯度

把輸入乘以十會大幅改變損失曲面和梯度。同一個學習率不再等價。應標準化特徵,或按尺度調整學習率,並在長時間訓練前檢查最初幾個損失值。

def gradient(w, xs, ys):
    return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)

for factor in [1, 10]:
    xs = [factor, 2 * factor]
    ys = [2 * factor, 4 * factor]
    print(factor, round(gradient(1.0, xs, ys), 1))

同一個學習率並不具備尺度不變性

本課要點

  • 損失把預測誤差變成最佳化目標。
  • 梯度指向損失增加的方向,所以更新時相減。
  • 學習率控制每次更新移動多遠。
  • 學習率過大可能發散,過小則可能很慢。
  • 記錄損失,並設定步數上限或收斂門檻值。
  • 動量累積方向;特徵縮放會改變有效學習率。

常見錯誤

  • 目標是最小化損失,卻把梯度加上去。
  • 參數已經改變,卻繼續使用舊梯度。
  • 只看最終參數,不看損失曲線。
  • 改變特徵單位後繼續沿用原學習率,並假定收斂行為不變。
輪到你

在同一組資料上以學習率 0.1 更新十次,列印最終權重,保留三位小數。

動手試試

改改變數、先預測輸出,再執行程式碼。第一次點擊「執行程式碼」時會下載 Python 執行時,可能需要幾秒鐘。

瀏覽器內 Python本機執行 · 無需帳號
首次執行會從 CDN 下載 Pyodide。修改後點擊「執行程式碼」。

練習本課

12 道題,作答後立即看到解釋。

查看 12 道題答案開始練習
← 線性回歸樸素貝葉斯 →