重抽樣:用切分與重抽評估模型及估計量

ISLP 第 5 章 — 對應講義 05
驗證集|LOOCV|k-fold CV|偏差–變異取捨|Bootstrap|1−(1−1/n)ⁿ → 0.632
向下捲動開始互動↓
📌 本頁使用方式(ISLP Ch.5|講義 05)

① 依序閱讀:每節先讀說明;遇到互動元件時,先預測結果,再操作驗證。 ② 對照來源:章節旁標示課本章節與講義主題,正文保留公式、條件與算例;計算細節、完整實作與證明預設收合,可按需要展開。來源標記可查原教材。 ③ 檢查理解:答錯時先看回饋,再回到相關說明;標為延伸的內容可留待第二輪。 ④ 最後翻關鍵詞彙卡自測術語,並用 重點速查與來源查閱。

教科書:An Introduction to Statistical Learning with Applications in Python(ISLP);
進階參考:The Elements of Statistical Learning(ESL)。章節旁的來源標記可點到本頁書目。

CONTENTS · 內容目錄
PROLOGUE · 開場

用重抽樣評估模型在新資料上的表現 教科書 · 第 5 章開頭講義 05

你已經會擬合模型了。現在的問題換成:這個模型在沒見過的資料上會有多準? 最直覺的做法是拿訓練資料算誤差,但那個數字幾乎一定太樂觀,因為模型是照著那批資料調出來的。 極端一點想:一棵長到每個葉子只剩一個樣本的樹,訓練誤差是 0,可是它什麼都沒學到。

如果手上有一大筆獨立的測試資料,事情很簡單。但真實情況通常是資料就這麼多, 再切出去一塊當測試集就不夠訓練了。重抽樣(resampling)的想法是: 反覆從同一批資料裡切出訓練/驗證組合,用它們的平均表現估計測試誤差。

交叉驗證與 bootstrap

本章介紹兩種重抽樣方法。交叉驗證(cross-validation)估計測試誤差,並用它挑選適當的模型彈性,也就是選超參數(hyperparameter)。講義的定義是:超參數是用來控制學習過程的參數,無法在把模型擬合到訓練集時順便推得。它分成兩類:

bootstrap 則用來估計參數估計值的標準差,也用在集成學習(第 8 章的 bagging)。

兩種方法的分工 交叉驗證估「預測誤差」,用來比較模型、選超參數;bootstrap 估「估計量的不確定性」,例如標準誤與信賴區間。兩者都在重抽樣,問的問題不同。
延伸閱讀:參數、超參數與兩類超參數的例子

參數(parameter)是擬合時由訓練資料直接學出來的量,例如線性迴歸的 $\beta_0,\beta_1,\ldots,\beta_p$,或神經網路的權重;最佳化程序會把它們算出來。超參數則是用來控制學習過程的設定值:在擬合模型之前就要先指定,訓練程序本身不會順便把它估出來,通常要靠驗證集或交叉驗證來選。例如 ridge regression:

$$\hat\beta=\arg\min_\beta\Bigl[\sum_{i=1}^n(y_i-x_i^\mathsf{T}\beta)^2+\lambda\|\beta\|^2\Bigr].$$

這裡 $\beta$ 是參數,$\lambda$ 是超參數:給定 $\lambda$,最佳化會解出 $\hat\beta$;但若把 $\lambda$ 也拿來最小化訓練誤差,永遠會選 $\lambda=0$,所以要另外用 CV 評估。兩類超參數的對照:

模型超參數(model hyperparameter)演算法超參數(algorithm hyperparameter)
控制什麼學什麼模型:模型的形式、複雜度或假設空間怎麼學:最佳化程序如何把模型訓練出來
例子KNN 的 $k$、多項式的次數、決策樹的最大深度、ridge 的 $\lambda$、神經網路的層數與每層神經元數學習率、batch size、epoch 數、最佳化器的選擇、momentum
改變它會改變可能的擬合結果與偏差–變異取捨改變收斂速度、穩定性,或最後停在哪個解

例如梯度下降 $\theta^{(t+1)}=\theta^{(t)}-\eta\nabla L(\theta^{(t)})$ 的學習率 $\eta$:太大可能跳過最佳點、甚至不收斂;太小則學得很慢。它不改變模型的形式,只控制最佳化怎麼走,所以算演算法超參數。這兩類的界線並不嚴格;dropout 比例既改變有效的模型,也影響訓練過程,常被視為訓練/模型兩用的超參數。實務上大家通常統稱為超參數,重點是:它們都不由擬合程序直接估出,要靠獨立於訓練的評估來選,本章介紹的正是這類評估工具。

訓練誤差與測試誤差

講義先複習兩個定義。測試誤差是用學好的方法去預測一筆沒參與訓練的新觀測時的平均誤差;訓練誤差則把方法套回訓練用的那些觀測就能算出來,通常會低估測試誤差。

推導:為什麼訓練 MSE 的期望小於測試 MSE?

以最小平方線性迴歸為例:$y=X\beta+\varepsilon$,$X$ 為 $n\times p$ 滿欄秩(含截距),誤差獨立、平均 0、變異數 $\sigma^2$。

訓練 MSE。殘差 $e=(I-H)y=(I-H)\varepsilon$,$H=X(X^\mathsf{T}X)^{-1}X^\mathsf{T}$。$I-H$ 對稱且冪等,跡為 $n-p$,所以

$$E\Bigl[\frac1n\|e\|^2\Bigr]=\frac1n E\bigl[\varepsilon^\mathsf{T}(I-H)\varepsilon\bigr]=\frac{\sigma^2}{n}\operatorname{tr}(I-H)=\sigma^2\Bigl(1-\frac pn\Bigr).$$

在同樣的 $X$ 上重新觀測的測試 MSE。新反應 $y^{new}=X\beta+\varepsilon^{new}$,$\varepsilon^{new}$ 與訓練誤差獨立。預測誤差 $y^{new}-X\hat\beta=\varepsilon^{new}-H\varepsilon$,兩項獨立,

$$E\Bigl[\frac1n\|y^{new}-X\hat\beta\|^2\Bigr]=\sigma^2+\frac{\sigma^2}{n}\operatorname{tr}(H)=\sigma^2\Bigl(1+\frac pn\Bigr).$$

兩者相差 $2p\sigma^2/n$:參數愈多、樣本愈少,訓練誤差就愈樂觀。這正是第 6 章 $C_p$ 在訓練誤差上加 $2p\hat\sigma^2/n$ 的由來。更一般的結論(模型類事先固定、取訓練誤差最小者時,期望訓練誤差不大於期望測試誤差)見第 2 章的證明。

講義補充連結:證明訓練資料上的期望 MSE 小於測試資料。

最好的解法是一個夠大的獨立測試集,但通常拿不到。講義列出兩條替代路線:

  1. 用數學修正訓練誤差:例如 $C_p$、AIC、BIC,在訓練誤差上加一個隨模型複雜度增加的懲罰。第 6 章模型選擇準則會詳細介紹。
  2. 保留一部分訓練觀測不參與擬合,再把方法套到這些被保留的觀測上估計測試誤差。本章的驗證集法與交叉驗證都屬於這一類。

講義補充連結:scikit-learn:交叉驗證總覽。

本章你會學到的三件事 1. 驗證集法(validation set):切兩半,最簡單,但答案會隨切法跳動。
2. 交叉驗證(cross-validation):LOOCV 與 $k$-fold,讓每個樣本都輪到當一次測試資料。
3. Bootstrap:有放回地重抽,不靠公式就能算出任何估計量的標準誤。
切幾次每次訓練用多少有隨機性嗎主要用途
驗證集法1約 $n/2$有,換 seed 就變快速粗估
LOOCV$n$$n-1$沒有(分割唯一)小資料、要穩定
$k$-fold CV$k$(通常 5 或 10)$n(k-1)/k$有,但比驗證集小很多選模型的標準做法
Bootstrap$B$(通常 1000)$n$(有放回)有,$B$ 大就穩估標準誤與信賴區間
QUIZ · 為什麼不能用訓練誤差

為什麼「訓練誤差」不能拿來當測試誤差的估計?

(A) 訓練資料的樣本數太少,誤差的變異太大
(B) 模型的參數是照著這批資料挑出來的,誤差已經被最小化過,所以會系統性偏低
(C) 訓練誤差用的是 MSE,測試誤差用的是別的指標,兩者不能比
PART 01 · 驗證集法

驗證集法:觀察資料切分帶來的變動 教科書 · 第 5.1.1 節講義 05

最直接的做法:把資料隨機切成兩半,一半訓練、一半當驗證集(validation set, 也叫 hold-out set)。模型完全沒看過驗證集,所以驗證集上的 MSE 就是一個誠實的測試誤差估計:

$$\mathrm{MSE}_{\text{valid}} = \frac{1}{|V|} \sum_{i \in V} \left(y_i - \hat f(x_i)\right)^2$$

講義用 Auto 資料($n=392$)示範:把 196 筆拿去訓練、196 筆當驗證集, 用 horsepower 預測 mpg,比較線性、二次與更高次多項式 $\texttt{mpg}=\beta_0+\sum_{j=1}^d\beta_j\,\texttt{horsepower}^j$。單一次切分得到的驗證 MSE 曲線顯示二次明顯比線性好、三次以上沒有再改善。看起來很乾淨——問題是換一個切法,答案就變了。

講義補充連結:ISLP 的 Auto 資料說明。

圖表需要連網載入 Chart.js。此圖的重點:十種不同的隨機切分,畫出來是十條差很多的曲線——驗證集法的答案取決於你剛好怎麼切。
›十條淡線是十種固定切分,粗線是平均。
課程/lab 資料
怎麼看這張圖 圖 5.2 右
每條淡線是一次隨機切分(test_size=196)算出的驗證 MSE,$x$ 軸是 horsepower 多項式的次數。粗線是十次的平均。十條線的高低差距,就是驗證集法的變異。
十次切分的落點(degree 2)
最低—
最高—
全距—
平均—
結論
每一條都會告訴你「二次比一次好」。這個結論很穩。但如果你想引用一個具體數字當測試 MSE,那個數字非常不可靠。
完整實作:驗證集法(Auto,degree 1)

講義完整實作:切一半、擬合模型、算驗證 MSE

講義 05 · 驗證集法(Auto,degree 1)
Auto_train, Auto_valid = train_test_split(Auto, test_size=196, random_state=rng) X_valid = hp_mm.transform(Auto_valid) y_valid = Auto_valid['mpg'] valid_pred = results.predict(X_valid) np.mean((y_valid - valid_pred)**2)
預期輸出
np.float64(25.57387818968441)

這個 25.57 就是課本 §5.1.1 引用的數字。注意 random_state=rng——換掉它,下面的數字就會不一樣。

來源:Ch05-resample-lab-zh.ipynb
講義 05 · 換一個切法(random_state=3)
Auto_train, Auto_valid = train_test_split(Auto, test_size=196, random_state=3) MSE = np.zeros(3) for idx, degree in enumerate(range(1, 4)): MSE[idx] = evalMSE([poly('horsepower', degree)], 'mpg', Auto_train, Auto_valid) MSE
預期輸出
array([20.75540796, 16.94510676, 16.97437833])

degree 1/2/3 的驗證 MSE 從 [25.57, 22.22, 22.67] 變成 [20.76, 16.95, 16.97]。同一份資料、同一個模型,只是切法不同,數字差了快 5。但兩次都指向同一個結論:二次比一次好,三次沒有再更好。

