專項練習 · 10 分鐘閱讀

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 求導。係數 2 和額外的 x 不是裝飾,它們決定更新的方向和大小。

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]))

指向改進方向的負斜率

練習:記錄損失歷史

只列印最終權重,看不出學習是否穩定。每次更新前後把損失加入列表,檢查序列。健康的訓練整體應下降。

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

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

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

損失應該下降

練習:比較兩個學習率

用同樣步數分別執行較小和較大的學習率。不要為了證明發散而列印一個巨大的數字,改為檢查最終值是否仍在合理範圍內。

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

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

xs = [1, 2]
ys = [2, 4]
print(abs(final_weight(0.1, xs, ys)) < 10)
print(abs(final_weight(1.5, xs, ys)) < 10)

一個收斂,一個失控

練習:加入停止規則

固定步數只是預算,不是收斂判斷。當梯度或參數更新量低於容差時停止,同時保留步數上限作為硬保護。

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(100):
    g = gradient(w, xs, ys)
    if abs(g) < 0.01:
        break
    w -= 0.1 * g
print(abs(gradient(w, xs, ys)) < 0.01)

訊號足夠小就停止

你的練習清單

常見問題

梯度下降一定能找到最優答案嗎?

不一定。它可能停在局部最小、發散,或因縮放不佳而變慢。對於簡單的凸損失,合適步長有更強的保證。

學習率怎麼選?

先取較小值,觀察損失曲線,再按數量級調整。不存在通用的最佳值。

為什麼開始時梯度是負的?

目前預測低於目標,因此增大權重會降低損失。負梯度提示更新應該增大 w。

一次只練一個更新

12 道題,覆蓋損失、梯度、學習率、發散與停止規則。

打開練習

繼續閱讀: