① 照節次讀:每節先讀說明,動手玩互動元件——先預測結果,再按按鈕驗證。 ② 對照講義:每個 §徽章都標了 ISLP 節號與講義頁碼,細節與完整推導請回講義與課本。 ③ 每節做 quiz:答錯就回到該節重讀,不要往下跳;錯的選項也寫了「錯在哪」。 ④ 最後翻關鍵詞彙卡自測術語,並用 REF 總覽當速查表。
你已經會配模型了。現在的問題換成:這個模型在沒見過的資料上會有多準? 最直覺的做法是拿訓練資料算誤差——但那個數字幾乎一定太樂觀,因為模型是照著那批資料調出來的。 極端一點想:一棵長到每個葉子只剩一個樣本的樹,訓練誤差是 0,可是它什麼都沒學到。
如果手上有一大筆獨立的測試資料,事情很簡單。但真實情況通常是資料就這麼多, 再切出去一塊當測試集就不夠訓練了。重抽樣(resampling)的想法是: 反覆從同一批資料裡切出「假的」訓練/測試組合,用它們的平均表現去猜真正的測試誤差。
這三個工具的分工可以先記住:交叉驗證是用來估「預測誤差」並藉此選模型, bootstrap 是用來估「某個估計量的不確定性」。兩者都在重抽樣,但問的問題不同。
| 切幾次 | 每次訓練用多少 | 有隨機性嗎 | 主要用途 | |
|---|---|---|---|---|
| 驗證集法 | 1 | 約 n/2 | 有,換 seed 就變 | 快速粗估 |
| LOOCV | n | n − 1 | 沒有(分割唯一) | 小資料、要穩定 |
| k-fold CV | k(通常 5 或 10) | n(k−1)/k | 有,但比驗證集小很多 | 選模型的標準做法 |
| Bootstrap | B(通常 1000) | n(有放回) | 有,B 大就穩 | 估標準誤與信賴區間 |
為什麼「訓練誤差」不能拿來當測試誤差的估計?
最直接的做法:把資料隨機切成兩半,一半訓練、一半當驗證集(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。看起來很乾淨——問題是換一個切法,答案就變了。
test_size=196)算出的驗證 MSE,x 軸是 horsepower 多項式的次數。粗線是十次的平均。十條線的高低差距,就是驗證集法的變異。
np.float64(25.57387818968441)
這個 25.57 就是課本 §5.1.1 引用的數字。注意 random_state=rng——換掉它,下面的數字就會不一樣。
Ch05-resample-lab-zh.ipynb · 儲存格 16、20
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 · 儲存格 26、28
因為它只用了一半的資料訓練。統計模型通常餵愈多資料表現愈好,所以用 n/2 筆訓練出來的模型,本來就比用全部 n 筆訓練出來的差。你量到的是「一個比較弱的模型」的誤差,因此系統性偏高。
這是驗證集法的兩個缺點之一。另一個是變異太大(就是上面那張圖)。交叉驗證同時改善了這兩點:每一輪都用 n(k−1)/k 筆訓練(比 n/2 多),而且做 k 輪再平均(變異變小)。
random_state 到底該不該固定?該固定,但理由是可重現,不是「這個 seed 比較好」。固定 seed 讓你(和讀你程式的人)能重跑出同樣的數字。
危險的做法是「試幾個 seed,挑數字最漂亮的那個報出來」——那等於用驗證集調參,報出來的誤差就不再誠實了。要降低這種隨機性的影響,正解是改用 k-fold CV,不是換 seed。
驗證集法有兩個公認的缺點。下面哪一組說對了?
驗證集法浪費了一半的資料,而且答案會跳。把它推到極端: 每次只留一個樣本當驗證集,其餘 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 筆」配出來的模型。這樣做有兩個好處: 每一輪都用了幾乎全部的資料(偏差很小),而且分割方式唯一——沒有隨機性, 跑一百次都是同一個數字。
random_state 可以調。
cross_validate 跑 LOOCVnp.float64(24.23151351792922)
24.2315 就是 degree 1 的 LOOCV 估計。跟上面驗證集法的 25.57/20.76 比一比:LOOCV 只有一個答案,不會因為切法而跳。
來源:Ch05-resample-lab-zh.ipynb · 儲存格 32
scikit-learn 的通用 cross_validate() 沒有用這個公式,
所以 lab 裡跑 LOOCV 還是慢——這是實作的事,不是理論的事。
因為它的變異偏大。LOOCV 平均的那 n 個 $\mathrm{MSE}_i$ 彼此高度相關——任兩輪的訓練集有 n − 2 筆是重疊的,所以配出來的模型幾乎一樣。把 n 個高度相關的數字平均,並不會像平均 n 個獨立數字那樣有效地降低變異。
k-fold(k = 5 或 10)的訓練集重疊程度低得多,各折之間比較不相關,所以平均起來變異較小。代價是每輪少用一點資料,偏差稍微大一些——這就是 k 的取捨。
嚴格說,它估的是「用 n − 1 筆資料訓練出來的模型」的期望測試誤差,但因為 n − 1 跟 n 幾乎一樣,實務上直接把它當成「用全部 n 筆訓練出來的那個模型」的測試誤差。
這也是為什麼 CV 的最後一步通常是:用 CV 選出超參數(例如次數 = 2),然後用全部資料重配一次,交出那個模型。CV 的角色是選,不是產出最終模型。
同一份資料上,把 LOOCV 跑兩次,會得到一樣的答案嗎?
LOOCV 要配 n 次模型,n 大就吃不消。折衷方案:把資料隨機平分成 k 份, 每次拿一份當驗證集、其餘 k − 1 份訓練,做 k 輪再平均。這就是 k-fold 交叉驗證:
$$\mathrm{CV}_{(k)} = \frac{1}{k} \sum_{j=1}^{k} \mathrm{MSE}_j$$LOOCV 其實就是 k = n 的特例。實務上 k 取 5 或 10——原因下一節講。
KFold 跑 10-foldshuffle=True 很重要:如果原始資料有排序(例如 Auto 按年份),不打亂就會讓每一折的分佈完全不同。random_state=0 是為了讓不同 degree 用同一組分割——這樣比較才公平。這格 lab 沒存下輸出,下一節的圖用同樣設定重算了一次。
Ch05-resample-lab-zh.ipynb · 儲存格 41
比較不同模型(例如 degree 1 到 10)的 CV 誤差時,為什麼要讓每個模型用同一組折分割?
k 該取多少?兩個方向在拉扯:
ISLP §5.1.4 的結論是:k = 5 或 k = 10 是偏差與變異都可接受的折衷,
而且計算量只有 LOOCV 的 5/n 或 10/n。下面這張圖是 Auto 上的實測:
horsepower 多項式的次數,y 軸是 CV 估的測試 MSE。兩條線分別是 LOOCV 與 10-fold CV。兩條幾乎重疊——這在實務上很常見,也是大家願意用便宜的 10-fold 取代 LOOCV 的理由。
| k | 每輪訓練用 | 偏差 | 變異 | 配模型次數 | 評語 |
|---|---|---|---|---|---|
| 2 | n/2 | 最大(高估) | 最小 | 2 | 太粗,很少用 |
| 5 | 0.8 n | 小 | 小 | 5 | 實務常用 |
| 10 | 0.9 n | 更小 | 略大 | 10 | 實務最常用 |
| n(LOOCV) | n − 1 | 最小 | 最大 | n | 小資料、或有式 5.2 捷徑時 |
平均更多項會更穩,前提是那些項彼此獨立。這裡不是。
算式上看:$\mathrm{Var}(\bar X) = \frac{\sigma^2}{k} + \frac{k-1}{k}\rho\sigma^2$。第一項隨 k 變大而變小,但第二項隨 $\rho$(各折之間的相關)變大而變大。LOOCV 的 $\rho$ 接近 1,第二項就吃掉了第一項的好處。
直覺版:LOOCV 的 n 個模型幾乎是同一個模型,所以你其實只有「一個」意見被重複算了 n 次。
要,而且必須在每一折裡面做:用該折的訓練部分算平均與標準差,再套到驗證部分。
如果先用全部資料標準化再切折,驗證資料的平均與標準差就洩漏進訓練流程了,CV 誤差會偏低。scikit-learn 的正解是把標準化包進 Pipeline,再把整個 pipeline 丟給 cross_validate——它會自動在每折內重新 fit。這跟下一節的錯誤是同一個病。
為什麼實務上不直接用偏差最小的 LOOCV,而多半選 k = 5 或 10?
整套邏輯搬到分類問題只要換一件事:把 MSE 換成錯誤率。
$$\mathrm{CV}_{(k)} = \frac{1}{k} \sum_{j=1}^{k} \mathrm{Err}_j, \qquad \mathrm{Err}_j = \frac{1}{|C_j|} \sum_{i \in C_j} I(y_i \neq \hat y_i)$$$I(\cdot)$ 是指示函數:預測錯就是 1、對就是 0。其餘完全一樣:切 k 折、輪流當驗證集、平均。 ISLP 圖 5.7–5.8 用邏輯斯迴歸加上不同次數的多項式項示範, CV 誤差率確實會在「真正的邊界複雜度」附近最低。
StratifiedKFold,讓每一折的類別比例跟整體一致。類別很少的時候,
普通 KFold 可能切出「某一折完全沒有正例」的荒謬情況。scoring 參數就好。| 情況 | 該用的切分器 | 為什麼 |
|---|---|---|
| 一般迴歸/分類 | KFold(shuffle=True) | 最基本 |
| 類別不平衡 | StratifiedKFold | 保住每折的類別比例 |
| 同一實體有多筆資料 | GroupKFold | 同組資料不可跨訓練/驗證 |
| 時間序列 | TimeSeriesSplit | 不能用未來預測過去 |
| 只想快速看一眼 | ShuffleSplit(n_splits=1) | 等於驗證集法 |
因為隨機切折會讓「未來」的資料進到訓練集、「過去」的資料留在驗證集。模型於是可以用 2026 年的資訊去預測 2025 年——這在部署時根本不可能發生,所以 CV 誤差會嚴重低估真實表現。
正解是只往前切(TimeSeriesSplit):用前 100 筆訓練、預測第 101–120 筆;再用前 120 筆訓練、預測第 121–140 筆…訓練集永遠在驗證集之前。
一份資料裡正例只佔 2%。用普通的 KFold(n_splits=10) 會有什麼風險?
這一節是整章最容易在實務上出錯、而且錯了不會有任何錯誤訊息的地方。
常見情境:手上有 p = 500 個特徵、n = 50 筆資料。你先算每個特徵跟 y 的相關係數, 挑出最相關的 10 個,然後對這 10 個特徵做 5-fold CV,得到一個漂亮的誤差率。 這個數字是假的。
問題在於「挑特徵」這一步看過了全部的 y,包含後來被當成驗證資料的那些。 篩選本身就是模型訓練的一部分,它必須關在每一折裡面做。
scikit-learn 裡,把它們串成 Pipeline 再交給
cross_validate,這件事就自動對了。
嚴格說要,實務上影響小得多。
不看 y 的前處理不會直接把答案洩漏進來,所以偏差通常很輕微。但它仍然用到了驗證資料的分佈資訊(PCA 的方向、特徵的變異數),而在部署時你不可能先看過未來資料的分佈。既然包進 Pipeline 幾乎沒有額外成本,就一律包進去,不必去分辨哪種洩漏比較嚴重。
不太行,它會偏低。你已經拿 CV 誤差當目標挑過超參數了——被挑中的那組本來就是「在這組折上運氣最好」的那組,這叫做選擇偏差。
要誠實的估計,用嵌套交叉驗證(nested CV):外層折只負責評估、完全不參與調參;內層折在外層的訓練部分裡調參。或者更簡單:一開始就切出一份從頭到尾沒碰過的測試集。
在 5-fold CV 之前先用全部資料把特徵標準化(減平均、除標準差)。這樣有問題嗎?
換一個問題。前面都在問「模型的預測有多準」;現在問「我算出來的這個數字有多不確定」。
ISLP §5.2 的例子:把錢分成比例 α 投資 X、1 − α 投資 Y,要讓報酬的變異最小,最佳比例是
$$\alpha = \frac{\sigma_Y^2 - \sigma_{XY}}{\sigma_X^2 + \sigma_Y^2 - 2\sigma_{XY}}$$把樣本變異數代進去就得到 $\hat\alpha$。但 $\hat\alpha$ 有多可靠? 它的標準誤沒有簡單的公式可查。理想上我們會重新蒐集 1000 份新資料、算 1000 個 $\hat\alpha$、 看它們的標準差——但我們只有一份資料。
Bootstrap 的把戲:把手上這份資料當成母體,從裡面有放回地抽 n 筆, 當成一份「新」資料集,重算 $\hat\alpha^*$。重複 B 次,那 B 個 $\hat\alpha^*$ 的標準差 就是 $\mathrm{SE}(\hat\alpha)$ 的估計。不需要任何分佈假設,也不需要推導。
boot_SE()np.float64(0.57583207459283)
這就是 ISLP 書上的 α̂ = 0.5758。
來源:Ch05-resample-lab-zh.ipynb · 儲存格 53、55
np.float64(0.6074452469619004)
replace=True 是關鍵:有放回,所以同一筆可能被抽到好幾次,也會有一些完全沒被抽到。這一次抽出來的 α̂* = 0.6074,跟 0.5758 差了不少——這個「差」正是我們要量化的東西。
Ch05-resample-lab-zh.ipynb · 儲存格 57
lab 的第 62 格寫著:SE(α̂) 的 bootstrap 估計是 0.0912。注意這支函式用 $E[\theta^2] - (E[\theta])^2$ 累加,不必存下 1000 個值。
來源:Ch05-resample-lab-zh.ipynb · 儲存格 59、61
有放回地抽 n 次,某一筆始終沒被抽到的機率是 $\left(1 - \frac{1}{n}\right)^n$。 n 一大,這個數趨近 $e^{-1} \approx 0.368$。所以
$$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)。 它們對這一輪的模型來說是天然的驗證集——第 9 章的 bagging 與 random forest 就靠這招 免費拿到測試誤差估計。
可以,但要很小心,而且通常比不上交叉驗證。
問題出在重疊:bootstrap 樣本平均含有原始資料的 63.2%,所以如果你用 bootstrap 樣本訓練、用原始全部資料當測試,那個「測試集」裡有三分之二的資料模型已經看過了,誤差會嚴重低估。
補救方式是只用 OOB 的那 36.8% 來評估,這就接近 k-fold 的精神了。所以講義第 36 頁的答案是:估標準誤用 bootstrap,估預測誤差用交叉驗證。
估標準誤時 B = 1000 通常就很夠;要估信賴區間的尾端分位數(例如 2.5% 與 97.5%),B 建議拉到 2000 以上,因為尾端需要更多樣本才穩。
注意 B 大只會讓「bootstrap 對真實 SE 的估計」更穩定,不會讓原始資料變多。n 小的時候 bootstrap 本身就不可靠,拉高 B 也救不了——它只是把同一份薄薄的資訊算得更精確而已。
幾個典型的坑:
n = 100 時,一筆特定的觀測值完全沒有出現在某個 bootstrap 樣本裡的機率約為多少?
下面幾題取自 ISLP §5.4 的課後習題,題號都對得回課本。先自己想過再點選項; 每個選項——包含錯的——都寫了為什麼。想看完整解答再對照下面3個站。
從 n 筆資料中有放回地抽第一筆時,抽到的不是第 j 筆觀測值的機率是多少?第二筆呢?
相對於驗證集法,k-fold CV 的優點是什麼?
課本第 8 題在模擬資料上跑 degree 1 到 4 的 LOOCV,真實模型是二次的。預期會看到什麼?
課本第 9 題要對 Boston 的 medv 平均值做 bootstrap,並跟公式解 $\mathrm{{SE}}(\bar\mu) = s/\sqrt{{n}}$ 比較。預期結果是?
考前把這一頁掃過去就好。
| 方法 | 在估什麼 | 偏差 | 變異 | 算力 | 隨機性 | 典型用途 |
|---|---|---|---|---|---|---|
| 驗證集法 | 測試誤差 | 大(高估) | 大 | 1× | 有,很大 | 快速看一眼 |
| LOOCV | 測試誤差 | 最小 | 大 | n× | 無 | 小資料、線性有捷徑 |
| k-fold CV | 測試誤差 | 小 | 小 | k× | 有,較小 | 選模型的標準做法 |
| Bootstrap | 估計量的 SE | — | — | B× | 有,B 大就穩 | 估不確定性 |
| degree | 1 | 2 | 3 | 5 | 10 |
|---|---|---|---|---|---|
| LOOCV | 24.23 | 19.25 | 19.34 | 19.03 | 19.91 |
| 10-fold CV | 24.21 | 19.19 | 19.28 | 19.14 | 19.87 |
| 驗證集(seed 0) | 23.62 | 18.76 | 18.80 | 18.45 | 19.07 |
degree 1 的 LOOCV = 24.2315,跟 lab 儲存格 32
的 np.float64(24.23151351792922) 相符。三列都在 degree 2 之後就拉平了。
| 名稱 | 式子 | 備註 |
|---|---|---|
| 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 的來源 |
Pipeline 就對了。
本頁「預期輸出」逐字取自課程 lab notebook(老師在課程環境實跑);圖表用的烘焙資料由 tools/frames/ 在固定種子下產生,環境為 numpy 1.24.4 · pandas 2.3.2 · scikit-learn 1.6.1 · scipy 1.13.1 · statsmodels 0.14.2 · ISLP 0.4.0 · pygam 0.10.1。每張卡下方的「來源」標了 lab 的儲存格編號,可以直接回去對。
詞彙卡取自本章講義與 ISLP 第 5 章,正面是中文術語(附英文原名)。 先看正面、心裡默想定義,再翻面對答案;洗牌後再過一輪,直到每張都能不看答案講出來。