來源:Ch05-resample-lab-zh.ipynb
講義:驗證集法的兩個缺點 1. 估計的變異很大:測試誤差的估計取決於哪些觀測剛好被分到訓練集、哪些分到驗證集,上圖十條曲線就是例子。
2. 容易高估測試誤差:只有一半的觀測用來訓練;訓練資料較少時模型通常較差,所以驗證誤差傾向高估「用全部資料訓練的模型」的測試誤差。
觀念釐清
Q:驗證集法為什麼會「高估」測試誤差?

因為它只用了一半的資料訓練。統計模型通常有愈多訓練資料,表現愈好,所以用 $n/2$ 筆訓練出來的模型,本來就比用全部 $n$ 筆訓練出來的差。你量到的是「一個比較弱的模型」的誤差,因此系統性偏高。

交叉驗證同時改善了講義列的兩個缺點:每一輪都用 $n(k-1)/k$ 筆訓練(比 $n/2$ 多),而且做 $k$ 輪再平均(變異變小)。

Q:那 random_state 到底該不該固定?

固定 seed 讓你和讀程式的人能重跑出同樣的數字,使結果可重現。

危險的做法是「試幾個 seed,挑數字最漂亮的那個報出來」。那等於用驗證集調參,報出的誤差會受到這次選擇影響。可以用 $k$-fold CV 降低單次分割對結果的影響。

QUIZ · 驗證集法

驗證集法有兩個公認的缺點。下面哪一組說對了?

(A) ① 計算量太大 ② 只能用在迴歸,不能用在分類
(B) ① 會用到測試資料的資訊 ② 需要假設誤差是常態分佈
(C) ① 估計值變異大(換切法就變)② 只用一半資料訓練,所以高估測試誤差
PART 02 · LOOCV

每次保留一筆資料的交叉驗證 教科書 · 第 5.1.2 節講義 05

驗證集法浪費了一半的資料,而且答案會跳。把它推到極端: 每次只留一個樣本當驗證集,其餘 $n-1$ 筆全部拿去訓練,做 $n$ 次再平均。 這就是留一交叉驗證(leave-one-out cross-validation, LOOCV):

$$\mathrm{CV}_{(n)} = \frac{1}{n} \sum_{i=1}^{n} \mathrm{MSE}_i, \qquad \mathrm{MSE}_i = \left(y_i - \hat f^{(-i)}(x_i)\right)^2$$

其中 $\hat f^{(-i)}$ 是「拿掉第 $i$ 筆」擬合出來的模型。這樣做有兩個好處: 每一輪都用了幾乎全部的資料(偏差很小),而且分割方式唯一——沒有隨機性, 跑一百次都是同一個數字。

輪次訓練資料驗證資料得到的量
$i=1,\ldots,n$除了第 $i$ 筆以外的 $n-1$ 筆第 $i$ 筆$(y_i-\hat f^{(-i)}(x_i))^2$
最後——把 $n$ 個平方誤差平均成 $\mathrm{CV}_{(n)}$
完整實作:LOOCV(Auto,degree 1)

講義完整實作:用 cross_validate 跑 LOOCV

講義 05 · LOOCV(Auto,degree 1)
hp_model = sklearn_sm(sm.OLS, MS(['horsepower'])) X, Y = Auto.drop(columns=['mpg']), Auto['mpg'] # 交叉驗證迭代器 cv = LeaveOneOut() cv_results = cross_validate(hp_model, X, Y, cv=cv ) cv_err = np.mean(cv_results['test_score']) cv_err
預期輸出
np.float64(24.23151351792922)

24.2315 就是 degree 1 的 LOOCV 估計。跟上面驗證集法的 25.57/20.76 比一比:LOOCV 只有一個答案,不會因為切法而跳。

來源:Ch05-resample-lab-zh.ipynb

一個好用的特例:最小平方的 LOOCV 捷徑

講義:LOOCV 的捷徑公式 對最小平方的線性或多項式迴歸(ISLP 式 5.2;ESL §5.5、§7.10), $$\mathrm{CV}_{(n)}=\frac1n\sum_{i=1}^n\left(\frac{y_i-\hat y_i}{1-h_i}\right)^2,$$ 其中 $\hat y_i$ 是用全部資料擬合一次的預測值,$h_i$ 是第 3 章的槓桿值。只要擬合一次,就能算出 LOOCV,不必擬合 $n$ 次。

它像普通的訓練 MSE,只是每個殘差除以 $1-h_i$:槓桿值愈大的點,愈能把擬合結果拉向自己,訓練殘差就愈樂觀,所以要放大得愈多。這個公式只適用於最小平方這類模型;scikit-learn 的通用 cross_validate() 不會用它,仍然真的擬合 $n$ 次。

延伸閱讀:為什麼要除以 $1-h_i$?

$h_i$ 是「自己對自己擬合值」的權重

最小平方的擬合值是 $\hat y=Hy$,$H=X(X^\mathsf{T}X)^{-1}X^\mathsf{T}$ 是帽子矩陣,$h_i=H_{ii}$ 是第 3 章學過的槓桿值。把第 $i$ 個擬合值拆開:

$$\hat y_i=\underbrace{h_iy_i}_{\text{自己對自己的影響}}+\underbrace{\sum_{j\ne i}H_{ij}y_j}_{\text{其他資料的影響}}.$$

$h_i$ 愈大,這筆資料愈能把擬合結果拉向自己,訓練殘差 $e_i$ 就愈過度樂觀。LOOCV 不允許 $y_i$ 參與預測自己,所以要把這部分影響拿掉;刪掉一筆後係數也會重新調整,最後精確的修正剛好是除以 $1-h_i$;完整推導見本節最後的「計算細節:LOOCV 捷徑與槓桿值」。

兩個數字感受修正幅度

  • 高槓桿點 $h_i=0.8$、訓練殘差 $e_i=1$:留一殘差是 $1/(1-0.8)=5$。它在完整擬合裡大幅參與了「預測自己」,真正的樣本外誤差比訓練殘差大得多。
  • 低槓桿點 $h_i=0.02$:$e_i/0.98\approx e_i$。這筆資料本來就幾乎不影響擬合,拿掉它模型幾乎不變。

講義補充連結:LOOCV 捷徑公式的證明。

LOOCV 的缺點

講義列出三點:

  1. 對資料的擾動不夠大:每次只拿掉一筆,各輪的訓練資料幾乎相同。
  2. 仍然很花時間:捷徑只適用於最小平方;一般方法要真的擬合 $n$ 次。
  3. 各折的估計高度相關:許多高度相關的數字取平均,變異降不太下來,估計可能仍有較大的變異。
延伸閱讀:三個缺點的詳細說明與平均的變異數

假設 $n=100$。LOOCV 每次都是 99 筆訓練、1 筆驗證。例如 $D_{-1}=\{2,3,\ldots,100\}$ 與 $D_{-2}=\{1,3,\ldots,100\}$ 有 98 筆完全相同,訓練出來的兩個模型通常也非常像。講義列出三個後果:

1. 對資料的擾動不夠大。每次只移除一筆,$n\to n-1$,不同輪的訓練資料幾乎一樣;相比之下,10-fold CV 每次拿掉約 10%($100\to90$),模型受到比較明顯的擾動。LOOCV 因此沒有充分觀察到「訓練樣本改變時,模型表現會怎麼變」。

2. 仍然很花時間。上面的捷徑只適用於最小平方這類線性平滑器。一般的機器學習方法沒有這種捷徑:$n=10000$ 時 LOOCV 要真的擬合 10000 次模型,10-fold CV 只要 10 次。對隨機森林、支持向量機、神經網路等方法,LOOCV 通常成本很高。

3. 各折的估計高度相關,平均之後的變異可能仍然很大。令第 $i$ 折的誤差為 $E_i$,$\mathrm{CV}=\frac1n\sum_iE_i$。若各 $E_i$ 獨立,平均的變異是 $\sigma^2/n$;但它們的訓練集幾乎相同,$\operatorname{Cov}(E_i,E_j)>0$,正相關會抵銷平均的效果(公式見下一節的「平均相關誤差的變異數」)。極端情況 $E_1=E_2=\cdots=E_n$,平均 100 次的資訊和只有 1 次差不多。所以這句話的意思不是「取平均會增加變異」,而是因為各折太相關,取平均無法像獨立樣本那樣降低變異。

三點合起來看:LOOCV 偏差較低,但各折高度相關,變異可能較高,而且計算昂貴;5-fold 或 10-fold 通常提供較好的偏差、變異與計算量取捨。

觀念釐清
Q:LOOCV 算出來的 24.23,是在估「哪個」模型的測試誤差?

嚴格說,它估的是「用 $n-1$ 筆資料訓練出來的模型」的期望測試誤差,但因為 $n-1$ 跟 $n$ 幾乎一樣,實務上直接把它當成「用全部 $n$ 筆訓練出來的那個模型」的測試誤差。

這也是為什麼 CV 的最後一步通常是:用 CV 選出超參數(例如次數 = 2),然後用全部資料重新擬合一次,交出那個模型。CV 負責比較設定,選好後再擬合最終模型。

Q:scikit-learn 的 cross_validate() 為什麼跑 LOOCV 還是很慢?

因為它是通用函式,不知道你的模型是不是最小平方,所以不使用式 5.2 的捷徑,仍然真的擬合 $n$ 次。lab 也提到:原則上最小平方的 LOOCV 可以比 $k$-fold 還快,但通用函式沒有利用這個公式。

QUIZ · LOOCV

同一份資料上,把 LOOCV 跑兩次,會得到一樣的答案嗎?

(A) 不會,因為每輪的訓練集不同,隨機性會累積
(B) 會,因為 $n$ 種「留一」分割是枚舉出來的,沒有隨機性
(C) 不一定,取決於有沒有設 random_state
計算細節:LOOCV 捷徑與槓桿值

LOOCV 捷徑何時可用?

固定的最小平方設計矩陣 $X$ 滿欄秩,而且每次刪一筆後仍滿欄秩,才能用 $e_i^{(-i)}=e_i/(1-h_{ii})$。其中 $H=X(X^TX)^{-1}X^T$、$h_{ii}=x_i^T(X^TX)^{-1}x_i$。

$h_{ii}=1$ 時分母為零,刪除後也失去滿秩,不能把 $0/0$ 設成零。若每折重新選變數、選多項式次數或調整超參數,也不能只拿選好模型的一組 $H$ 取代整個流程。固定基底的普通最小平方適用;非線性估計器沒有此一般捷徑。

證明:刪一筆殘差等於 $e/(1-h)$

令 $A=X^TX$、$b=X^Ty$。刪除第 $i$ 筆的 normal equations 為 $(A-x_ix_i^T)\hat\beta_{(-i)}=b-x_iy_i$。完整資料則有 $A\hat\beta=b$。兩式相減並整理:

$$A(\hat\beta_{(-i)}-\hat\beta)=-x_i(y_i-x_i^T\hat\beta_{(-i)})=-x_i e_i^{(-i)}.$$

左乘 $x_i^TA^{-1}$,得 $x_i^T(\hat\beta_{(-i)}-\hat\beta)=-h_{ii}e_i^{(-i)}$。而 $e_i^{(-i)}=e_i-x_i^T(\hat\beta_{(-i)}-\hat\beta)$,所以 $(1-h_{ii})e_i^{(-i)}=e_i$。平方後逐筆平均就是 LOOCV 公式。矩陣行列式引理給 $\det(A-x_ix_i^T)=\det(A)(1-h_{ii})$,也說明分母為零對應刪除後秩不足。

