Learning Objectives
- 說明什麼是損失函數與梯度,並寫出梯度下降的更新規則
- 用 numpy 寫梯度下降擬合一條直線,和最小平方法的結果比較
- 從損失曲線判斷學習率太小、太大或合適
- 說明特徵尺度為什麼會影響梯度下降
沒有公式可以用的時候
上一課的最小平方法有公式,一步就算出最好的直線。但接下來的羅吉斯迴歸、神經網路都沒有這種公式,要找到最好的參數,得換一個方法。
想像你在濃霧裡的山谷,看不到谷底在哪裡,只感覺得到腳下的地往哪邊傾斜。最合理的做法是:往下坡的方向走一小步,再重新感覺一次,一直重複。這就是梯度下降(gradient descent)。
幾個名詞對應起來:
| 比喻 | 機器學習 |
|---|---|
| 山谷的高度 | 損失函數(loss function),例如 MSE,越低越好 |
| 你的位置 | 模型的參數,例如 w、b |
| 腳下的坡度 | 梯度(gradient),告訴你往哪個方向損失會增加 |
| 一步走多遠 | 學習率(learning rate) |
梯度指向損失增加的方向,所以要往反方向走。每一步的更新規則只有一行:
新的 w = 目前的 w − 學習率 × 梯度
先用最簡單的函數試試看:損失是 (w − 3)²,我們知道最低點在 w = 3,看看梯度下降找不找得到。它的梯度是 2(w − 3)。
逐步看:往下坡走八步
從 w = 0 出發,學習率 0.1。注意兩件事:g 的正負怎麼決定 w 往哪邊走,以及越接近谷底,每一步走得越短。
學習率:一步要走多遠
學習率是你要自己決定的值(這種值叫超參數,hyperparameter),不是模型學出來的。它影響很大:
| 學習率 | 會發生什麼 | 損失曲線的樣子 |
|---|---|---|
| 太小 | 每一步都很小,走很久還到不了 | 緩緩下降 |
| 合適 | 幾步就接近谷底 | 快速下降後變平 |
| 太大 | 跨過谷底,跳到對面更高的地方,越跳越高 | 上下跳動或一路上升,這叫發散(diverge) |
所以訓練模型時一定要看損失曲線:第幾步對損失是多少。下面同一個函數、同一個起點,只換學習率。
逐步看:三種學習率
run 函數把梯度下降走 12 步,並記下每一步的損失。對應的損失曲線畫在最後。
用梯度下降擬合一條直線
現在換成上一課的問題:參數有 w、b 兩個,損失是 MSE。兩個參數各有自己的梯度,意思是「只動這個參數時,往哪個方向損失會變大」(這就是偏微分):
error = w × x + b − y 每一筆預測得太高多少
w 的梯度 = 2 × (error × x) 的平均
b 的梯度 = 2 × error 的平均
可以這樣理解 b 的梯度:如果預測值平均太高(error 平均是正的),梯度就是正的,往反方向走就會把 b 調低。w 的梯度多乘了一個 x,因為 w 對預測值的影響和 x 的大小成正比。
每一步都用全部的資料算一次梯度,同時更新 w 和 b。
逐步看:用 numpy 寫梯度下降
用上一課同樣的六個點。這裡先把讀書時數標準化(減平均、除以標準差),下面會說明為什麼。看最後兩行:梯度下降走了 10 步,和最小平方法差多少?
為什麼要先標準化
剛才的長橢圓,來源是特徵的尺度。w 的梯度裡有一個 x,x 的數字越大,w 這個方向的坡就越陡,能用的學習率就越小;可是 b 這個方向的坡沒有變陡,只能用很小的步伐慢慢走。
把 x 標準化之後(平均 0、標準差 1),兩個方向的坡度差不多,等高線接近圓形,一個學習率就能同時顧好兩邊。下面用 students.csv 的前 60 筆(模擬資料),比較同樣走 6 步的結果。
逐步看:尺度對梯度下降的影響
descend 從 w = 0、b = 0 走 6 步,回傳最後的 MSE。同時算出這 60 筆資料可能的最低損失,用來比較。
資料很多時:小批次與隨機梯度下降
上面每一步都用全部的資料算梯度,叫全批次(batch)。資料有幾百萬筆時,走一步就要算很久,所以實務上常改成:
| 名稱 | 每一步用多少資料 | 特點 |
|---|---|---|
| 全批次 | 全部 | 方向準確,但每一步很慢 |
| 小批次(mini-batch) | 一小部分,例如 32 筆 | 最常用,速度和穩定度的折衷 |
| 隨機梯度下降(stochastic gradient descent,SGD) | 一筆 | 每一步很快,路徑抖動很大 |
方向是估計出來的,所以路徑會抖動,但整體還是往低處走。scikit-learn 的 SGDRegressor、SGDClassifier 就是用這種方法訓練。
還有一件事要知道:線性迴歸的損失曲面是一個碗,只有一個最低點,從哪裡出發都會走到同一個地方。神經網路的損失曲面就崎嶇得多,起點不同可能走到不同的地方,第 21、22 課會再談。
實作:寫一步梯度下降
完成 gradient_step(x, y, w, b, lr):算出 error、兩個梯度,回傳更新後的 (w, b)。下面的程式會連走三步,每一步印出 w、b 與損失,損失應該一直下降。
小測驗:一步一步走
損失是 (w − 3)²,下面程式會印出什麼?
w = 5.0
lr = 0.1
g = 2 * (w - 3)
w = w - lr * g
print(w)
更新規則為什麼要減去梯度?
下面程式對兩個點走一步,會印出什麼?
import numpy as np
x = np.array([1.0, -1.0])
y = np.array([3.0, -1.0])
w, b = 0.0, 0.0
error = w * x + b - y
grad_w = 2 * (error * x).mean()
grad_b = 2 * error.mean()
print(grad_w, grad_b)
小測驗:判斷訓練狀況
訓練時損失曲線一路往上,最後變成 inf。最可能的原因和第一個該做的事是?
損失曲線一直緩緩下降,走了 1000 步還沒有變平。最合理的調整是?
線性迴歸已經有最小平方法的公式,為什麼還要學梯度下降?
重點整理
- 損失函數衡量模型有多差;梯度指向損失增加的方向。
- 更新規則:
新的 w = 目前的 w − 學習率 × 梯度,重複到損失不再下降。 - 學習率太小走不動,太大會發散。訓練時一定要看損失曲線。
- 特徵尺度差很多時,損失曲面會變成狹長的橢圓,能用的學習率很小。先標準化可以大幅改善。
- 全批次每一步用全部資料;小批次與 SGD 每一步只用一部分資料,適合大資料。
- 線性迴歸的損失曲面是碗狀的,梯度下降會走到和最小平方法相同的答案。
下一課回到實用的工具:用 scikit-learn 做多元迴歸,並學會讀係數、處理不同尺度的特徵。