L0.1 差一度與差十度:損失函數是一種宣告
本篇重用M1.2天氣預報該報幾度:MSE 的最小值是 Conditional Expectation·M5.1用錯的機率表下注一年會多輸多少:KL Divergence 與 Cross-Entropy
起點:糟十倍,還是糟一百倍?
你在做明天最高溫的預報。報 25 度、實際 26 度,這是一個小錯;報 25 度、實際 35 度,這是一個大錯。
大錯比小錯糟幾倍?
差距是十倍。多數人的第一反應是「當然是糟十倍」。但如果你用的是均方誤差,你剛剛宣告的是糟一百倍——。如果用絕對誤差,才是十倍。如果你在意的是「有沒有錯到讓人沒帶外套」,那可能是「差 3 度以內都不算錯,超過就一律很糟」,那既不是十倍也不是一百倍。
沒有一個實驗能告訴你哪個對。這是宣告,不是發現。 資料裡沒有「差 10 度有多痛」這個資訊——那來自使用這個預報的人。
而一旦宣告完,剩下的事情就不再自由了:天氣預報該報幾度:MSE 的最小值是 Conditional Expectation 已經證明,平方誤差下的最佳策略是條件平均,換絕對誤差就變中位數。這一篇處理的是這個選擇在實務上的三個後果:學到的東西不同、對髒資料的抵抗力不同、以及好不好訓不同。
選損失就是選你要學的統計量。
差 10 度比差 1 度糟十倍,還是糟一百倍?
這個比例是資料告訴你的,
還是你自己宣告的?
課堂提問Q1
把「宣告」翻成數學:損失是什麼型別的物件?「換一個損失會學到不同的東西」這句話,精確地說是什麼在變?如果我只是想「訓練得順一點」而換損失,我付出了什麼?
先想一想,再展開看整理後的答案
型別。 損失是 ,把「你說的」與「實際的」變成一個扣分。注意第一個參數不一定和第二個同型:分類時 但 是一個機率。
在變的是誰。 對每個 ,最佳答案是
也就是「在這個桶子裡,哪個回答的期望扣分最小」。這個 只由 與桶內分佈決定。所以換 不是換一個計分板——是換掉你要模型逼近的那張表: 是平方就是 ,是絕對值就是中位數,是 cross-entropy 就是整條 。
為了好訓而換損失的代價。 你把目標換掉了。有時無害(Huber 在小殘差上就是平方,逼出的東西和平均很接近),有時致命(把 0–1 錯誤率換成平方損失去學機率,最佳解仍然是機率,但梯度會在錯得很自信的地方消失——本篇實作會把這個現象跑出來)。所以要分開問兩件事:這個損失逼出的統計量是我要的嗎?它好不好訓? 兩題都要過。
扣分表:你對錯誤的偏好長什麼形狀
把損失畫成「殘差 對應扣多少分」的一條曲線,三種常見宣告的形狀差別一眼可見:
- 平方 :越錯,每多錯一單位的邊際懲罰越大。它宣告「大錯特別不能忍」,代價是一顆離群值就能買走整條曲線——把它拉過去比讓其他二十九點各差一點更划算。
- 絕對 :每多錯一單位,代價固定。大錯不會被額外放大,所以離群值只算一票。
- Huber: 小的時候用平方(在合理範圍內保留平滑與「大錯更糟」的直覺),超過 之後切成線性(不讓極端值買走曲線)。它是把「我相信多數點,但不相信極端點」寫成式子。
同一個形狀語言也解釋了分類:cross-entropy 在「你說機率 0.01、答案卻是這一類」時扣分是 ,說 0.0001 就扣 ——錯得越自信,罰得越重且沒有上限。平方損失 的上限是 :錯到底也只扣一分。
換一張扣分表,最佳答案會跑到哪裡
固定 ,令 的條件分佈是 。逐項求 :
| 損失 | 最佳回答 | 一句話理由 |
|---|---|---|
| 條件平均 | 三行證明,見 M1.2 | |
| 條件中位數 | 導數 | |
| (pinball,低估罰 、高估罰 ) | 條件 -分位數 | 同上,改成 |
| (cross-entropy) | 整條條件機率 | 期望扣分 ,見 M5.1 |
| (0–1) | 條件眾數 | 猜中機率最大的那一類 |
最後一列的推導值得單獨看,因為它解釋了為什麼分類幾乎一律用 cross-entropy。把期望扣分寫開:
也就是說最小化 cross-entropy 就是最小化到真實條件分佈的 KL(M5.1 已經把這個等式證過)。這種「唯一最小點恰好是真實分佈」的損失叫 strictly proper scoring rule;平方損失作用在機率上(Brier score)也是 proper 的,0–1 錯誤率則不是——它只在乎哪一類最大,所以模型可以把機率報得離譜而分數不變。如果你之後要用機率做決策(門檻、期望成本、拒答),這個差別是關鍵的。
展開細節proper 不等於好訓:平方損失在分類上的梯度會消失
用 sigmoid 作輸出,對 logit 微分:
當模型錯得很自信( 但 ):cross-entropy 的梯度是 ,滿檔;平方損失的梯度多了一個 的因子,整個梯度被壓成幾乎零。錯得越離譜,修正的力道越小——這是最壞的組合。
兩個損失的最佳解是同一個(都是真實機率),但一個從錯誤的地方走得回來,一個走不回來。這就是「宣告」與「最佳化性質」必須分開檢查的原因:本篇的第二段實作會把它跑成一個 400 步之後準確率 對 的對比。
回到情境:預報員、房仲、與「先用 MSE 再說」
預報員。 電視台的計分若是平方誤差,最佳報法是條件平均;若觀眾真正在意的是「有沒有被冷到」(低估比高估痛),那正確的宣告是不對稱的 pinball,最佳報法變成一個偏低的溫度分位數。用 MSE 訓練然後再手動「調低一點」,是在補一個當初宣告錯的洞。
房仲。 上一篇留下的問題現在有答案了:如果客戶對低估的痛苦是高估的三倍,宣告 的 pinball,模型會直接學條件的 75% 分位數,不需要事後修正。
「先用 MSE 再說」什麼時候可以。 當三件事同時成立:你要的是條件平均; 沒有嚴重離群值;輸出不是機率。這三條在很多回歸任務上是真的——所以預設值有它的道理。危險的是把「常用」誤讀成「中性」:MSE 從來不是中性的,它是一個關於「大錯糟得特別快」而且「所有點都可信」的強宣告。
這一切依賴什麼。 一, 真的代表你的偏好,而不是你會微分的東西。二,離群值是雜訊而不是訊號——如果那三個「離群值」其實是海嘯前兆,穩健損失會把最重要的資訊丟掉。三,最佳解存在且模型有能力逼近它;否則對照表講的是理想目標,不是你會拿到的東西。
回到情境:換一個損失,同一份資料
第一段:離群值。 共用 toy 的 30 個點,把其中三個往上推 (想像三筆打錯的成交價),用三次多項式配三種損失擬合,再量擬合曲線到真值 的距離:
import numpy as np
from scipy.optimize import minimize
from ml_toy import toy_1d, truth
x, y = toy_1d(); y = y.copy()
y[[5, 14, 23]] += 2.5 # 三筆打錯的資料
xs, ys = truth()
V, Vs = np.vander(x, 4, increasing=True), np.vander(xs, 4, increasing=True)
def fit(rho): # rho 把殘差變成扣分
w0 = np.linalg.lstsq(V, y, rcond=None)[0]
return minimize(lambda w: rho(V @ w - y).mean(), w0, method="Powell",
options=dict(xtol=1e-10, ftol=1e-12, maxfev=200000)).x
losses = {
"平方 (MSE)": lambda r: r**2,
"絕對 (MAE)": lambda r: np.abs(r),
"Huber δ=0.3": lambda r: np.where(np.abs(r) <= .3, .5*r**2, .3*(np.abs(r) - .15)),
}
for name, rho in losses.items():
w = fit(rho)
print(f"{name:12s} 到真值的均方距離 {((Vs @ w - ys)**2).mean():.4f}")
平方 (MSE) 到真值的均方距離 0.1005
絕對 (MAE) 到真值的均方距離 0.0183
Huber δ=0.3 到真值的均方距離 0.0148
同一份資料、同一個假設空間、同一個最佳化器,只換扣分表,離真相的距離差了五倍以上。 三筆髒資料佔 10%,在平方損失下它們的話語權遠超過 10%——因為每一筆的扣分和距離的平方成正比,把曲線拉向它們是「划算」的。Huber 比 MAE 再好一點,因為在乾淨的那 27 點上它仍然是平方(更有效率),只對超過 的殘差鬆手。
第二段:宣告對了,也可能訓不動。 二維月牙 toy,同一個線性 logistic 模型、同一個學習率、同樣 400 步,只換損失,並且從兩種起點出發:
import numpy as np
from ml_toy import toy_2d
X, y = toy_2d()
A = np.c_[np.ones(len(X)), X]
sig = lambda z: 1/(1 + np.exp(-z))
def train(kind, w0, steps=400, lr=0.5):
w = np.array(w0, float)
for _ in range(steps):
p = sig(A @ w)
g = (A.T @ (p - y))/len(y) if kind == "ce" else (A.T @ ((p - y)*p*(1-p)))/len(y)
w -= lr*g
p = sig(A @ w)
nll = -(y*np.log(p+1e-12) + (1-y)*np.log(1-p+1e-12)).mean()
return ((p > .5) == y).mean(), nll
for w0, tag in (([0, 0, 0], "從零初始化"), ([0, -8, 8], "從一個很自信但錯誤的起點")):
for kind, name in (("ce", "cross-entropy"), ("sq", "平方損失")):
acc, nll = train(kind, w0)
print(f"{tag:14s} {name:14s} 準確率 {acc:.3f} NLL {nll:.4f}")
從零初始化 cross-entropy 準確率 0.890 NLL 0.2603
從零初始化 平方損失 準確率 0.865 NLL 0.2946
從一個很自信但錯誤的起點 cross-entropy 準確率 0.890 NLL 0.2610
從一個很自信但錯誤的起點 平方損失 準確率 0.180 NLL 9.0023
從零出發,兩者差不多——這是大部分教科書示範停下來的地方,於是「平方損失也行」變成一個常見的誤解。刻意違反假設:把起點換成一個「錯得很自信」的權重(模型堅信答案相反)。cross-entropy 完全恢復(),平方損失卡在 ——比亂猜還差,而且 400 步之後幾乎沒有移動。原因就是上面那個 因子:模型越自信,梯度越小,錯誤越沒有被修正的力道。
實務上「錯得很自信的起點」不是人造的怪例——深層網路的初始化、學習率一次過大之後的狀態、分佈偏移之後的舊模型,都會把你放在那裡。
proper 保證你要的東西在最小點上,不保證你走得到。
換扣分表,看最佳答案與梯度同時改變
互動 demo:換損失函數,同一批資料的最佳擬合線就跟著換——扣分表決定你追到的是平均數還是中位數。
先消化一下
參考文獻
- Gneiting, T., Raftery, A. E. Strictly Proper Scoring Rules, Prediction, and Estimation. JASA 2007.(哪些損失的唯一最小點是真實分佈,以及損失與統計量的完整對照。)
- Huber, P. J., Ronchetti, E. M. Robust Statistics, 2nd ed. Wiley 2009, Ch. 1, 7.(Huber 損失的來源、效率與抵抗力的取捨、 該怎麼設。)
- Bishop, C. M. Pattern Recognition and Machine Learning. Springer 2006, Ch. 1.5.5, 4.3.2.(Minkowski loss 一族;logistic 輸出下 cross-entropy 與平方損失的梯度差異。)