PART 03 · k-fold CV

折成 $k$ 份輪流當驗證集 教科書 · 第 5.1.3 節講義 05

LOOCV 要擬合 $n$ 次模型,$n$ 大就吃不消。折衷方案:把資料隨機平分成 $k$ 份, 每次拿一份當驗證集、其餘 $k-1$ 份訓練,做 $k$ 輪再平均。這就是 $k$-fold 交叉驗證:

講義的寫法:令 $C_1,\ldots,C_k$ 為各折觀測的索引集合,第 $j$ 折有 $n_j$ 筆($n$ 能被 $k$ 整除時 $n_j=n/k$)。第 $j$ 折當驗證時,

$$\mathrm{MSE}_j=\frac1{n_j}\sum_{i\in C_j}\bigl(y_i-\hat y_i^{(-j)}\bigr)^2,\qquad \mathrm{CV}_{(k)}=\sum_{j=1}^k\frac{n_j}{n}\,\mathrm{MSE}_j,$$

其中 $\hat y_i^{(-j)}$ 是拿掉第 $j$ 折擬合的模型對 $x_i$ 的預測。各折一樣大時,權重都是 $1/k$,就是常見的 $\frac1k\sum_j\mathrm{MSE}_j$;scikit-learn 的 cross_validate() 回傳各折分數,lab 直接取平均,相當於等權重,$n$ 不能被 $k$ 整除時與加權式會有些微差異。

LOOCV 其實就是 $k=n$ 的特例。實務上 $k$ 取 5 或 10——原因下一節講。

講義接著用三組模擬資料比較真正的測試 MSE、LOOCV 與 10-fold CV(ISLP 圖 5.6)。三張圖中,CV 曲線的高度有時低估、有時高估真正的測試 MSE,但最小值出現的位置大致正確。選擇模型彈性時,我們在乎的正是最小值的位置,所以 CV 即使數值不準,仍然有用。

講義:真正在乎的是最小值的位置 用 CV 選模型彈性時,重點是CV 曲線在哪個彈性達到最小,最小值本身的數字倒在其次。CV 的數值可能高估或低估真正的測試 MSE,但最小值的位置通常接近真正測試誤差最小的位置。
方法每輪訓練每輪驗證輪數分割是否唯一
5-fold4/5 的資料1/5 的資料5否;需固定分割以公平比較模型
10-fold9/10 的資料1/10 的資料10否
LOOCV$n-1$ 筆1 筆$n$是;等同 $k=n$
完整實作:10-fold CV(degree 1–5)

講義完整實作:KFold 跑 10-fold

講義 05 · 10-fold CV(degree 1–5)
cv_error = np.zeros(5) cv = KFold(n_splits=10, shuffle=True, random_state=0) # use same splits for each degree not rng here, see https://scikit-learn.org/stable/common_pitfalls.html#robustness-of-cross-validation-results for i, d in enumerate(trange(1,6)): X = np.power.outer(H, np.arange(d+1)) M_CV = cross_validate(M, X, Y, cv=cv) cv_error[i] = np.mean(M_CV['test_score']) cv_error

shuffle=True 很重要:如果原始資料有排序(例如 Auto 按年份),不打亂就會讓每一折的分佈完全不同。random_state=0 是為了讓不同 degree 用同一組分割。這樣比較才公平。這格 lab 沒存下輸出,下一節的圖用同樣設定重算了一次。

來源:Ch05-resample-lab-zh.ipynb
QUIZ · $k$-fold

比較不同模型(例如 degree 1 到 10)的 CV 誤差時,為什麼要讓每個模型用同一組折分割?

(A) 否則模型之間的差異會混進「分割不同」造成的雜訊,可能比模型本身的差異還大
(B) 因為 scikit-learn 要求 cv 物件必須重複使用
(C) 因為用不同分割會讓 CV 誤差變成有偏估計
PART 04 · k 該取多少

$k$ 的偏差–變異取捨:為什麼實務上取 5 或 10 教科書 · 第 5.1.4 節講義 05

$k$ 該取多少?講義從偏差與變異兩個方向說明:

講義的結論:$k=5$ 或 $10$ 考慮偏差與變異的取捨,實務上通常用 $k=5$ 或 $k=10$:經驗上,它們給出的測試誤差估計既不會偏差太大、也不會變異太高,計算量也只有 LOOCV 的 $5/n$ 或 $10/n$。

講義以 Auto 比較不同次數的模型:

圖表需要連網載入 Chart.js。此圖的重點:從 degree 1 到 2,CV 誤差從 24.2 掉到 19.2;之後就平了。LOOCV 與 10-fold 的曲線幾乎重疊。
›LOOCV 與 10-fold CV 在同一份 Auto 資料上的比較。
課程/lab 資料
怎麼看這張圖 圖 5.4
$x$ 軸是 horsepower 多項式的次數,$y$ 軸是 CV 估的測試 MSE。兩條線分別是 LOOCV 與 10-fold CV。兩條幾乎重疊。這在實務上很常見,也是大家願意用計算量較少的 10-fold 取代 LOOCV 的理由。
最低點
LOOCV 最小—
10-fold 最小—
兩者最大差距—
讀圖的重點
不要只看「哪個 degree 的數字最小」。degree 5 的 CV 誤差是 19.03、degree 2 是 19.25,差 0.2。這遠在雜訊範圍內。看的是曲線在哪裡「拉平」,拉平之後就選最簡單的那個。
$k$每輪訓練用偏差(高估測試誤差的程度)各折相關擬合模型次數評語
2$n/2$較大低2太粗,很少用
5$0.8n$小中5實務常用
10$0.9n$較小中10實務最常用
$n$(LOOCV)$n-1$最小很高,估計變異可能較大$n$小資料、或有式 5.2 捷徑時
觀念釐清
Q:「$k$ 愈大變異愈大」聽起來很反直覺——平均更多項不是應該更穩嗎?

平均更多項會更穩,前提是那些項彼此獨立。這裡不是。

算式上看:$\mathrm{Var}(\bar X) = \frac{\sigma^2}{k} + \frac{k-1}{k}\rho\sigma^2$。第一項隨 $k$ 變大而變小,但第二項隨 $\rho$(各折之間的相關)變大而變大。講義的論點是 LOOCV 各折訓練出的模型幾乎相同,各折估計因而正相關;$\rho>0$ 時第二項會抵銷一部分第一項隨 $k$ 減小的效果。實際的相關程度依方法與資料而定,不一定接近 1。

直覺版:LOOCV 的 $n$ 個模型幾乎是同一個模型,所以你其實只有「一個」意見被重複算了 $n$ 次。

這條式子假設每折變異 $\sigma^2$ 與兩兩相關 $\rho$ 都相同;$k$ 改變時 $\sigma^2$ 與 $\rho$ 也會一起變,詳見本節的收合補充。

QUIZ · $k$ 的取捨

為什麼實務上常選 $k=5$ 或 $10$,而不直接用 LOOCV?

(A) $k=5$ 或 $10$ 常能以較少計算提供有用比較;LOOCV的變異不保證較小,通用流程要擬合 $n$ 次
(B) 因為 LOOCV 會用到驗證資料的資訊,估計不誠實
(C) 因為 LOOCV 只能用在線性模型上
延伸閱讀:LOOCV 的變異一定比較高嗎?

講義與 ISLP 的說法是 LOOCV 的估計「傾向」有較高的變異。這是常見的經驗,但不是對每個問題都成立的定理。

  • 下式說明了正相關如何限制平均的效果;可是 $k$ 改變時,每折誤差本身的變異 $v$ 也會改變(LOOCV 每折只有一筆驗證資料,$k$-fold 每折有 $n/k$ 筆),相關結構也不同。
  • 對很穩定的學習方法(例如低次多項式的最小平方),各留一模型幾乎相同,LOOCV 與 10-fold 的估計往往很接近,前面 Auto 的兩條曲線就是例子;對不穩定的方法,差異才較明顯。
  • 實務上的建議不變:一般用 5-fold 或 10-fold;LOOCV 適合資料很少或有最小平方捷徑的情況。

講義補充連結:相關與不相關資料的平均,其變異數如何不同、LOOCV 與 $k$-fold CV 的偏差與變異。

證明:平均相關誤差的變異數

對 $K$ 個有有限變異數的折誤差 $E_1,\ldots,E_K$,由平方展開與期望線性性:

$$\operatorname{Var}\left(\frac1K\sum_k E_k\right)=\frac1{K^2}\left[\sum_k\operatorname{Var}(E_k)+2\sum_{k\lt l}\operatorname{Cov}(E_k,E_l)\right].$$

若每折變異數為 $v$、兩兩相關均為 $\rho$,則結果為 $v/K+(K-1)\rho v/K$。$\rho=0$ 才有熟悉的 $v/K$;正相關會限制平均所能降低的變異。不過不同 $K$ 的 $v$ 與相關結構也會改變,因此這個公式解釋取捨,沒有證明每個問題的 LOOCV 都比 10-fold 變異大。

PART 05 · 分類問題上的 CV

把 MSE 換成錯誤率,其餘一樣 教科書 · 第 5.1.5 節講義 05

整套邏輯搬到分類問題只要換一件事:把 MSE 換成錯誤率。

$$\mathrm{CV}_{(k)} = \sum_{j=1}^{k} \frac{n_j}{n}\,\mathrm{Err}_j, \qquad \mathrm{Err}_j = \frac{1}{n_j} \sum_{i \in C_j} I(y_i \neq \hat y_i)$$

$I(\cdot)$ 是指示函數:預測錯就是 1、對就是 0。其餘完全一樣:切 $k$ 折、輪流當驗證集、平均。 ISLP 圖 5.7–5.8 用邏輯斯迴歸加上不同次數的多項式項示範:訓練錯誤率隨彈性增加持續下降, 真正的測試錯誤率先降後升,而 10-fold CV 錯誤率雖然略為低估測試錯誤率, 最小值的位置卻和測試錯誤率接近,所以能用來挑多項式的次數;KNN 的 $1/K$ 也呈現同樣的趨勢。

延伸閱讀:StratifiedKFold 與 GroupKFold

承接講義的「分類問題的交叉驗證」與選讀的切分方式:Lab 匯入了 StratifiedKFold,但主要實作仍是 Auto 迴歸的 KFold。下面說明分層與群組切分;病人的例子是為了看清切分界線而設計的,不是 Auto 的欄位。

先問:你要預測的是新觀測,還是新病人?

StratifiedKFold(分層)依照 y 的類別安排觀測,讓各折的類別比例盡量接近整體。例如 100 筆獨立觀測有 20 筆正例,分成 5 折時,每折 20 筆約有 4 筆正例。一般分類也可以用分層,不必等到嚴重不平衡才使用;它沒有增加少數類資料,也不會解決所有抽樣不確定性。

若正例只有 3 筆卻切成 5 折,就不可能每折都有正例。折數與評估指標要配合各類的筆數;ROC AUC 等指標在只有單一類別的驗證折上無法計算。分層可避免部分不良切分,但不能保證折分數的變異完整反映母體的不確定性。

