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 道題,覆蓋損失、梯度、學習率、發散與停止規則。
繼續閱讀: