L1.1 十個房仲各看一百間房:Bias 與 Variance
本篇重用M1.0兩台體重計:Gaussian 的線性組合·M1.1從鞋子猜身高:Conditional Expectation·M1.2天氣預報該報幾度:MSE 的最小值是 Conditional Expectation
起點:十個房仲,同一間房
台北有十位資深房仲,本領一樣、方法一樣。差別只在他們各自看過的一百間房子不同——同一個市場裡隨機抽的一百間。
現在把同一間房子帶去給十個人估。你會得到十個數字。
問題一:這十個數字散得多開? 如果從 2,700 萬到 3,100 萬,那表示「他看過哪一百間」對答案影響很大——換一份資料就換一個答案。
問題二:這十個數字的平均,會偏嗎? 如果十個人平均起來是 2,880 萬而真實價值是 2,900 萬,那有一個 20 萬的系統性偏差——這個偏差不會因為找第十一個房仲而消失,因為它是「這套方法」本身的偏差,不是誰的運氣。
兩個問題是獨立的。一個方法可以很穩定但一直偏(十個人都說 2,600 萬);也可以平均起來很準但每個人差很多(平均 2,900 但散佈 ±400 萬)。兩種都不好,但要修的東西完全不同。
這一篇把 背考古題的學生:過擬合與欠擬合 裡定性的「過擬合/欠擬合」換成三個可以分別算出數字的量。
十個房仲對同一間房的估價,會散得多開?
平均起來會偏嗎?
這兩個問題為什麼一定要分開問?
課堂提問Q1
「十個房仲」在數學上是什麼?期望是對什麼取的?把 拆成三塊——每一塊分別對應上面的哪個問題?
先想一想,再展開看整理後的答案
十個房仲 = 十份訓練資料。 訓練集 本身是隨機的(從 抽 筆),所以 是一個隨機函數:換一份資料就換一個模型。房仲的「本領」是演算法(假設空間+損失+最佳化),十個人共用;不同的是餵給它的資料。
期望對兩件事取。 固定一個查詢點 : 的隨機(同一個 的實際成交價本來就有波動)與 的隨機(抽到哪一百間)。兩者獨立。
三項分解。 令 (十個房仲的平均答案, 的版本),(M1.2 說的最佳答案):
bias 是「平均起來偏多少」,variance 是「換一份資料會抖多少」,不可約是「同一個 的 本身就會變」。 三項都非負,所以每一項都是總誤差的下界之一——想要總誤差小,三項都得小。
三塊,各由不同的旋鈕控制
- 不可約誤差 :由問題本身決定。換模型沒用,加資料沒用。唯一能動它的方式是換問題——加入新的特徵,讓原本「隨機」的部分變成可預測的(房子的漏水史、賣方的急迫程度)。這一項在共用 toy 裡正好是 。
- bias²:由假設空間決定。 裡最好的那個離真相多遠,這是 L0 的 approximation error 在單點上的樣子。加資料壓不動它——十萬個只會畫直線的房仲,平均起來還是一條直線。
- variance:由假設空間的大小與資料量的比值決定。空間越大、資料越少,換一份資料換一個模型的程度越劇烈。這一項是 L0 的 estimation error,而且加資料對它非常有效。
三個旋鈕解釋了為什麼「模型不夠好」需要先診斷。同樣的症狀(測試誤差高),bias 主導時要加容量,variance 主導時要加資料或減容量,不可約主導時該做的是接受它並停止投入——很多專案在這一格燒掉幾個月。
一個立刻可用的推論。十個房仲的平均比任何單一房仲穩,但如果他們都只會畫直線,平均起來還是直線。這是 bagging、random forest、以及深度學習裡權重平均之所以有效、也之所以有極限的原因。
平均多個模型(ensembling)只壓 variance,不壓 bias。
把總誤差拆成三塊,再算出 variance 的量級
固定 ,寫 、、。把 拆成三段:
平方後取期望。三個交叉項都是零:第一段對 的條件期望為零且與 獨立;第三段對 的期望為零;第二段是常數。於是
這與 天氣預報該報幾度:MSE 的最小值是 Conditional Expectation 的三行證明是同一個技巧(拆成「均值為零的部分」加「常數部分」,交叉項消失),只是那裡對 分解,這裡多了一層對 的分解。
線性模型有一個乾淨的量級公式。設計矩陣固定、噪聲變異數 、參數個數 (本篇的 一律留給分布),最小平方估計在訓練點上的平均 variance 是
兩個依賴一目了然:參數越多,variance 線性上升;資料越多,線性下降。 這是「 要跟 一起長」這句老話的來源,也是下面實測會驗證的公式。
注意這個分解只對平方損失這麼漂亮
三項分解依賴平方損失的代數(交叉項為零)。換成 0–1 損失或 cross-entropy,「bias」與「variance」仍然可以定義(Domingos 2000 給了一個統一的框架),但它們不再單純相加:分類問題裡 variance 有時幫得上忙——一個平均起來偏向錯誤類別的預測,只要抖得夠厲害,仍然可能有一半的時間落在正確的那一邊。
所以在分類任務上,請把 bias–variance 當成思考工具(是「方法本身偏了」還是「換份資料就變」)而不是可以逐項相加的帳。要量化,直接量你在意的那個指標在多次重抽下的平均與散佈——這也正是 同一個實驗跑三次結果都不同,該報哪一個? 要做的事。
回到情境:三種處方,與「加資料」的極限
回到十個房仲。如果十個人的估價彼此差很多(高 variance):他們的方法對「看過哪一百間」太敏感。修法是讓每個人多看一些(加資料)、或讓方法更受限(減容量、加正則化),或把十個人的答案平均起來(ensembling)。
如果十個人很一致但一起偏低(高 bias):多找一百個房仲、每人多看一千間,都不會改善。要換方法——加入他們現在沒在看的特徵,或允許更豐富的調整規則。
如果十個人一致、也不偏,但每次的實際成交價本來就在 ±5% 之間跳(不可約):這是市場本身的隨機。此時該做的是改變目標——報一個區間而不是一個數字,或者去找能解釋那 5% 的新資訊。
這一切依賴什麼。 一,十份資料真的是獨立同分佈抽的;若十個房仲其實共用一半的案例,variance 會被低估。二,這裡的 是「無限多份資料的平均模型」,實務上只能用有限次重抽估計,重抽次數不夠時 bias 與 variance 的估計本身就有 variance。三,平方損失(見上面的 Details)。
回到情境:把三塊量出來
固定 30 個位置(只重抽噪聲,這樣分解裡沒有「設計也在變」這個額外來源),每個 degree 重抽 300 份資料:
import numpy as np, warnings; warnings.filterwarnings("ignore")
from numpy.polynomial import Polynomial
from ml_toy import truth, NOISE
xs, ys = truth()
x = np.linspace(0, 1, 30) # 固定的 30 個位置
Y = np.sin(2*np.pi*x) + NOISE*np.random.default_rng(0).standard_normal((300, 30))
for d in (1, 2, 3, 5, 7, 9, 12):
P = np.array([Polynomial.fit(x, yy, d)(xs) for yy in Y]) # 300 條擬合曲線
bias2 = ((P.mean(0) - ys)**2).mean() # 平均曲線離真值多遠
var = P.var(0).mean() # 每條離平均曲線多遠
print(f"degree {d:2d} bias² {bias2:.4f} variance {var:.4f}"
f" noise {NOISE**2:.4f} 總和 {bias2+var+NOISE**2:.4f}")
degree 1 bias² 0.2004 variance 0.0016 noise 0.0225 總和 0.2245
degree 2 bias² 0.2004 variance 0.0022 noise 0.0225 總和 0.2251
degree 3 bias² 0.0048 variance 0.0028 noise 0.0225 總和 0.0301
degree 5 bias² 0.0000 variance 0.0041 noise 0.0225 總和 0.0266
degree 7 bias² 0.0000 variance 0.0053 noise 0.0225 總和 0.0278
degree 9 bias² 0.0000 variance 0.0069 noise 0.0225 總和 0.0294
degree 12 bias² 0.0000 variance 0.0095 noise 0.0225 總和 0.0320
兩欄的方向完全相反,一如理論。 bias² 從 一路掉到 ( 之後,多項式已經足以逼近 到量測不出的程度);variance 從 單調爬到 。總和的谷底在 (),與上一篇 U 形的谷底一致——U 形不是一個經驗現象,它是這兩欄相加的結果。
三個細節值得停一下:
- 與 的 bias² 完全相同():二次項對 在 上幫不上忙,所以「加容量」在這一步只買到 variance、沒買到 bias。加錯方向的容量比不加更糟。
- 之後 bias² 是 :這時再減 degree 只會傷害,再加 degree 只買 variance。這一格的正確判斷是「停」,而不是繼續調。
- 不可約誤差那一欄從頭到尾是 :它不是被模型影響的量。總和永遠 ——這條地板線在讀任何訓練曲線時都該先畫出來。
驗證量級公式。 理論說 variance 。固定 ,改變 :
for n in (30, 100, 300):
x = np.linspace(0, 1, n)
Y = np.sin(2*np.pi*x) + NOISE*np.random.default_rng(0).standard_normal((300, n))
P = np.array([Polynomial.fit(x, yy, 9)(xs) for yy in Y])
print(f"degree 9, n={n:3d}: variance {P.var(0).mean():.4f}"
f" (理論 σ²(d+1)/n = {NOISE**2*10/n:.4f})")
degree 9, n= 30: variance 0.0069 (理論 σ²(d+1)/n = 0.0075)
degree 9, n=100: variance 0.0021 (理論 σ²(d+1)/n = 0.0022)
degree 9, n=300: variance 0.0008 (理論 σ²(d+1)/n = 0.0007)
三個數字都對到個位數的百分比。這條公式因此可以當成一個預算工具:想把 variance 減半,要嘛砍一半參數,要嘛加一倍資料。
加一倍資料只買得到 variance 那一項,bias 與不可約誤差不動。
刻意違反:把噪聲關掉。 令 ,不可約誤差變 、公式預測 variance 也變 。但這個結論只在「資料位置固定」時成立——若每次重抽連 的位置一起重抽(更接近真實的重新蒐集資料),高次多項式仍然抖得很厲害,因為設計本身的隨機也是 variance 的來源,而它不隨 消失。這解釋了上一篇的觀察:噪聲為零時仍然可以過擬合。做這個分解時把「什麼在重抽」講清楚,比記住公式更重要。
拖旋鈕,看三塊怎麼此消彼長
互動 demo:三塊各自由不同的旋鈕控制:加資料壓 variance、換假設空間壓 bias、雜訊那塊誰也動不了。
先消化一下
參考文獻
- Geman, S., Bienenstock, E., Doursat, R. Neural Networks and the Bias/Variance Dilemma. Neural Computation 1992.(把分解系統地用在學習演算法上的經典來源。)
- Hastie, T., Tibshirani, R., Friedman, J. The Elements of Statistical Learning, 2nd ed. Springer 2009, Ch. 7.3.(分解、線性模型的 、以及它與模型選擇準則的關係。)
- Domingos, P. A Unified Bias-Variance Decomposition and its Applications. ICML 2000.(0–1 損失等非平方損失下的推廣,以及 variance 在分類裡為何可能有益。)