GroupKFold(分組)依照 groups 的群組編號安排整組觀測。同一病人的 5 次就診,應一起放在訓練或驗證的一側;每一折的兩側沒有共用病人,各群組在一輪 CV 中恰好當一次驗證資料。若逐筆隨機分折,模型可能在驗證時認出訓練過的病人;這樣的分數回答的是「熟悉病人的新紀錄」,不適合用來宣稱「新病人的表現」。至少要有 n_splits 個不同群組;群組大小不同時,驗證筆數也可能不一樣。

分層與分組保護的是不同界線。分層不保證病人隔離;分組不保證類別比例相同。若兩者都需要,用 StratifiedGroupKFold:先確保群組不跨兩側,再嘗試維持類別比例。若正例都集中在少數大群組,比例可能無法平衡,仍應列出各折的群組數與各類筆數。

資料情境切分器切分時保留什麼
獨立的迴歸觀測KFold(shuffle=True)讓觀測分散到各折
獨立的分類觀測StratifiedKFold各折類別比例盡量接近整體
同一病人/使用者有多筆紀錄GroupKFold同群組不跨訓練與驗證
分類且同一實體有多筆紀錄StratifiedGroupKFold群組隔離,並盡量維持類別比例
預測未來的時間序列TimeSeriesSplit用較早資料預測較晚資料;必要時留間隔

講義補充連結:scikit-learn:交叉驗證與資料相依性、StratifiedKFold:分層切分、GroupKFold:群組隔離、StratifiedGroupKFold:同時考慮類別與群組。

觀念釐清
Q:時間序列為什麼不能用普通的 $k$-fold?

因為隨機切折會讓「未來」的資料進到訓練集、「過去」的資料留在驗證集。模型於是可以用 2026 年的資訊去預測 2025 年。這在部署時根本不可能發生,所以 CV 誤差會嚴重低估真實表現。

正解是只往前切(TimeSeriesSplit):用前 100 筆訓練、預測第 101–120 筆;再用前 120 筆訓練、預測第 121–140 筆…訓練集永遠在驗證集之前。

QUIZ · 分類上的 CV

一份資料裡正例只佔 2%。用普通的 KFold(n_splits=10) 會有什麼風險?

(A) 某些折可能幾乎(或完全)沒有正例,那一折的錯誤率就沒有意義
(B) 錯誤率的公式在不平衡資料上不成立,要改用 MSE
(C) $k$ 必須小於少數類別的樣本數,否則 CV 無法執行
PART 06 · CV 的對與錯

特徵選擇與交叉驗證的正確順序 進階參考 · 第 7.10.2 節講義 05

先篩選特徵再做 CV,可能讓驗證資料的資訊提前進入訓練流程;程式仍會正常執行。

講義的情境:兩類資料,5000 個預測變數、50 筆樣本。步驟一:找出和類別標籤相關最大的 100 個預測變數; 步驟二:只用這 100 個變數擬合分類器,例如 logistic regression。要估計這個分類器的測試表現, 能不能只對步驟二做交叉驗證、忘掉步驟一?不能,這個數字受到資料洩漏(data leakage)影響。

問題在於「挑特徵」這一步看過了全部的 $y$,包含後來被當成驗證資料的那些。 篩選本身就是模型訓練的一部分,它必須在每一折的訓練部分內重新執行。

下面把兩種流程放在預測變數和類別完全無關的資料上比較,真正的錯誤率是 0.5:

圖表需要連網載入 Chart.js。此圖的重點:在純雜訊資料上,先選特徵再做 CV 回報的錯誤率接近 0,正確流程則接近 0.5。
›兩根長條是 100 次模擬的平均 CV 錯誤率。
模擬示意
100 次純雜訊模擬
錯誤流程的平均 CV 錯誤率—
正確流程的平均 CV 錯誤率—
錯誤流程 10%–90% 百分位—
正確流程 10%–90% 百分位—
模擬怎麼做
每次產生 50 筆、5000 個與類別完全無關的常態預測變數,類別隨機給 0/1。錯誤流程先用全部資料挑出和類別最相關的 100 個變數,再對 logistic regression 做 5-fold CV;正確流程在每一折的訓練部分內重新挑那 100 個變數。真實的錯誤率是 0.5,因為預測變數不含任何資訊。
講義沒有規定的細節
講義只給「5000 個預測變數、50 筆、挑 100 個」的流程。分類器的正則化、分折方式與重複次數是本頁的示意設定,重點是兩種流程的差距。
重現程式:純雜訊資料上的兩種 CV 流程

下面的程式產生上圖的 100 次模擬:每次的種子是 7000 到 7099,兩種流程用同一組切分,差別只在「挑變數」放在 CV 迴圈外或迴圈內。用 numpy 1.24.4、scikit-learn 1.6.1 執行,兩個平均是 0.001 與 0.501;其他版本的數字可能有小差異,但錯誤流程接近 0、正確流程接近 0.5 的結論不變。100 次重複在一般筆電上約需數秒到數十秒。

import numpy as np from sklearn.linear_model import LogisticRegression from sklearn.model_selection import KFold, cross_val_score n_obs, p_all, k_sel = 50, 5000, 100 # 講義的 50 筆、5000 個變數、挑 100 個 def abs_corr(X, y): # 每個變數與 0/1 類別的相關係數絕對值 xc, yc = X - X.mean(axis=0), y - y.mean() den = np.sqrt((xc * xc).sum(axis=0) * float(yc @ yc)) return np.abs((xc.T @ yc) / np.where(den == 0, 1, den)) def cv_misuse_once(seed): rng = np.random.default_rng(seed) X = rng.standard_normal((n_obs, p_all)) y = rng.integers(0, 2, n_obs) # 類別與 X 完全無關 kf = KFold(n_splits=5, shuffle=True, random_state=seed) # 錯誤流程:先用全部資料(含之後的驗證折)挑變數,再只對分類器做 CV top = np.argsort(-abs_corr(X, y))[:k_sel] wrong = 1 - cross_val_score(LogisticRegression(max_iter=2000), X[:, top], y, cv=kf).mean() # 正確流程:挑變數也放進每一折,只看該折的訓練部分 acc = [] for tr, te in kf.split(X): sel = np.argsort(-abs_corr(X[tr], y[tr]))[:k_sel] model = LogisticRegression(max_iter=2000).fit(X[tr][:, sel], y[tr]) acc.append(model.score(X[te][:, sel], y[te])) right = 1 - np.mean(acc) return wrong, right res = np.array([cv_misuse_once(7000 + i) for i in range(100)]) print("錯誤流程的平均 CV 錯誤率", res[:, 0].mean().round(3)) print("正確流程的平均 CV 錯誤率", res[:, 1].mean().round(3))

想看單次結果可以只呼叫 cv_misuse_once(7000)。把 k_sel 調小,或把 p_all 減少,錯誤流程的錯誤率會上升:可以挑的雜訊變數少了,「剛好」和標籤相關的變數也比較少。

錯誤流程回報的 CV 錯誤率接近 0,彷彿找到了完美的分類器;正確流程才接近真實的 0.5。在 5000 個純雜訊變數中,總會有一些剛好和這 50 個標籤高度相關,先看全部資料挑出它們,等於讓驗證折的答案參與了選擇。

講義補充連結:scikit-learn:常見陷阱中的資料洩漏。

一句話原則 講義:若前處理依賴資料(例如標準化、one-hot 編碼),應該只在訓練資料上計算,再用那次計算得到的參數去轉換驗證與測試資料。
凡是會從資料估計任何參數的步驟——特徵篩選、標準化的平均與標準差、 遺漏值填補、過抽樣、目標編碼、PCA 降維——都應只用每一折的訓練部分估計。 在 scikit-learn 裡,把它們串成 Pipeline 再交給 cross_validate,這件事就自動對了。
觀念釐清
Q:只用 $X$ 不看 $y$ 的前處理(例如 PCA、去除零變異特徵),也要在每一折內重新估計嗎?

要,因為部署時無法先用未來資料估前處理參數。

不看 $y$ 的前處理不會直接把標籤洩漏進來,但偏差方向與大小仍取決於模型與資料,有時預測可以完全不變。但它仍然用到了驗證資料的分佈資訊(PCA 的方向、特徵的變異數),而在部署時你不可能先看過未來資料的分佈。既然包進 Pipeline 幾乎沒有額外成本,就一律包進去,不必去分辨哪種洩漏比較嚴重。

Q:我用 CV 選超參數,那 CV 誤差可以當成最終模型的測試誤差報出來嗎?

不太行,它會偏低。你已經拿 CV 誤差當目標挑過超參數了——被挑中的那組本來就是「在這組折上運氣最好」的那組,這叫做選擇偏差。

要把調參與評估分開,可以用巢狀交叉驗證(nested CV):外層折只負責評估、完全不參與調參;內層折在外層的訓練部分裡調參。或者更簡單:一開始就切出一份從頭到尾沒碰過的測試集。

QUIZ · CV 的錯用

在 5-fold CV 之前先用全部資料把特徵標準化(減平均、除標準差)。這樣有問題嗎?

(A) 沒問題:標準化只是線性變換,不改變模型的預測能力
(B) 沒問題:標準化沒有用到 $y$,所以不算洩漏
(C) 有問題:平均與標準差用到了驗證折;應折內 fit,但誤差偏差方向不能預先斷言
〔選讀〕洗牌、KFold 與 ShuffleSplit

為什麼分折前通常要先洗牌

一般的 $k$-fold CV 希望每一折都像是「從整體資料抽出的一小部分」。若原始資料有排序,不洗牌就切,各折的分布會不一樣,CV 估計就會偏掉。

  • 依反應值排序:$y_1\le y_2\le\cdots\le y_n$ 直接切 5 折,第 1 折全是最小的一群、第 5 折全是最大的一群。驗證折與訓練資料的分布差很多,量到的除了泛化誤差,還混進了排序造成的分布偏移。
  • 依類別排序:前 500 筆都是 0、後 500 筆都是 1,不洗牌的 5 折中,有些折幾乎全是類別 0,有些幾乎全是類別 1,顯然不合理。
  • 課程的 Auto:資料大致依年份排列,所以 lab 的 KFold 加了 shuffle=True。

洗牌的目的是讓每一折更接近整體資料的分布,各折之間比較公平,CV 估計也比較穩定。scikit-learn 的 KFold 預設不洗牌;KFold(shuffle=True, random_state=0) 是在分折前先洗一次,固定 random_state 可以讓不同模型用同一組分折比較。分類問題通常再用 StratifiedKFold 分層:整體是 90% 負類、10% 正類時,每一折也盡量維持接近 90/10。

但不是所有 CV 都該洗牌。資料有自然結構時,亂洗反而可能造成資訊洩漏:

  • 時間序列、縱向資料:不能洗牌後用 2023 年的資料訓練、再預測 2020 年,實務上這等於看到未來。改用只往前切的 TimeSeriesSplit。
  • 群組資料:同一病人的重複量測、同一受試者的多張影像,應整組留在同一側,改用 GroupKFold。
  • 空間資料:鄰近位置彼此相關,隨機切分會讓驗證點的鄰居出現在訓練集中。

簡單記:i.i.d. 資料通常先洗牌;有時間、群組或空間結構時不能隨便洗牌。

KFold 與 ShuffleSplit:分割與重複抽樣

講義的選讀頁另外介紹可以精確控制驗證集大小的 ShuffleSplit。兩者的差別在於:KFold 把資料切成互斥的 $k$ 份,每一筆資料恰好當一次驗證;ShuffleSplit 每次都重新隨機抽一組訓練/驗證,不同次的驗證集可以重疊。假設 100 筆資料:

  • KFold(n_splits=5):先分成 $F_1,\ldots,F_5$,第 $j$ 次拿 $F_j$ 當驗證;各驗證折互不重疊,每筆剛好被驗證一次。
  • ShuffleSplit(n_splits=5, test_size=0.2):做 5 次彼此獨立的隨機 80/20 切分。某筆資料可能多次進入驗證集,也可能一次都沒被抽到。它比較接近重複隨機切分,有時也稱為蒙地卡羅交叉驗證(Monte Carlo cross-validation)。
KFoldShuffleSplit
每次是否重新隨機抽否,先分成 $k$ 折(可先洗牌一次)是
驗證集是否重疊不重疊可以重疊
每筆是否一定當驗證恰好一次不一定
驗證集大小約 $n/k$自己指定 test_size
切分次數$k$自己指定 n_splits
分類、類別不平衡時StratifiedKFoldStratifiedShuffleSplit

即使兩者都是「5 次、驗證 20%」,概念也不同:KFold 是分割(partition),ShuffleSplit 是重複隨機抽樣。ShuffleSplit(n_splits=1) 就是驗證集法;lab 用 10 次切分得到平均 23.80、標準差 1.42(這個標準差的解讀見 lab 區塊)。下面的元件把幾種切分畫在一起,資料依類別排序,方便看出洗牌與分層的效果。

›切換切分器,觀察驗證集的位置、重疊與類別比例。
概念示意
目前的切分
切分器KFold(不洗牌)
每次驗證筆數—
從未被驗證的筆數—
被驗證兩次以上—
各次驗證中類別 1 的比例—
怎麼看這張圖
橫軸是 40 筆資料的索引,最上面一列是類別(前 24 筆類別 0、後 16 筆類別 1,依類別排好)。下面每一列是一次切分:深色=驗證、淺色=訓練。不洗牌的 KFold 會讓某幾折只含單一類別。

講義補充連結:scikit-learn:不同交叉驗證切分的視覺化、scikit-learn:關於洗牌的說明、時間序列的交叉驗證討論。

完整實作:ShuffleSplit(n_splits=1) 就是驗證集法

講義完整實作:用 ShuffleSplit 做驗證集法與重複切分

講義 05 · ShuffleSplit(n_splits=1) 就是驗證集法
validation = ShuffleSplit(n_splits=1, test_size=196, random_state=0) results = cross_validate(hp_model, Auto.drop(['mpg'], axis=1), Auto['mpg'], cv=validation); results['test_score']
預期輸出
array([23.61661707])

驗證 MSE 23.62。它和驗證集法一節圖中 random_state=0 那次切分的一次式數字相同,因為兩者用了同一種隨機切法。

來源:Ch05-resample-lab-zh.ipynb
講義 05 · 重複 10 次隨機切分
validation = ShuffleSplit(n_splits=10, test_size=196, random_state=0) results = cross_validate(hp_model, Auto.drop(['mpg'], axis=1), Auto['mpg'], cv=validation) results['test_score'].mean(), results['test_score'].std()
預期輸出
(np.float64(23.802232661034168), np.float64(1.4218450941091842))

10 次切分的平均 23.80、標準差 1.42。各次的訓練樣本重疊、彼此相關,所以這個標準差只反映換不同隨機切分的蒙地卡羅變異,不能當作平均測試分數的抽樣標準誤。

來源:Ch05-resample-lab-zh.ipynb
延伸閱讀:巢狀 CV

調參與外層評估

承接講義「Cross-validation: right and wrong」的原則:資料驅動的選擇必須放在適當的訓練折內。Lab 用 Auto 的 horsepower 預測 mpg,比較 1 到 5 次多項式的 CV 誤差;以下沿用同一資料與候選模型,延伸到自動選次數與獨立的外層評估。Nested CV 是這裡的延伸實作,並非原 Lab 已執行的步驟。

一般 CV 與 Nested CV:評估的對象不同

一般 CV 有兩種用途。超參數事先固定時,它可評估這個訓練程序;搭配 GridSearchCV 等搜尋時,它可比較設定並選超參數。若同一份 CV 分數既用來選冠軍,又用來報告冠軍的表現,就可能偏樂觀:例如很多候選設定的真實表現接近,但估計有起伏,挑最高分等於偏向挑中在這次切分中運氣好的設定。

Nested CV 評估包含選擇步驟的完整程序。它問的是:「收到一批新的訓練資料後,依照這套調參規則選模型,對未見資料的預測會多準?」因此每個外層折都要重新執行內層搜尋,不是先用全部資料選好設定,再做一次外層 CV。

做法是兩層迴圈:外層每一折先留下一份資料只供評估,在其餘資料上用內層 CV 選超參數(例如 Lab 的多項式次數),選好後擬合並對留下的那份評分;換下一個外層折時,從搜尋開始重做。各外層折選到不同次數是正常的。評估完成後,再用全部資料重新搜尋、擬合交付的模型。sklearn 的寫法是把 GridSearchCV 搜尋器直接交給 cross_validate,見下方官方範例。

Nested CV 仍要選對切分方式。群組資料兩層都要隔離群組,時間資料兩層都要遵守時間順序;巢狀結構本身不會修復病人重疊或時間洩漏。

講義補充連結:scikit-learn:一般 CV 與 Pipeline、Nested versus non-nested cross-validation:官方範例、GridSearchCV:best_params_、best_score_ 與 refit。

PART 07 · Bootstrap

有放回地重抽:直接把標準誤算出來 教科書 · 第 5.2 節講義 05

換一個問題。前面都在問「模型的預測有多準」;現在問「我算出來的這個數字有多不確定」。

抽樣分佈與自助法還不熟時,可先複習 S4 的 Bootstrap 入門。

ISLP §5.2 的例子:把錢分成比例 $\alpha$ 投資 $X$、$1-\alpha$ 投資 $Y$,要讓報酬的變異最小,最佳比例是

$$\alpha = \frac{\sigma_Y^2 - \sigma_{XY}}{\sigma_X^2 + \sigma_Y^2 - 2\sigma_{XY}}$$
證明:投資組合最小變異權重(ISLP 第 5 章習題 1)

組合 $\alpha X+(1-\alpha)Y$ 的變異數為

$$V(\alpha)=\alpha^2\sigma_X^2+(1-\alpha)^2\sigma_Y^2+2\alpha(1-\alpha)\sigma_{XY}.$$

對 $\alpha$ 微分並令為零:

$$V'(\alpha)=2\alpha\sigma_X^2-2(1-\alpha)\sigma_Y^2+2(1-2\alpha)\sigma_{XY}=2\alpha(\sigma_X^2+\sigma_Y^2-2\sigma_{XY})-2(\sigma_Y^2-\sigma_{XY})=0,$$ $$\Longrightarrow\quad \alpha=\frac{\sigma_Y^2-\sigma_{XY}}{\sigma_X^2+\sigma_Y^2-2\sigma_{XY}}.$$

二階導數 $V''(\alpha)=2(\sigma_X^2+\sigma_Y^2-2\sigma_{XY})=2\operatorname{Var}(X-Y)$。分母大於 0 時 $V$ 是開口向上的拋物線,這個臨界點就是唯一的最小值;分母等於 0 時 $X-Y$ 幾乎處處是常數,變異數不隨 $\alpha$ 改變,公式沒有定義。

公式允許任意實數權重($\alpha<0$ 或 $\alpha>1$ 代表放空)。若另外規定 $0\le\alpha\le1$,把上面的解截到 $[0,1]$ 即可;這是額外加上的限制,不是 bootstrap 的一部分。

把樣本變異數代進去就得到 $\hat\alpha$。但 $\hat\alpha$ 有多可靠? 它的標準誤沒有簡單的公式可查。理想上我們會重新蒐集 1000 份新資料、算 1000 個 $\hat\alpha$、 看它們的標準差,但我們只有一份資料。

這個名字來自英文俗語「拉著自己的靴帶把自己提起來」:只靠手上這一份資料,就估出估計量的不確定性。

延伸閱讀:bootstrap 名稱的由來

這句俗語常和吹牛男爵 Munchausen 的故事連在一起:他聲稱抓著自己的頭髮(後來的版本變成靴帶)把自己從沼澤裡拉出來。這件看似不可能的事,後來被用來形容「不靠外力、只用自己手上的東西完成」。統計上的 bootstrap 也是這樣:沒有新的資料,只從原本那一份資料重抽,就估出估計量的抽樣變異。

講義補充連結:bootstrap 這個名字的由來。

先看理想情況:如果能從母體反覆抽樣

講義先用模擬說明 bootstrap 想近似的東西。假設知道母體參數 $\sigma_X^2=1$、$\sigma_Y^2=1.25$、$\sigma_{XY}=0.5$,代入公式得真正的 $\alpha=0.6$。每次從母體抽 100 對 $(X,Y)$、算一次 $\hat\alpha$,重複 1000 次。講義(ISLP 圖 5.9、5.10)的四個例子 $\hat\alpha$ 為 0.576、0.532、0.657、0.651;1000 次的平均是

$$\bar\alpha=\frac1{1000}\sum_{r=1}^{1000}\hat\alpha_r=0.5996,$$

很接近 $\alpha=0.6$;標準差

$$\sqrt{\frac1{1000-1}\sum_{r=1}^{1000}(\hat\alpha_r-\bar\alpha)^2}=0.083,$$
講義的模擬結果 1000 個 $\hat\alpha$ 的平均 0.5996 很接近真值 0.6;標準差 0.083,也就是 $\mathrm{SE}(\hat\alpha)\approx0.083$:$\hat\alpha$ 和 $\alpha$ 的差距大約在 0.08 這個量級。

問題是真實世界只有一份資料,無法從母體反覆抽樣。bootstrap 改從手上的資料有放回地重抽,模仿這個過程。下圖左邊是從母體重抽的 $\hat\alpha$,右邊是只從一份資料做 bootstrap 的 $\hat\alpha^*$。書上圖 5.10 的 bootstrap 直方圖來自一份從同一母體模擬的資料,這裡改用課程 lab 的 Portfolio;兩者的中心不同(一個在真值 0.6 附近,一個在這份資料的 $\hat\alpha$ 附近),但散布寬度相近,而 bootstrap 要估的正是這個寬度。

圖表需要連網載入 Chart.js。此圖的重點:從母體重抽與 bootstrap 重抽的直方圖寬度相近。
›左右兩種重抽的分布寬度相近。
模擬示意
兩種重抽的摘要
真實 $\alpha$0.6
母體模擬:平均—
母體模擬:標準差—
bootstrap:平均—
bootstrap:標準差—
怎麼看這張圖
兩組長條共用同一組區間。綠色是 1000 份新模擬資料的 $\hat\alpha$,藍色是 Portfolio 一份資料的 1000 個 bootstrap $\hat\alpha^*$。本頁的模擬用了新的亂數種子,平均與標準差會接近、但不會和講義的 0.5996、0.083 逐位相同。書上圖 5.10 的 bootstrap 欄用的是一份模擬資料,這裡改用 Portfolio。

回到真實世界

上面的程序在真實資料上做不到,因為無法從原本的母體產生新樣本。bootstrap 讓電腦模仿「取得新資料」的過程:它不從母體重複抽獨立的資料集,改成從原始資料有放回地重複抽樣。每份 bootstrap 資料集和原資料一樣大,所以有些觀測會出現不只一次,有些一次也沒出現。

Bootstrap 的做法:把手上這份資料當成母體,從裡面有放回地抽 $n$ 筆, 當成一份「新」資料集,重算 $\hat\alpha^*$。重複 $B$ 次,那 $B$ 個 $\hat\alpha^*$ 的標準差 就是 $\mathrm{SE}(\hat\alpha)$ 的估計。不必指定常態分布,但重抽單位須符合資料的獨立性與研究設計。

尚未重抽。按「抽一次」後會顯示 Portfolio 的抽樣索引摘要。
圖表需要連網載入 Chart.js。圖中每一個值都是 Portfolio 的 $\hat\alpha^*$,其標準差估計 $\mathrm{SE}(\hat\alpha)$。
›從 Portfolio 的 100 對 (X,Y) 有放回抽 100 對,再重算同一個 α̂。
課程/lab 資料
虛擬碼
for b in range(B): idx = 有放回抽 n 個 θ*[b] = f(資料[idx]) SE = std(θ*)
這一次重抽
前 12 個抽樣索引—
沒被抽到(OOB)—
這次的 $\hat\alpha^*$—
累計 $B$0
累計 SE—
Portfolio 的真實結果 ISLP §5.2
用全部 100 筆算:$\hat\alpha$ = 0.5758。跑 $B=1000$ 次 bootstrap 後,$\mathrm{SE}(\hat\alpha)$ = 0.0912。下面的 lab 卡片有逐字輸出。

bootstrap 的一般圖像:真實世界與 bootstrap 世界

講義用兩個平行的世界整理這個想法:

真實世界bootstrap 世界
母體未知的分布 $P$手上的資料 $Z$(每筆機率 $1/n$ 的經驗分布)
抽一份資料從 $P$ 抽 $n$ 筆從 $Z$ 有放回抽 $n$ 筆,得到 $Z^{*r}$
算估計量$\hat\alpha$$\hat\alpha^{*r}$
重複理論上重複很多次(做不到)$r=1,\ldots,B$,電腦可以做

為什麼一定要「有放回」?有放回時,每次抽籤都面對同一個經驗分布,$n$ 筆彼此獨立,才能模仿「從母體再抽一份資料」;不放回只會得到原資料的重新排列,看不到變異。直覺與前提見下方「為什麼要有放回重抽?」收合區。

只有 3 筆資料的例子最容易看清楚:原資料是觀測 1、2、3;一份 bootstrap 樣本可能是 3、1、3,第 3 筆出現兩次、第 2 筆沒出現。重複 $B$ 次得到 $\hat\alpha^{*1},\ldots,\hat\alpha^{*B}$。

講義:bootstrap 標準誤 以 $B$ 份 bootstrap 樣本 $Z^{*1},\ldots,Z^{*B}$ 算出 $\hat\alpha^{*1},\ldots,\hat\alpha^{*B}$,它們的標準差就是 $\hat\alpha$ 標準誤的估計: $$\mathrm{SE}_B(\hat\alpha)=\sqrt{\frac1{B-1}\sum_{r=1}^B\Bigl(\hat\alpha^{*r}-\frac1B\sum_{r'=1}^B\hat\alpha^{*r'}\Bigr)^2}.$$

ISLP 用 $B=1000$ 得到 $\mathrm{SE}_B(\hat\alpha)=0.087$,和上面理想模擬的 0.083 很接近;課程 lab 用自己的種子得到 0.0912。

延伸閱讀:為什麼要有放回重抽?直覺與前提

把樣本當成母體:plug-in 的想法

我們想知道的是:如果能從母體 $P$ 一再抽出大小 $n$ 的新資料,估計量 $\hat\theta$ 會怎麼波動。$P$ 未知,bootstrap 就用手上資料的經驗分布 $\hat P$(每筆機率 $1/n$)代替它,這叫 plug-in 原則。接下來在 bootstrap 世界裡照做一次「抽樣 → 估計」:

$$\text{真實世界:}\ P\ \xrightarrow{\text{抽 }n\text{ 筆}}\ Z\ \to\ \hat\theta,\qquad \text{bootstrap 世界:}\ \hat P\ \xrightarrow{\text{有放回抽 }n\text{ 筆}}\ Z^{*}\ \to\ \hat\theta^{*}.$$

直覺是:$\hat\theta^{*}$ 在 $\hat\theta$ 周圍的散布,模仿 $\hat\theta$ 在真值 $\theta$ 周圍的散布。所以 $\hat\theta^{*}$ 的標準差可以拿來估 $\mathrm{SE}(\hat\theta)$。

為什麼要放回

  • 模擬獨立抽樣。原資料被假設成從 $P$ 獨立抽出的 $n$ 筆。從 $\hat P$ 抽一筆,就是在 $n$ 筆中等機率挑一筆;放回之後下一次抽籤面對的仍是同一個 $\hat P$,所以 $n$ 次抽籤互相獨立、同分布,結構和真實世界的抽樣一樣。
  • 不放回就沒有變異。不放回地抽滿 $n$ 筆,得到的永遠是原資料的某種排列;平均數、$\hat\alpha$ 這類與順序無關的統計量每次都相同,量不出任何抽樣變異。
  • 重複與缺席正是變異的來源。有放回時,有的觀測出現兩三次、有的沒出現,每份 $Z^{*}$ 的組成都不同,就像真實世界每次抽樣都會抽到不同的人。

這個近似成立需要什麼條件

  • 觀測彼此獨立且同分布(iid)。逐筆重抽假設每一筆是獨立的抽樣單位。時間序列、同一病人的重複測量、群集抽樣等資料,要改抽區塊或群組,讓重抽單位對應真正獨立的單位(見下方區塊 bootstrap)。
  • 樣本要能代表母體的變異。$\hat P$ 只會產生原資料裡出現過的值。樣本太小、或抽樣本身有偏差(例如只調查某一群人),$\hat P$ 就不像 $P$;bootstrap 只能重現手上資料的樣子,無法補救不具代表性的樣本,增加 $B$ 也沒用。
  • 估計量對分布的變化要夠平滑。平均數、迴歸係數、$\hat\alpha$ 這類量通常沒問題;樣本最大值這類端點統計量常不適用:bootstrap 樣本的最大值永遠不超過原資料最大值。尾端分位數在尾端觀測太少,或分位位置隨樣本量逼近端點時,近似也可能失準(見下方「bootstrap 什麼時候會失效?」)。

這些條件說的是「bootstrap 世界和真實世界夠像」。在這些條件,加上適當的矩條件(例如平均數需要有限變異數;$\hat\alpha$ 還要求分母不退化)成立時,bootstrap 不必假設常態,也不必推導 $\mathrm{SE}$ 的公式;條件不成立時,程式照樣算得出數字,但它不再描述估計量真正的波動。

bootstrap 信賴區間

百分位區間。把 $B$ 個 $\hat\alpha^{*r}$ 排序,取第 5 與第 95 百分位數,就得到近似 90% 的信賴區間。講義的例子是 $(0.43,\,0.72)$。這叫 bootstrap 百分位信賴區間(percentile interval),是最簡單的一種;估計量偏差大、樣本太少或統計量不規則時,涵蓋率可能不足。

迴歸曲線的信賴區間。講義用局部迴歸(lowess)曲線示範,步驟是:

  1. 從 $n$ 筆 $(x_i,y_i)$ 有放回地抽 $n$ 筆,成為一份 bootstrap 樣本。
  2. 在這份樣本上重新擬合曲線。
  3. 在一組固定的 $x$ 網格上記錄擬合值。
  4. 重複步驟 1–3 共 $B$ 次。
  5. 在每個 $x$ 取這 $B$ 個擬合值的第 2.5 與第 97.5 百分位,連起來就是逐點的 95% 帶。

這是逐點區間:每個 $x$ 各自有 95% 的近似涵蓋,不代表整條曲線同時落在帶內的機率是 95%。它描述平均曲線的不確定性,不包含新觀測本身的雜訊,所以不是預測區間。

講義補充連結:statsmodels 的 lowess 範例(含 bootstrap 信賴區間帶)。

延伸閱讀:用 bootstrap 建立預測區間

平均曲線的不確定性,不等於新觀測的不確定性

講義先將觀測 $(x_i,y_i)$ 配對重抽、每次重新擬合,再在同一組 $x$-grid 評估曲線。每個 grid 點的重抽分位數描述平均曲線的估計不確定性,屬於逐點區間。它不涵蓋新觀測本身的誤差,也不是整條曲線同時涵蓋的區間。

用車子資料想:我們問「100 horsepower 的車平均 mpg 大約多少」,與「下一台 100 horsepower 的車會有多少 mpg」,後者還有個別車輛的變動。即使平均曲線估得很準,新車的 mpg 仍會分散。如果只重抽係數或擬合線,再取曲線的分位數,就會漏掉新觀測的誤差。

要得到新觀測的預測區間,必須在係數的重抽變異之外,再加入新觀測本身的誤差。講義附錄只列出連結,建構方法(殘差 bootstrap、保形預測等)可從下方連結延伸閱讀。

延伸來源:statsmodels:重抽 LOWESS 曲線的逐點區間/講義的 bootstrap 預測區間討論/MAPIE 官方文件:預測區間與保形方法

資料不獨立時:區塊 bootstrap

bootstrap 每次抽一筆觀測,隱含假設觀測彼此獨立。時間序列的相鄰觀測相關,講義的做法是改成有放回地抽連續的區塊,以保留區塊內的相關結構。

延伸閱讀:區塊 bootstrap 怎麼做?

為什麼不能把時間序列的每筆觀測獨立重抽?

假設每天的溫度有正向自我相關,今天偏高,明天也往往偏高。逐筆重抽會把連續的高溫段打散,重抽序列便不再有同樣的相依性。對平均數,差別可以直接從變異數展開看見:

$$\operatorname{Var}(\bar X)=\frac1{n^2}\left[\sum_i\operatorname{Var}(X_i)+2\sum_{i\lt j}\operatorname{Cov}(X_i,X_j)\right].$$

正共變異數增加平均數的變異;逐筆 bootstrap 打散相依結構後,可能漏掉這部分變異。方向仍取決於相依型態與統計量,不能斷言所有相依資料的標準誤都必定偏小。

移動區塊 bootstrap先取長度 $l$ 的所有連續區塊,起點可為 1 到 $n-l+1$;再有放回抽區塊、按抽到的順序串接,直到至少 $n$ 筆,最後裁成 $n$ 筆。區塊內原本的先後與相關被保留,區塊接縫則不保留原序列的全部相依性。非重疊區塊、循環區塊與隨機區塊長度是不同版本,使用時要交代採用哪一種。

例如序列 1 到 8、區塊長度 3,移動區塊有 $(1,2,3),(2,3,4),\ldots,(6,7,8)$。抽到起點 4、1、5,串成 $(4,5,6,1,2,3,5,6,7)$,裁前 8 筆後才重算估計量。重複的是整個步驟,不是只拿一次亂序結果當作所有樣本。

區塊太短可能保留不了主要相依;太長又讓可重新組合的單位太少,估計不穩。平穩性、長期趨勢與季節性也會影響做法:若資料生成方式隨時間改變,單純抽區塊並不能自動處理。先判斷資料有哪些相依關係,再選區塊版本與長度,並檢查改變長度時結果是否穩定。

群組資料同樣要先決定重抽的單位。例如病人內重複觀測相關、病人之間可視為獨立,就可考慮抽整位病人而非逐次就診;是否還要在病人內重抽,取決於研究的抽樣設計。bootstrap 的重抽單位與前面 GroupKFold 的群組界線都來自資料結構,但兩者的目的不同:一個估不確定性,一個評估未見群組的預測。

延伸來源:講義的區塊 bootstrap 討論/講義的區塊抽樣應用文獻/arch 官方範例:時間相依資料的區塊與區間

延伸閱讀:配對重抽、退化樣本與重抽次數

配對資料要一起重抽

Portfolio 的一列是同一期的兩個報酬 $(X_i,Y_i)$。同一期兩個資產的共同漲跌決定共變異數,因此不能把 $X$ 與 $Y$ 分別洗牌或分別重抽;那會破壞原本的配對,回答另一個問題。每次抽的是列索引,兩欄一起跟著走。Lab 的 alpha_func(data, idx) 正是先取同一組 idx,再算兩欄的樣本共變異數。

權重公式的推導與分母為正的條件,見本節開頭 $\alpha$ 公式下方的證明。

三筆觀測的重抽例子也要區分「位置」與「不同資料」:索引 $(3,1,3)$ 仍有三筆,但第三列出現兩次,第二列完全沒出現。對樣本平均數可列出全部 $3^3=27$ 個等機率索引組合;對 Portfolio 權重,若重抽後全是同一列,樣本共變異數可能退化,估計量便未必有定義。抽出索引後,還要確認重抽資料能不能算出要研究的統計量;資料退化時,bootstrap 估計也可能失去定義。

完整實作:定義 alpha_func 並用全部 100 筆

講義完整實作:boot_SE()

講義 05 · 定義 alpha_func 並用全部 100 筆
Portfolio = load_data('Portfolio') def alpha_func(D, idx): cov_ = np.cov(D[['X','Y']].iloc[idx], rowvar=False) # idx 是列的位置 return ((cov_[1,1] - cov_[0,1]) / (cov_[0,0]+cov_[1,1]-2*cov_[0,1])) alpha_func(Portfolio, range(100))
預期輸出
np.float64(0.57583207459283)

這就是 ISLP 書上的 $\hat\alpha=0.5758$。

來源:Ch05-resample-lab-zh.ipynb
講義 05 · 一次 bootstrap 重抽
rng = np.random.default_rng(0) alpha_func(Portfolio, rng.choice(100, 100, replace=True))
預期輸出
np.float64(0.6074452469619004)

replace=True 是關鍵:有放回,所以同一筆可能被抽到好幾次,也會有一些完全沒被抽到。這一次抽出來的 $\hat\alpha^*=0.6074$,跟 0.5758 差了不少。這個「差」正是我們要量化的東西。

來源:Ch05-resample-lab-zh.ipynb
講義 05 · 通用的 boot_SE
def boot_SE(func, D, n=None, B=1000, seed=0): rng = np.random.default_rng(seed) first_, second_ = 0, 0 n = n or D.shape[0] for _ in trange(B): # 依「列的位置」抽樣:Auto 的 index 是車名且有重複, # 若用 D.index 抽標籤,D.loc 會把同名的車一次全部取出,重抽樣本會多於 n 列 idx = rng.choice(D.shape[0], n, replace=True) value = func(D, idx) first_ += value second_ += value**2 return np.sqrt(second_ / B - (first_ / B)**2)

在 Portfolio 資料上,$\mathrm{SE}(\hat\alpha)$ 的 bootstrap 估計是 0.0912。注意這支函式用 $E[\theta^2] - (E[\theta])^2$ 累加,不必存下 1000 個值;它抽的是列的位置,不是 index 標籤。Auto 的 index 是車名且有重複,依標籤抽樣會把同名的車一次全部取出,標準誤會被低估。

來源:Ch05-resample-lab-zh.ipynb
講義 05 · 用 bootstrap 估計迴歸係數的標準誤
def boot_OLS(model_matrix, response, D, idx): # 為了方便檢查,先確保 idx 是一個 NumPy array idx = np.asarray(idx) # 檢查 idx 的資料類型 (dtype) 是否為整數類型 if np.issubdtype(idx.dtype, np.integer): # 如果是整數,代表是位置 (position),使用 .iloc D_ = D.iloc[idx] else: # 否則,代表是標籤 (label),使用 .loc D_ = D.loc[idx] Y_ = D_[response] X_ = clone(model_matrix).fit_transform(D_) return sm.OLS(Y_, X_).fit().params hp_func = partial(boot_OLS, MS(['horsepower']), 'mpg') rng = np.random.default_rng(0) np.array([hp_func(Auto, rng.choice(392, 392, replace=True)) for _ in range(10)])
預期輸出
array([[39.88064456, -0.1567849 ],
       [38.73298691, -0.14699495],
       [38.31734657, -0.14442683],
       [39.91446826, -0.15782234],
       [39.43349349, -0.15072702],
       [40.36629857, -0.15912217],
       [39.62334517, -0.15449117],
       [39.0580588 , -0.14952908],
       [38.66688437, -0.14521037],
       [39.64280792, -0.15555698]])

partial() 凍結模型與反應變數兩個參數,得到 boot_SE() 需要的 (D, idx) 函式。上面是前 10 份 bootstrap 樣本的截距與斜率。

來源:Ch05-resample-lab-zh.ipynb
講義 05 · bootstrap 標準誤與公式標準誤
hp_se = boot_SE(hp_func, Auto, B=1000, seed=10) hp_se hp_model.fit(Auto, Auto['mpg']) model_se = summarize(hp_model.results_)['std err'] model_se
預期輸出
intercept     0.717
horsepower    0.006
Name: std err, dtype: float64

1000 次 bootstrap 給 $\mathrm{SE}(\hat\beta_0)\approx0.85$、$\mathrm{SE}(\hat\beta_1)\approx0.0074$,公式給 0.717 與 0.006。差異來自公式的假設:它用殘差估 $\sigma^2$,而直線模型漏掉了非線性,殘差被誇大;公式也假設 $x$ 固定。bootstrap 不依賴這些假設,但仍假設各列彼此獨立,也不保證一定比較準。

來源:Ch05-resample-lab-zh.ipynb
講義 05 · 二次模型:兩種標準誤更接近
quad_model = MS([poly('horsepower', 2, raw=True)]) quad_func = partial(boot_OLS, quad_model, 'mpg') boot_SE(quad_func, Auto, B=1000) M = sm.OLS(Auto['mpg'], quad_model.fit_transform(Auto)) summarize(M.fit())['std err']
預期輸出
intercept                                  1.800
poly(horsepower, degree=2, raw=True)[0]    0.031
poly(horsepower, degree=2, raw=True)[1]    0.000
Name: std err, dtype: float64

二次模型對資料的擬合好得多,bootstrap 與公式的標準誤因此對應得更好。

來源:Ch05-resample-lab-zh.ipynb

bootstrap 樣本只含約 63.2% 的原始樣本

有放回地抽 $n$ 次,每筆至少出現一次的機率如下;它也是不同原始觀測所占比例的期望,不代表每份樣本的固定比例。

$$P(\text{第 } i \text{ 筆有進 bootstrap 樣本}) = 1 - \left(1-\frac{1}{n}\right)^n \;\xrightarrow[n \to \infty]{}\; 1 - e^{-1} \approx 0.632$$

剩下那 36.8% 沒被抽到的樣本叫做 out-of-bag(OOB)。 它們對這一輪的模型來說是天然的驗證集。樹狀方法的 bagging 與 random forest 就靠這招 不另切驗證集就能估計測試誤差。

$n$520100$n\to\infty$
$1-(1-1/n)^n$0.67230.64150.6340$1-e^{-1}=0.6321$

這也回答了講義的問題「bootstrap 能估預測誤差嗎?」交叉驗證的 $k$ 個驗證折和訓練用的其他 $k-1$ 折沒有重疊,交叉驗證行得通靠的就是這一點。若拿每份 bootstrap 樣本訓練、用原始資料驗證,每份訓練樣本約含三分之二的原始觀測,驗證資料大量出現在訓練資料裡,會嚴重低估真正的預測誤差。只用沒抽到的觀測來驗證可以部分修正,但方法會變複雜;最後還是交叉驗證比較簡單。

講義:bootstrap 能估預測誤差嗎? 估標準誤用 bootstrap,估預測誤差用交叉驗證。
推導:為什麼每份 bootstrap 樣本約含三分之二的觀測

第一步:某筆在一次抽籤中沒被抽到。從 $n$ 筆中有放回抽一次,抽到第 $j$ 筆的機率是 $1/n$,沒抽到的機率是 $1-1/n$。

第二步:$n$ 次都沒被抽到。各次抽籤獨立,所以第 $j$ 筆完全不在 bootstrap 樣本裡的機率是 $(1-1/n)^n$。

第三步:取極限。由 $\lim_{n\to\infty}(1-1/n)^n=e^{-1}\approx0.368$,第 $j$ 筆至少出現一次的機率趨近 $1-e^{-1}\approx0.632$。

第四步:期望的不同觀測數。令 $I_j$ 表示第 $j$ 筆是否至少出現一次,則一份 bootstrap 樣本中不同觀測的個數是 $\sum_jI_j$,期望為 $n\{1-(1-1/n)^n\}\approx0.632n$,大約三分之二。各 $I_j$ 不獨立也不影響這個期望;每一份樣本的實際比例會在 0.632 附近起伏。

講義補充連結:為什麼每份 bootstrap 樣本平均約含三分之二的觀測。

延伸閱讀:重抽重疊、OOB 與預測誤差

為什麼「bootstrap 訓練、原始資料驗證」會重疊?

上方的推導已說明:每份重抽資料平均只含約三分之二的不同觀測,所以「重抽資料訓練、全部原始資料驗證」有大量重疊。補救的方向是只對這次沒抽到的 OOB 觀測預測,避開直接的觀測重疊。

例如原始索引 1 到 5,抽到 $(1,1,3,4,4)$,這次的 OOB 是 2、5。下一次抽到 $(2,3,3,5,5)$,OOB 是 1、4。各次可評估的資料與數目都不同;同一觀測會在多個重抽模型的 OOB 出現,模型也不是彼此獨立的。

OOB 解決的是訓練/評估重疊,並沒有讓 bootstrap 訓練集等同於 $n$ 筆獨立的新觀測。不同訓練規模、重複列、調參使用 OOB 以及如何平均分數,都會影響估計的解讀。講義因此以 CV 作為本章預測誤差評估的主線;第八章 bagging/random forest 再使用 OOB 對集成程序做內部評估。

觀念釐清
Q:$B$ 要取多少?

估標準誤時 $B=1000$ 通常就很夠;要估信賴區間的尾端分位數(例如 2.5% 與 97.5%),$B$ 建議拉到 2000 以上,因為尾端需要更多樣本才穩。

注意 $B$ 大只會讓「bootstrap 對真實 SE 的估計」更穩定,不會讓原始資料變多。$n$ 小的時候 bootstrap 本身就不可靠,拉高 $B$ 也救不了。它只是把同一份資料提供的資訊算得更精確而已。

Q:bootstrap 什麼時候會失效?

常見的三種情況:估計量依賴極值(bootstrap 樣本的最大值永遠不超過原資料的最大值)、資料不獨立(要改用區塊 bootstrap)、$n$ 很小。背後的原因見上方「為什麼要有放回重抽?直覺與前提」。

bootstrap 的主線是:以觀測為單位有放回重抽、每次重算同一估計量,再由這些估計值的散布估標準誤。平均曲線的信賴區間和新觀測的預測區間不同,後者還要包含新觀測雜訊。下面的延伸說明預測區間與講義附錄的其他重抽樣方法。

QUIZ · Bootstrap

$n=100$ 時,一筆特定的觀測值完全沒有出現在某個 bootstrap 樣本裡的機率約為多少?

(A) 約 0.01
(B) 約 0.366
(C) 0,因為有放回抽 $n$ 次一定會抽到每一筆

講義附錄:其他重抽樣方法

講義最後的附錄列出 jackknife 與置換檢定,作為 bootstrap 以外的重抽樣工具;兩者都是選讀,可展開閱讀。

延伸閱讀:Jackknife 的標準誤

刪掉一筆,量出估計量對每筆資料的敏感度

Jackknife 不用隨機重抽,而是第 $i$ 次刪掉第 $i$ 筆資料,得到 $\hat\theta_{(-i)}$,總共 $n$ 次。令刪一估計的平均為 $\bar\theta_{(-)}$,其標準誤估計為

$$\widehat{SE}_{jack}=\sqrt{\frac{n-1}{n}\sum_{i=1}^n(\hat\theta_{(-i)}-\bar\theta_{(-)})^2}.$$

公式前面的 $(n-1)/n$ 用來校正刪一估計值的變動量。各次只刪一筆,刪一估計值的變動通常比 $n$ 筆樣本重新抽出的變動小,不能直接把 $n$ 個刪一估計的普通標準差當標準誤。

樣本平均數能精確驗算:$\bar x_{(-i)}=(n\bar x-x_i)/(n-1)$,刪一估計的平均仍是 $\bar x$。代入上式得到 $\widehat{SE}_{jack}=s/\sqrt n$,與獨立觀測的樣本平均數標準誤一致。例如資料 $(2,4,9)$,平均 5,$s^2=13$,標準誤是 $\sqrt{13/3}\approx2.082$。

Jackknife 常適用於對單筆資料影響平滑的統計量;中位數、樣本最大值等非平滑或端點問題不能一概套用。與 bootstrap 相比,它只有 $n$ 個決定性的刪一樣本,常較省計算,但不會給出同樣豐富的重抽分布。

方法每次怎麼產生樣本主要用途與限制
Bootstrap從 $n$ 筆有放回抽 $n$ 筆,重複 $B$ 次近似估計量抽樣分布;必須選對重抽單位
Jackknife逐次刪一筆,得到 $n$ 份大小 $n-1$ 的樣本估標準誤;對非平滑統計量有限制
Permutation依虛無假設允許的交換方式重排標籤或觀測近似虛無分布與 p-value;交換性與設計必須合理

延伸來源:講義的 Jackknife 說明/講義的 Jackknife 與 bootstrap 比較

延伸閱讀:置換檢定與分類器評估

Bootstrap 與置換檢定各在近似什麼分布?

Bootstrap 通常從經驗分布重抽,問估計量在重複抽樣時如何波動;置換檢定則依虛無假設,把允許交換的部分重排,問「如果沒有這個關係,會得到多極端的統計量」。兩者都會重複計算;讀結果之前,先確認近似的是抽樣分布還是虛無分布。

例如兩組的平均數差,先固定每組筆數並計算觀測差 $T_{obs}$。若虛無假設使觀測在兩組間可交換,就把組別標籤重排、重新計算差 $T$。隨機做 $B$ 次雙尾置換,可用

$$\hat p=\frac{1+\sum_{b=1}^B I(|T_b|\ge |T_{obs}|)}{B+1}.$$

雙尾時兩邊都要取絕對值;只寫 $T_b\ge |T_{obs}|$ 會漏掉負方向的極端結果。加一是隨機置換常用的 Monte Carlo 校正,使原始排列也被計入比較;完整枚舉所有允許排列時,則直接以其精確比例計算。$B=199$ 的隨機置換最小 p-value 是 1/200,不可能宣稱得到了 0。

置換要求的是虛無假設下的交換性。同一病人的重複紀錄、配對實驗、時間序列不能任意逐筆洗牌;應尊重配對、區塊或隨機分派設計。兩組平均相同也不必表示整個分布相同,因此簡單重排標籤檢定的條件,比「只要平均相同」更強。

分類器:標籤重排後,完整重新訓練

講義的分類例子保留 $X$、重排 $y$。原始資料先得到交叉驗證分數;每次置換標籤後,重新擬合每一折的模型、重新得到 CV 分數,形成模型在虛無假設下的分數分布。不能只洗牌已算好的預測值,或固定原始資料訓練好的分類器,因為那沒有重做學習程序。

sklearn 的 score 一律愈大愈好,所以 permutation_test_score 比較置換分數是否大於或等於原始分數。若使用負 MSE,也依這個方向比較,不另把 MSE 的「小才好」直接貼到 score 上。前處理包進 Pipeline;若原始程序含調參或特徵選擇,置換時也要重新執行同樣的選擇程序。

from sklearn.datasets import load_iris from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.model_selection import StratifiedKFold, permutation_test_score # 承接講義的分類器置換檢定;此例是延伸實作,不是 Ch05 原 Lab。 X, y = load_iris(return_X_y=True) model = make_pipeline(StandardScaler(), SVC(kernel="linear", C=1)) cv = StratifiedKFold(5, shuffle=True, random_state=0) score, null_scores, p_value = permutation_test_score( model, X, y, cv=cv, scoring="accuracy", n_permutations=199, random_state=0, n_jobs=1) print("原始標籤的 CV accuracy", score) print("置換標籤後的平均 accuracy", null_scores.mean()) print("置換檢定 p-value", p_value)

這個例子沿用官方分類器示範的 Iris,用分層 CV 和線性 SVM;是講義附錄的延伸,而不是 Portfolio 重抽 Lab 的結果。低 p-value 表示觀測分數相對於這套置換程序很不尋常,支持特徵與標籤之間存在模型能利用的關係;不表示分數一定足夠高、模型一定適合部署,也不是虛無假設為真的機率。高 p-value 也可能是模型沒利用到關係或檢定力不足,不能證明完全沒有關係。

延伸來源:講義:Permutation test/scikit-learn:分類分數的置換檢定示範/permutation_test_score:分數、群組與 p-value

EXERCISES · 練習

動手驗證:教科書第 5 章精選題 教科書 · 第 5.4 節 習題

下面幾題取自 ISLP §5.4 的課後習題,題號都對得回課本。先自己想過再點選項; 每個選項都有解說;需要完整解答時,可查閱下面3個站。

EXERCISE 1 · ISLP 5.4 第 2 題(a)(b)

從 $n$ 筆資料中有放回地抽第一筆時,抽到的不是第 $j$ 筆觀測值的機率是多少?第二筆呢?

(A) 兩次都是 $(n-1)/n$
(B) 第一次 $(n-1)/n$,第二次 $(n-2)/(n-1)$
(C) 第一次 $1/n$,第二次 $1/n$
EXERCISE 2 · ISLP 5.4 第 3 題(b)

相對於驗證集法,$k$-fold CV 的優點是什麼?

(A) $k$-fold CV 完全沒有隨機性,驗證集法有
(B) 偏差較小(每輪用 $n(k-1)/k$ 筆訓練,多於 $n/2$),而且做 $k$ 輪平均後變異也較小
(C) $k$-fold CV 不需要把資料切開,所以能用到全部資料訓練
EXERCISE 3 · ISLP 5.4 第 8 題

課本第 8 題在模擬資料上跑 degree 1 到 4 的 LOOCV,真實模型是二次的。預期會看到什麼?

(A) 誤差隨 degree 單調下降,degree 4 最低
(B) 四個 degree 的 LOOCV 誤差幾乎相同,因為 LOOCV 對模型複雜度不敏感
(C) degree 1 到 2 誤差大幅下降,之後幾乎不再改善(甚至微幅上升)
EXERCISE 4 · ISLP 5.4 第 9 題

課本第 9 題要對 Boston 的 medv 平均值做 bootstrap,並跟公式解 $\mathrm{{SE}}(\bar\mu) = s/\sqrt{{n}}$ 比較。預期結果是?

(A) bootstrap 的 SE 會明顯較小,因為它重複用了同一份資料
(B) 無法比較,因為 bootstrap 只能用在迴歸係數上
(C) 兩個數字會很接近,因為 bootstrap 不需要公式也能重現公式給的答案
重點速查與來源

重抽樣方法速查表 教科書 · 第 5 章

來源:完整書目與章節定位

本頁書目與章節定位

  • 教科書:An Introduction to Statistical Learning with Applications in Python。James、Witten、Hastie、Tibshirani、Taylor,2023。原書與下載資訊
    本頁對應:教科書 · 第 5 章開頭;教科書 · 第 5.1.1 節;教科書 · 第 5.1.2 節;教科書 · 第 5.1.3 節;教科書 · 第 5.1.4 節;教科書 · 第 5.1.5 節;教科書 · 第 5.2 節。
  • 進階參考:The Elements of Statistical Learning。Hastie、Tibshirani、Friedman,第二版。原書與下載資訊
    本頁對應:進階參考 · 第 7.10.2 節。

考前把這一頁掃過去就好。

四種方法對照

方法在估什麼偏差變異計算成本隨機性典型用途
驗證集法測試誤差大(高估)大$1\times$有,很大快速看一眼
LOOCV測試誤差最小大$n\times$無小資料、線性有捷徑
$k$-fold CV測試誤差小小$k\times$有,較小選模型的標準做法
Bootstrap估計量的 SE——$B\times$有,$B$ 大就穩估不確定性
計算細節:完整公式速查

公式速查

名稱式子備註
LOOCV$\mathrm{CV}_{(n)} = \frac1n\sum_i \mathrm{MSE}_i$式 5.1
LOOCV 捷徑(最小平方)$\frac1n\sum_i\left(\frac{y_i-\hat y_i}{1-h_i}\right)^2$式 5.2,只擬合一次模型
$k$-fold$\mathrm{CV}_{(k)} = \frac1k\sum_j \mathrm{MSE}_j$式 5.3
分類版$\mathrm{Err}_j = \frac{1}{|C_j|}\sum_{i\in C_j} I(y_i \ne \hat y_i)$式 5.4
最小變異配置$\alpha = \frac{\sigma_Y^2-\sigma_{XY}}{\sigma_X^2+\sigma_Y^2-2\sigma_{XY}}$式 5.7
在 bootstrap 樣本裡的機率$1-(1-1/n)^n \to 1-e^{-1} \approx 0.632$OOB 的來源
三個一定要記住的觀念 1. 交叉驗證估「預測誤差」,bootstrap 估「估計量的不確定性」。 兩者都在重抽樣,但問的問題不同,不要混用。
2. $k=5$ 或 $10$ 是偏差、變異、計算成本三方的折衷。 LOOCV每輪的樣本量最接近完整資料,但變異仍依資料與方法而定。
3. 任何從資料估參數的前處理都要在每一折的訓練部分內進行。 特徵篩選、標準化、填補、過抽樣、目標編碼——在每折訓練部分 fit,再套到驗證部分。 洩漏造成的數值偏差方向依模型與資料而定。
CARDS · 關鍵詞彙卡

關鍵詞彙卡:點卡片翻面 課程詞彙

詞彙卡整理本章講義與 ISLP 第 5 章中常用的統計或機器學習專有名詞,正面附英文名稱。 先看正面的術語,試著說出定義,再翻面核對。可以洗牌複習,也可以搭配本章例子練習解釋。

複習:展開關鍵詞彙卡