① 照節次讀:每節先讀說明,動手玩互動元件——先預測結果,再按按鈕驗證。 ② 對照講義:每個 §徽章都標了 ISLP 節號與講義頁碼,細節與完整推導請回講義與課本。 ③ 每節做 quiz:答錯就回到該節重讀,不要往下跳;錯的選項也寫了「錯在哪」。 ④ 最後翻關鍵詞彙卡自測術語,並用 REF 總覽當速查表。
到目前為止的每一個模型都長成 $y = \beta_0 + \beta_1 x_1 + \cdots + \beta_p x_p$。
它好用、好解讀、推論工具齊全——但它假設「x 每增加一單位,y 就固定增加 $\beta$」。
真實世界很少這麼客氣。Wage 資料裡薪水隨年齡先升後降,
用一條直線去配,你會得到一個「年齡愈大薪水愈高」的結論,然後在 60 歲那群人身上錯得很難看。
要放寬線性假設,最極端的做法是丟掉參數化模型(KNN、樹、核方法)。 但那樣同時丟掉了「這個變數對 y 的影響長什麼樣」這種可以畫出來、可以講給人聽的東西。 這一章走的是中間路線:把 x 換成一組事先選好的固定轉換 $b_1(x), \ldots, b_K(x)$,模型對係數還是線性的。所以第 3 章那一整套 最小平方、標準誤、F 檢定,全部可以照用——只是預測變數換人了。
| 方法 | 彈性怎麼來的 | 由什麼控制 | 邊界行為 | 一句話 |
|---|---|---|---|---|
| 多項式 | 拉高次數 d | d(整數) | 很糟,會暴衝 | 最省事,但 d > 4 就別碰 |
| 階梯函數 | 多切幾段 | 切點數 K | 還可以(常數) | 沒有自然切點就會漏掉趨勢 |
| 立方樣條 | 多加節點 | 節點數 K(df = K+4) | 偏糟,信賴帶會爆開 | 次數固定 3,靠節點取得彈性 |
| 自然樣條 | 多加節點 | 節點數 K(df = K+2) | 好,兩端是直線 | 同樣的彈性、更穩的兩端 |
| 平滑樣條 | 調 λ | 有效自由度 dfλ(連續) | 好(兩端線性) | 不必選節點,只要選 λ |
| 局部迴歸 | 調鄰域大小 | 跨距 s | 偏糟(單邊資料) | 每次預測都要用到全部資料 |
這一章的講義有 54 頁,順序就是上面這張表。往下每一節都有一個可以拖、可以推的元件—— 先猜結果再按按鈕,猜錯的地方才是你真正學到東西的地方。
把 age、age²、age³、age⁴ 一起丟進迴歸,為什麼書上還說這是「一般的線性迴歸模型」?
最直接的放寬:把直線換成 d 次多項式。
$$y_i = \beta_0 + \beta_1 x_i + \beta_2 x_i^2 + \beta_3 x_i^3 + \cdots + \beta_d x_i^d + \varepsilon_i \tag{7.1}$$係數照樣用最小平方估。但注意:個別的 $\hat\beta_j$ 沒有解讀價值。
lab 儲存格 14 配出來的 447.07, −478.32, 125.52, −77.91,
你沒辦法說「age 的二次項效果是 125.52」——這些數字還取決於用哪一組基底
(正交多項式 vs 原始冪次,係數完全不同,配出來的曲線一模一樣)。
要看的是整條配適曲線,以及它的信賴帶。
信賴帶怎麼來的?在某個 $x_0$ 上,配適值是 $\hat f(x_0) = \hat\beta_0 + \hat\beta_1 x_0 + \cdots + \hat\beta_d x_0^d$。 令 $\ell_0 = (1, x_0, x_0^2, \ldots, x_0^d)^{\mathsf T}$、$\hat C$ 是 $\hat\beta$ 的 共變異數矩陣,那麼
$$\widehat{\operatorname{Var}}\left[\hat f(x_0)\right] = \ell_0^{\mathsf T} \hat C \, \ell_0 \tag{7.2}$$把每個 $x_0$ 的 $\hat f(x_0) \pm 2\,\mathrm{SE}$ 連起來,就是圖 7.1 那兩條虛線。 這個公式是整節的關鍵:$x_0$ 跑到資料邊界時 $x_0^{15}$ 大得離譜, 乘上共變異數矩陣以後變異就炸開了。下面把滑桿推到 15 就看得到。
Wage 的 90 筆子樣本(只是背景),紅線與淡藍帶是用全部 3000 筆配出來的 d 次多項式與 95% 信賴帶。下圖:訓練 MSE(藍)一路往下,10-fold CV MSE(紅)在 d = 4 觸底之後回頭往上。
coef std err t P>|t| intercept 111.7036 0.729 153.283 0.000 poly(age, degree=4)[0] 447.0679 39.915 11.201 0.000 poly(age, degree=4)[1] -478.3158 39.915 -11.983 0.000 poly(age, degree=4)[2] 125.5217 39.915 3.145 0.002 poly(age, degree=4)[3] -77.9112 39.915 -1.952 0.051
poly('age', degree=4) 產生的是正交多項式基底,所以四個係數彼此不相關,t 檢定可以一個一個讀。最後一項的 p 值 0.051 剛好在邊界上——這就是「三次或四次都合理」的來源。
Ch07-nonlin-lab-zh.ipynb · 儲存格 14
df_resid ssr df_diff ss_diff F Pr(>F) 0 2998.0 5.022216e+06 0.0 NaN NaN NaN 1 2997.0 4.793430e+06 1.0 228786.010128 143.593107 2.363850e-32 2 2996.0 4.777674e+06 1.0 15755.693664 9.888756 1.679202e-03 3 2995.0 4.771604e+06 1.0 6070.152124 3.809813 5.104620e-02 4 2994.0 4.770322e+06 1.0 1282.563017 0.804976 3.696820e-01
第 1 列(線性 vs 二次)p 值 2.4e−32,第 2 列(二次 vs 三次)0.0017,第 3 列(三次 vs 四次)0.051,第 4 列(四次 vs 五次)0.37。結論:三次或四次夠了,五次沒有必要。順帶對一下上面那張卡:$(-11.983)^2 = 143.59$ 正好是這裡的 F 統計量(lab 儲存格 29 就在算這個)。
來源:Ch07-nonlin-lab-zh.ipynb · 儲存格 26
coef std err z P>|z| intercept -4.3012 0.345 -12.457 0.000 poly(age, degree=4)[0] 71.9642 26.133 2.754 0.006 poly(age, degree=4)[1] -85.7729 35.929 -2.387 0.017 poly(age, degree=4)[2] 34.1626 19.697 1.734 0.083 poly(age, degree=4)[3] -47.4008 24.105 -1.966 0.049
同一套基底換到 GLM 上就得到 ISLP 圖 7.1 右。注意 n = 3000 但高收入者只有 79 人,所以係數的標準誤很大、信賴帶很寬——尤其在 age 大的那一端。「樣本數夠大」要看的是關鍵事件的數量,不是總筆數。
來源:Ch07-nonlin-lab-zh.ipynb · 儲存格 33
同一份 Wage 資料上,d 從 1 加到 15,訓練 MSE 一路從 1674 掉到 1585,但 10-fold CV MSE 在 d = 4 觸底(1596)之後回頭升到 1603。這說明什麼?
多項式有個結構上的毛病:它是全域的。60 歲那群人的資料會透過 $\hat\beta_3$ 影響 20 歲那一段的曲線形狀。階梯函數(step function)換一個想法: 把 x 的範圍切成幾段,每段各配一個常數,段與段之間互不干涉。
做法是選切點 $c_1, \ldots, c_K$,造出 K+1 個指示變數:
$$C_0(X) = I(X < c_1),\quad C_k(X) = I(c_k \le X < c_{k+1}),\quad C_K(X) = I(c_K \le X) \tag{7.4}$$然後把 $C_1, \ldots, C_K$ 丟進迴歸($C_0$ 跟截距重複,要丟掉一個):
$$y_i = \beta_0 + \beta_1 C_1(x_i) + \cdots + \beta_K C_K(x_i) + \varepsilon_i \tag{7.5}$$pd.get_dummies() 就是這樣),係數會直接變成各段的平均值。
兩種編碼配出來的曲線完全一樣。
pd.qcut 切四段coef std err t P>|t| (17.999, 33.75] 94.1584 1.478 63.692 0.0 (33.75, 42.0] 116.6608 1.470 79.385 0.0 (42.0, 51.0] 119.1887 1.416 84.147 0.0 (51.0, 80.0] 116.5717 1.559 74.751 0.0
pd.qcut(age, 4) 自動用 25%/50%/75% 分位數當切點,切出 (17.999, 33.75]、(33.75, 42.0]、(42.0, 51.0]、(51.0, 80.0] 四段。因為 get_dummies() 保留了全部四欄(沒有丟基準組),四個係數就直接是四段的平均薪水:94.16、116.66、119.19、116.57。把它們跟上面元件裡的紅線對一下——同一件事。
不用分位數就改 pd.cut()(等寬切),lab 儲存格 41 有。
Ch07-nonlin-lab-zh.ipynb · 儲存格 39
只有 age 一個預測變數時,階梯函數在每一段的配適值是什麼?
停下來看一下前兩節做了什麼。多項式迴歸用的預測變數是 $x, x^2, x^3, \ldots$; 階梯函數用的是 $I(c_1 \le x < c_2), I(c_2 \le x < c_3), \ldots$。 形式不同,但結構完全一樣:都是「先把 x 過一組固定函數,再做線性迴歸」。 ISLP §7.3 把這個結構抽出來叫做基底函數(basis function):
$$y_i = \beta_0 + \beta_1 b_1(x_i) + \beta_2 b_2(x_i) + \cdots + \beta_K b_K(x_i) + \varepsilon_i \tag{7.7}$$關鍵是 $b_1(\cdot), \ldots, b_K(\cdot)$ 事先選定、固定、已知—— 它們不含要估的參數。於是 (7.7) 就是一個以 $b_1(x_i), \ldots, b_K(x_i)$ 為預測變數的 標準線性模型,最小平方、標準誤、F 檢定原封不動搬過來。
下面這個元件是本頁最重要的一個。左邊每一個核取鈕都是一個基底函數; 你選哪幾個,下面就用那幾個去配。三個預設按鈕分別把選擇切成 「三次多項式」、「立方樣條」、「階梯函數」——你會看到它們只是勾選項不同而已。
Wage 的 25% 與 75% 分位數)。
coef std err t P>|t| intercept 94.1584 1.478 63.687 0.0 bs(age, df=3, degree=0)[0] 22.3490 2.152 10.388 0.0 bs(age, df=3, degree=0)[1] 24.8076 2.044 12.137 0.0 bs(age, df=3, degree=0)[2] 22.7814 2.087 10.917 0.0
這張卡是上面那個元件的程式版證據。bs('age', df=3, degree=0) 指定 3 個自由度、次數 0,節點就落在同樣的三個分位數上,配出來的是分段常數。截距 94.158 跟 qcut 版一模一樣(第一段的平均),而 94.158 + 22.349 = 116.507 ≈ 116.611(第二段的平均)——差一點是因為 qcut() 用 ≤ 判斷區間、bs() 用 <,邊界上那幾筆歸屬不同。同一個模型,不同編碼。
Ch07-nonlin-lab-zh.ipynb · 儲存格 53
基底函數框架要求 $b_1(\cdot), \ldots, b_K(\cdot)$ 必須「事先選定、固定且已知」。這個要求為什麼這麼重要?
多項式的毛病是全域、次數一高就在邊界暴衝。階梯函數的毛病是段內沒有斜率。 把兩個想法縫起來:分段配低次多項式。節點(knot)就是換係數的地方。 一個節點在 c 的分段三次多項式長這樣:
$$y_i = \begin{cases} \beta_{01} + \beta_{11} x_i + \beta_{21} x_i^2 + \beta_{31} x_i^3 + \varepsilon_i & x_i < c \\ \beta_{02} + \beta_{12} x_i + \beta_{22} x_i^2 + \beta_{32} x_i^3 + \varepsilon_i & x_i \ge c \end{cases} \tag{7.8}$$兩段各 4 個參數,總共 8 個自由度。ISLP 圖 7.3 左上就是這樣配出來的—— 函數在節點上斷開,看起來很荒謬。解法是加約束。每加一個約束就少一個自由度:
一般而言,K 個節點的立方樣條用掉 K + 4 個自由度。 d 次樣條的定義是:分段 d 次多項式,且導數連續到 d − 1 階。 所以線性樣條只要求函數連續(圖 7.3 右下),而 §7.2 的階梯函數就是 0 次樣條。
那要怎麼真的把約束配進去?不必解限制式最小平方——換基底就好。 從三次多項式的基底 $x, x^2, x^3$ 出發,每個節點加一個截斷冪基底 (truncated power basis)函數:
$$h(x, \xi) = (x - \xi)^3_+ = \begin{cases} (x-\xi)^3 & x > \xi \\ 0 & \text{否則} \end{cases} \tag{7.10}$$加上 $\beta_4 h(x, \xi)$ 只會讓三階導數在 ξ 跳(跳 $6\beta_4$), 函數值、一階、二階導數都保持連續。所以 K 個節點的立方樣條就是對 $X, X^2, X^3, h(X,\xi_1), \ldots, h(X,\xi_K)$ 做普通的最小平方, K + 4 個係數。這就是 (7.9)。
先看少一階。只要求函數值與一階導數連續,二階導數可以跳——二階導數是「斜率變化的速度」,也就是曲率。曲率突然改變,人眼看得出來:曲線在節點附近會有一個「彈一下」的感覺。所以只到一階不夠。
再看多一階。三次多項式的三階導數是常數 $6d$;如果連三階導數也要求連續,那 $d$ 在節點兩側必須相同,二階導數又是 $2c + 6dx$,連續加上 $d$ 相同就迫使 $c$ 也相同…一路推下去,兩段會退化成同一個三次多項式——節點等於不存在,整條曲線變回全域三次多項式,彈性全部消失。所以三次的極限就是二階。
這也是「d 次樣條=分段 d 次多項式 + 導數連續到 d−1 階」這個定義的來源:d−1 階是還能留下彈性的最高要求。書上還補了一句很實用的話:三階導數的不連續人眼幾乎偵測不到,所以立方樣條「看起來」就是平滑的——這就是為什麼實務上 degree 3 幾乎是唯一的選擇,更高次只是多花自由度,看起來沒有更平滑。
BSpline 與 bs()coef std err t P>|t| intercept 60.4937 9.460 6.394 0.000 bs(age, internal_knots=[25, 40, 60])[0] 3.9805 12.538 0.317 0.751 bs(age, internal_knots=[25, 40, 60])[1] 44.6310 9.626 4.636 0.000 bs(age, internal_knots=[25, 40, 60])[2] 62.8388 10.755 5.843 0.000 bs(age, internal_knots=[25, 40, 60])[3] 55.9908 10.706 5.230 0.000 bs(age, internal_knots=[25, 40, 60])[4] 50.6881 14.402 3.520 0.000 bs(age, internal_knots=[25, 40, 60])[5] 16.6061 19.126 0.868 0.385
BSpline(internal_knots=[25,40,60], intercept=True) 給出 7 欄=K + 4 = 3 + 4,正好是理論值。bs() 預設 intercept=False,所以會丟掉一個基底函數讓模型自己的截距去頂,摘要裡只剩 6 個 bs(age)[j] 加一個 intercept——加起來還是 7 個參數。
注意這裡用的是 B-樣條基底,不是上面講的截斷冪基底。兩者張出同一個函數空間,配出來的曲線一模一樣,但 B-樣條的設計矩陣是帶狀的(每列只有 degree + 1 個非零元素),條件數低得多。
Ch07-nonlin-lab-zh.ipynb · 儲存格 44、46
array([33.75, 42. , 51. ])
要求 6 個自由度(df=6),轉換器就自己把 3 個節點放在 33.75、42.0、51.0——正好是 age 的 25%、50%、75% 分位數。6 = K + 3(不含截距)。實務上就是這樣用的:給 df,讓軟體放節點。上面那個基底積木元件的兩個節點 33.75 與 51 就是從這裡抄來的。
Ch07-nonlin-lab-zh.ipynb · 儲存格 51
| 約束 | 自由度(單節點) | 圖 7.3 的位置 | 長相 |
|---|---|---|---|
| 都不加(分段三次) | 8 | 左上 | 在節點斷開,很荒謬 |
| 函數值連續 | 7 | 右上 | 接上了,但是 V 字折角 |
| +一階導數連續 | 6 | (書上沒單獨畫) | 折角消失 |
| +二階導數連續 = 立方樣條 | 5 = K+4 | 左下 | 看起來完全平滑 |
| 線性樣條(只要求連續) | 3 = K+2 | 右下 | 折線 |
在 age 上配一個有 5 個內部節點的立方樣條(含截距),要估幾個參數?
立方樣條看起來很漂亮,但有一個藏起來的問題:兩端的變異很大。 在最小節點以左、最大節點以右,資料只從單邊來,三次多項式卻還有完整的四個自由度可以亂扭。 ISLP 圖 7.4 就在示範這件事——三個節點的立方樣條,兩端的信賴帶「appear fairly wild」。
自然樣條(natural spline)的修法很直接:多加兩個邊界約束, 要求函數在兩端的區域是線性的。線性只需要 2 個參數(截距 + 斜率), 而三次要 4 個,所以每一端省下 2 個自由度,兩端合計省 4 個。 K 個內部節點的自然立方樣條因此只用 K + 2 個參數(含截距)。
ISLP 圖 7.7 把這件事推到極端:15 個自由度的自然樣條 vs 15 次多項式。 兩者複雜度相同,但多項式在尾端狂野擺盪,自然樣條還很體面。 這就是「樣條通常優於多項式」的理由:樣條靠加節點取得彈性、把次數鎖在 3; 多項式只能靠拉高次數,而高次的代價全部集中在邊界。
放哪裡:理論上該把節點放在「函數變化快」的地方,變化慢的地方少放。實務上幾乎沒人這樣做,因為你事先不知道哪裡變化快。標準做法是指定自由度,讓軟體把對應數量的節點放在資料的均勻分位數上——lab 儲存格 51 的 BSpline(df=6) 就自動選了 33.75、42.0、51.0,正好是 25%/50%/75% 分位數。用分位數而不是等距,好處是每一段的樣本數差不多,不會出現「某一段只有 3 個點」的情況。
放幾個:用交叉驗證。ISLP 圖 7.6 對 Wage 掃了 df = 1 到 10 的10-fold CV MSE:自然樣條在 df = 3、立方樣條在 df = 4 就已經足夠,曲線之後就拉平了。做法跟第 5 章完全一樣——留一部分資料、配一個指定節點數的樣條、在留出的部分算誤差,換不同的 K 重複,選 CV 誤差最小的。
還有一個很務實的答案:§7.7 配多變數 GAM 時,每個變數都要選 df 就太麻煩了,所以實務上常常直接把所有項的 df 都定成 4,先跑起來再說。書上原話是「we typically adopt a more pragmatic approach」。
ns() 配自然樣條coef std err t P>|t| intercept 60.4752 4.708 12.844 0.000 ns(age, df=5)[0] 61.5267 4.709 13.065 0.000 ns(age, df=5)[1] 55.6912 5.717 9.741 0.000 ns(age, df=5)[2] 46.8184 4.948 9.463 0.000 ns(age, df=5)[3] 83.2036 11.918 6.982 0.000 ns(age, df=5)[4] 6.8770 9.484 0.725 0.468
ns('age', df=5):5 個自由度(不含截距),節點由分位數自動決定。跟前一節 bs() 的摘要對比一下——係數的標準誤明顯小很多(4.7~11.9 對上 9.6~19.1)。這就是邊界線性約束買到的東西。
ISLP 圖 7.5 用的是 4 個自由度的自然樣條(三個內部節點在 25%/50%/75% 分位數);腳註 4 解釋了「含邊界節點共 5 個節點的立方樣條有 9 個自由度,兩端各 2 個線性約束後剩 5,扣掉被截距吸收的常數就記成 4」。
Ch07-nonlin-lab-zh.ipynb · 儲存格 56
自然樣條相對於立方樣條,多了什麼約束、換到了什麼?
迴歸樣條的流程是:選節點 → 造基底 → 最小平方。平滑樣條(smoothing spline) 換一個完全不同的入口:直接寫下你要的東西,然後解一個最佳化問題。
我們要一個配得好、又不要太扭的函數 g。「配得好」是 RSS 小,「不要太扭」怎麼寫? 二階導數 $g''(t)$ 衡量斜率變化的速度,也就是粗糙度: g 在 t 附近很抖,$|g''(t)|$ 就大;直線的二階導數恆為 0。 把它平方後在整個範圍上積起來,就得到總粗糙度。於是:
$$\min_g \;\sum_{i=1}^{n} \left(y_i - g(x_i)\right)^2 \;+\; \lambda \int g''(t)^2 \, dt \tag{7.11}$$令人意外的是這個無限維最佳化有漂亮的解:使 (7.11) 最小的 g 是 在每一個相異的 $x_1, \ldots, x_n$ 上都有節點的自然立方樣條。 但它不等於「拿全部 x 當節點去配自然樣條」——那樣一定過度配適; 它是那個自然樣條的收縮版,收縮的程度由 λ 決定。
既然每個點都是節點,名目上有 n 個參數。所以我們不用「參數個數」描述它的彈性, 改用有效自由度(effective degrees of freedom)。把配適值寫成
$$\hat g_\lambda = S_\lambda \, y, \qquad \mathrm{df}_\lambda = \sum_{i=1}^{n} \{S_\lambda\}_{ii} \tag{7.12–7.13}$$$S_\lambda$ 是那個 $n \times n$ 的平滑矩陣,$\mathrm{df}_\lambda$ 是它的跡。 λ 從 0 增到 ∞ 時,$\mathrm{df}_\lambda$ 從 n 一路降到 2。 它是連續值,可以是 6.8 這種數字。
λ 怎麼選?交叉驗證。而且平滑樣條的 LOOCV 有捷徑,只配一次模型就算得出來 (跟第 5 章式 5.2 同一個套路,$\{S_\lambda\}_{ii}$ 扮演槓桿值的角色):
$$\mathrm{RSS}_{\mathrm{cv}}(\lambda) = \sum_{i=1}^{n} \left(y_i - \hat g_\lambda^{(-i)}(x_i)\right)^2 = \sum_{i=1}^{n} \left[\frac{y_i - \hat g_\lambda(x_i)} {1 - \{S_\lambda\}_{ii}}\right]^2$$pygam 的 gridsearch() 依 GCV(廣義交叉驗證)挑 λ,在這份資料上選出 λ = 251.19、dfλ = 5.64、GCV = 1596.88。課本圖 7.8 的 6.8 是用 LOOCV 挑的,兩者的準則不同、答案自然不會完全一樣——但都落在 5~7,結論一致:這份資料不需要 16 個自由度。
不完全是。前兩個是同一回事,第三個是另一種東西。
(1)多項式的次數 d、(2)樣條的 K + 4:這兩個都是老實的參數計數——你要估幾個 β,自由度就是幾。它們一定是整數,而且「d = 4」跟「3 個節點的立方樣條(df = 7)」都可以直接對應到設計矩陣有幾欄。第 3 章那套「殘差自由度 = n − p」照用。
(3)平滑樣條的 $\mathrm{df}_\lambda$:這個不是參數計數。平滑樣條名目上有 n 個參數(每個 x 都是節點),但它們被懲罰項綁得死死的。所以我們改量「這個平滑器實際上用掉多少彈性」,定義成平滑矩陣的跡 $\sum_i \{S_\lambda\}_{ii}$。它是連續的(6.8、5.64 都合法),而且會隨 λ 連續變化。
把它們放在同一把尺上看是有道理的:$\mathrm{df}_\lambda = 5$ 的平滑樣條,彈性大約等於一個 5 個參數的迴歸樣條。這就是為什麼 pygam 提供 approx_lam(X, term, df) 讓你「用 df 指定 λ」——λ 沒有直覺,df 有。局部迴歸也可以這樣量:ISLP 圖 7.10 標了「span 0.2 相當於 16.4 個自由度、span 0.7 相當於 5.3 個」。
np.float64(4.000000100000307)
approx_lam(X_age, age_term, 4) 找出「讓有效自由度等於 4」的那個 λ,回代驗算得到 4.000000100000307。注意 lab 的說明:這個 df 包含平滑樣條那個沒有被懲罰的截距與線性項,所以下限是 2——這正好對上課本「λ → ∞ 時 df 降到 2」。所以 lab 儲存格 73 畫圖時用 approx_lam(..., df+1),標籤上的 df=1 其實就是直線配適。
上面元件的滑桿刻度用的是 degrees_of_freedom() 的定義(含截距),所以最左邊是 2 而不是 0。
Ch07-nonlin-lab-zh.ipynb · 儲存格 71
$\lambda \to \infty$ 時,(7.11) 的解 $\hat g$ 會變成什麼?
再換一個想法。前面所有方法都在配一個全域的函數形式 (就算是分段的,段的邊界也是事先定死的)。局部迴歸(local regression)說: 要預測 $x_0$ 上的值,就只用 $x_0$ 附近的點,配一條加權直線,取它在 $x_0$ 的值。 換一個 $x_0$,重配一次。
要做的選擇有三個:權重函數 K 怎麼定、第 3 步配常數/直線/二次、以及 跨距(span)s 取多少。前兩個影響不大, s 才是關鍵——它扮演的角色跟平滑樣條的 λ 一樣。 s 小則鄰域窄、曲線抖;s 大則鄰域寬、接近全域配適。s 一樣可以用交叉驗證選。
| span s | 鄰域 | 曲線 | ISLP 圖 7.10 標的有效自由度 |
|---|---|---|---|
| 0.2 | 20% 的資料 | 抖,跟著局部起伏 | 16.4 |
| 0.7 | 70% 的資料 | 平滑 | 5.3 |
| → 1.0 | 全部資料 | 趨近一條全域直線 | → 2 |
上表的 16.4 與 5.3 取自 ISLP 圖 7.10 的圖例
(Wage,局部線性)。lab 儲存格 125 畫的是 span 0.2 與 0.5。
注意上面元件是瀏覽器即時算的 tricube 加權最小平方,不含 lowess()
的穩健疊代(robustifying iterations),所以數字不會跟 lab 完全對上——
它示範的是機制,不是重現套件的輸出。
statsmodels 的 lowess()frac=span 就是跨距,xvals=age_grid 指定要在哪些點求配適值。lab 用 0.2 與 0.5,畫出來 0.5 明顯比 0.2 平滑。
這一格只存了圖沒有存文字輸出,所以這裡不放「預期輸出」——契約規定預期輸出一律逐字取自 lab,沒有就不編。
另外注意 lab 的註解:pygam 不支援把局部迴歸當成 GAM 的項,有些 GAM 實作(例如 R 的 gam)可以。
Ch07-nonlin-lab-zh.ipynb · 儲存格 124、125
為什麼局部迴歸被叫做「記憶式(memory-based)」方法?
前面七節都只處理一個預測變數。現在把它們裝到多變數上。 多元線性迴歸是 $y_i = \beta_0 + \sum_j \beta_j x_{ij} + \varepsilon_i$; 把每個線性項 $\beta_j x_{ij}$ 換成各自的非線性函數 $f_j(x_{ij})$,就得到 廣義加法模型(generalized additive model, GAM):
$$y_i = \beta_0 + \sum_{j=1}^{p} f_j(x_{ij}) + \varepsilon_i = \beta_0 + f_1(x_{i1}) + \cdots + f_p(x_{ip}) + \varepsilon_i \tag{7.15}$$「加法」的意思就是:各變數的貢獻是相加的,每個 $f_j$ 各自配、再加起來。 GAM 漂亮的地方在於前面每一種單變數方法都可以當積木用—— 自然樣條、平滑樣條、局部迴歸、甚至多項式,混搭也行。 ISLP 圖 7.11/7.12 配的就是
$$\mathrm{wage} = \beta_0 + f_1(\mathrm{year}) + f_2(\mathrm{age}) + f_3(\mathrm{education}) + \varepsilon \tag{7.16}$$其中 education 是類別變數,$f_3$ 就是「每個層級一個常數」(虛擬變數)。
如果 $f_1, f_2$ 用自然樣條,整個模型只是一個大號的線性迴歸
(基底矩陣橫向疊起來就好),sm.OLS() 一行配完——這是圖 7.11。
如果用平滑樣條,最小平方就不夠了,要用
逆向配適(backfitting)——這是圖 7.12。
pygam 裡用 f_gam(2, lam=0) 指定——類別項就是每個層級一個常數,沒有「平滑程度」可以調,而且 lam=0 表示完全不收縮。它的自由度固定是「層級數 − 1」。
| GAM 的優點 ✔ | GAM 的限制 ✘ |
|---|---|
| 每個 $X_j$ 各配一個非線性 $f_j$,不必手動試變換 | 模型被限制成加法的,變數多的時候會漏掉重要的交互作用 |
| 非線性配適通常預測更準 | 要交互作用得手動加 $X_j \times X_k$ 或二維的 $f_{jk}$ 項 |
| 因為是加法的,可以固定其他變數單獨看某一個變數的效果 | 二維平滑器(thin-plate spline 之類)不在這一章的範圍 |
| 每個 $f_j$ 的平滑程度可以用自由度總結 | 完全一般的模型還是得靠第 8 章的隨機森林與提升法 |
整套邏輯搬到分類問題只要把 (7.17) 的 logit 換成加法形式:
$$\log\left(\frac{p(X)}{1 - p(X)}\right) = \beta_0 + f_1(X_1) + f_2(X_2) + \cdots + f_p(X_p) \tag{7.18}$$ISLP 圖 7.13 對 Wage 配 $I(\text{wage} > 250)$,
結果最後一張圖的第一個層級 <HS 信賴帶大到看不出東西——
因為那個層級裡一個高收入者都沒有(lab 儲存格 105 的交叉表:268 個人、0 個高收入者)。
拿掉那群人重配就正常了(圖 7.14)。這是很典型的一課:
模型爆掉的時候先去看列聯表,不要先怪演算法。
保住的是加法可解釋性。因為模型是 $\beta_0 + \sum_j f_j(x_j)$,每個變數的貢獻可以單獨畫出來、單獨解讀:「固定 age 與 education,wage 隨 year 微幅上升」這種句子講得出來,而且圖 7.11 那三張圖就是證據。線性模型的 $\beta_j$ 也有這個好處,但 GAM 不必假設那個關係是直線。額外的好處是每個 $f_j$ 的複雜度可以用自由度總結,一個數字就講完。
放棄的是交互作用。加法性意味著「age 的效果」跟 education 是什麼無關。如果現實是「大學畢業的人薪水在 40 歲達到高峰,高中畢業的人在 30 歲」,GAM 抓不到——它只會給你一條平均起來的 $f_2(\text{age})$。
要補救有兩條路:手動加 $X_j \times X_k$ 這種乘積項,或者加二維的 $f_{jk}(X_j, X_k)$(用二維平滑器配,講義附錄的 thin-plate spline 就是這個)。但兩條路都要你事先知道哪一對變數有交互作用。如果你不知道、又有很多變數,那就該去第 8 章找隨機森林與提升法——它們自動抓交互作用,代價是失去這裡的可解釋性。書上的定位很準:GAM 是線性模型與完全無母數方法之間一個有用的折衷。
pygam 配 GAMs_gam(0) 是 age 的平滑樣條、s_gam(1, n_splines=7) 是 year(year 只有 7 個相異值,所以基底也只給 7 個)、f_gam(2, lam=0) 是 education 的類別項且不收縮。
第二段把兩個平滑項的 λ 用 approx_lam(..., df=4+1) 反解成「4 個自由度」(加 1 是因為 df 含截距),再重配一次。先 fit 再設 lam 再 fit 的順序是必要的——approx_lam 要用到 fit 時才建好的節點資訊。上面元件的「回到 lab 的設定」就是這一組(age df 5、year df 5)。
Ch07-nonlin-lab-zh.ipynb · 儲存格 82、86
LinearGAM
=============================================== ==========================================================
Distribution: NormalDist Effective DoF: 12.9927
Link Function: IdentityLink Log Likelihood: -24117.907
Number of Samples: 3000 AIC: 48263.7995
AICc: 48263.94
GCV: 1246.1129
Scale: 1236.4024
Pseudo R-Squared: 0.2928
==========================================================================================================
Feature Function Lambda Rank EDoF P > x Sig. Code
================================= ==================== ============ ============ ============ ============
s(0) [465.0491] 20 5.1 1.11e-16 ***
s(1) [2.1564] 7 4.0 8.10e-03 **
f(2) [0] 5 4.0 1.11e-16 ***
intercept 1 0.0 1.11e-16 ***
==========================================================================================================
Significance codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
WARNING: Fitting splines and a linear function to a feature introduces a model identifiability problem
which can cause p-values to appear significant when they are not.
WARNING: p-values calculated in this manner behave correctly for un-penalized models or models with
known smoothing parameters, but when smoothing parameters have been estimated, the p-values
are typically lower than they should be, meaning that the tests reject the null too readily.
/tmp/ipython-input-3870570873.py:1: UserWarning: KNOWN BUG: p-values computed in this summary are likely much smaller than they should be.
Please do not make inferences based on these values!
Collaborate on a solution, and stay up to date at:
github.com/dswah/pyGAM/issues/163
gam_full.summary()幾個要對的數字:Effective DoF 12.9927(age 5.1 + year 4.0 + education 4.0 + 截距)、GCV 1246.1129、Pseudo R² 0.2928——上面元件的「回到 lab 的設定」右側顯示的就是這三個數字,由 tools/frames/gen_nonlin.py 用同一組設定重算,對到小數第四位。
然後注意那三段警告。pygam 自己說:「平滑參數是估出來的時候,這裡的 p 值會比該有的小很多,不要拿它做推論」(pyGAM issue #163)。這是很好的示範——套件印給你的東西不代表你可以引用它。要檢定就用 lab 儲存格 96 的 anova_gam() 比巢狀模型。
Ch07-nonlin-lab-zh.ipynb · 儲存格 98
如果真實情況是「大學畢業者的薪水在 40 歲達到高峰,高中畢業者在 30 歲」,(7.16) 這個 GAM 會發生什麼事?
下面幾題取自 ISLP §7.9 的課後習題,題號都對得回課本。先自己想過再點選項; 每個選項——包含錯的——都寫了為什麼。想看完整解答再對照下面3個站。
第 1 題要你證明 $f(x) = \beta_0 + \beta_1 x + \beta_2 x^2 + \beta_3 x^3 + \beta_4 (x-\xi)^3_+$ 真的是立方樣條。為什麼加上 $\beta_4 (x-\xi)^3_+$ 這一項不會破壞 ξ 上的連續性與一、二階導數連續?
第 2 題把懲罰換成 $\lambda \int \left[g^{{(m)}}(x)\right]^2 dx$,要你畫出各種 (λ, m) 下的 $\hat g$。當 $\lambda = \infty$、$m = 3$ 時,$\hat g$ 長什麼樣?
第 3 題給基底 $b_1(X) = X$、$b_2(X) = (X-1)^2 I(X \ge 1)$,配出 $\hat\beta_0 = 1, \hat\beta_1 = 1, \hat\beta_2 = -2$,要你畫出 $X \in [-2, 2]$ 的曲線。下面哪個描述是對的?
第 5 題比較兩個平滑器:$\hat g_1$ 懲罰 $\int \left[g^{{(3)}}\right]^2$,$\hat g_2$ 懲罰 $\int \left[g^{{(4)}}\right]^2$。$\lambda \to \infty$ 時,哪一個的訓練 RSS 較小?
考前把這一頁掃過去就好。
| 方法 | 基底/機制 | 彈性由什麼控制 | 自由度 | 邊界 | 選參數的辦法 |
|---|---|---|---|---|---|
| 多項式 | $x, x^2, \ldots, x^d$ | 次數 d | d + 1 | 差 | ANOVA 或 CV |
| 階梯函數 | $I(c_k \le x < c_{k+1})$ | 切點數 K | K + 1 | 尚可 | CV |
| 線性樣條 | $x, (x-\xi_k)_+$ | 節點數 K | K + 2 | 尚可 | CV |
| 立方樣條 | $x, x^2, x^3, (x-\xi_k)^3_+$ | 節點數 K | K + 4 | 差(帶會爆) | CV(圖 7.6 右) |
| 自然樣條 | 立方樣條 + 兩端線性 | 節點數 K | K + 2 | 好 | CV(圖 7.6 左) |
| 平滑樣條 | 全部 x 當節點 + 二階導數懲罰 | λ | df$_\lambda$(連續,2 到 n) | 好 | LOOCV 捷徑 / GCV |
| 局部迴歸 | 鄰域內加權最小平方 | 跨距 s | 以等效 df 表示 | 差(單邊資料) | CV |
| 多項式次數 d | 1 | 2 | 4 | 8 | 15 |
|---|---|---|---|---|---|
| 訓練 MSE | 1674.1 | 1597.8 | 1590.5 | 1587.9 | 1585.1 |
| 10-fold CV MSE | 1676.7 | 1600.8 | 1596.0 | 1597.2 | 1603.3 |
| 80 歲端 95% 帶寬 | 10.0 | 23.3 | 53.5 | 76.8 | 78.3 |
| 中央(49 歲)帶寬 | 3.5 | 3.7 | 4.4 | 5.6 | 7.3 |
| 樣條(三個內部節點 25/40/60) | 參數個數 | 18 歲端帶寬 | 80 歲端帶寬 |
|---|---|---|---|
立方樣條 bs() | 7 = K + 4 | 37.1 | 65.8 |
自然樣條 ns() | 5 = K + 2 | 20.2 | 37.1 |
平滑樣條:pygam 的
gridsearch() 依 GCV 選出 λ = 251.19、dfλ = 5.64;
課本圖 7.8 用 LOOCV 選出 6.8。GAM(age df 5、year df 5):
Effective DoF 12.99、GCV 1246.1、Pseudo R² 0.2928、deviance 3 693 143——
跟 lab 儲存格 98 的 12.9927 / 1246.1129 / 0.2928 與儲存格 96 的
3.693143e+06 相符。
| 名稱 | 式子 | 備註 |
|---|---|---|
| 多項式迴歸 | $y = \beta_0 + \beta_1 x + \cdots + \beta_d x^d + \varepsilon$ | 式 7.1 |
| 配適值的變異 | $\widehat{\operatorname{Var}}[\hat f(x_0)] = \ell_0^{\mathsf T} \hat C \ell_0$ | 式 7.2 腳註;信賴帶的來源 |
| 階梯函數 | $y = \beta_0 + \beta_1 C_1(x) + \cdots + \beta_K C_K(x) + \varepsilon$ | 式 7.5;丟掉 $C_0$ |
| 基底函數框架 | $y = \beta_0 + \sum_{k=1}^{K} \beta_k b_k(x) + \varepsilon$ | 式 7.7;整章的骨架 |
| 截斷冪基底 | $h(x, \xi) = (x-\xi)^3_+$ | 式 7.10;每個節點加一個 |
| 立方樣條的自由度 | K + 4 | K 個內部節點,含截距 |
| 自然樣條的自由度 | K + 2 | 兩端各 2 個線性約束 |
| 平滑樣條 | $\min_g \sum_i (y_i - g(x_i))^2 + \lambda \int g''(t)^2 dt$ | 式 7.11;損失+懲罰 |
| 有效自由度 | $\mathrm{df}_\lambda = \sum_i \{S_\lambda\}_{ii}$,$\hat g_\lambda = S_\lambda y$ | 式 7.12–7.13;λ: 0→∞ 時 df: n→2 |
| 平滑樣條的 LOOCV | $\sum_i \left[\dfrac{y_i - \hat g_\lambda(x_i)}{1 - \{S_\lambda\}_{ii}}\right]^2$ | 配一次就算完,對照式 5.2 |
| 局部迴歸 | $\min_{\beta_0,\beta_1} \sum_i K_{i0}(y_i - \beta_0 - \beta_1 x_i)^2$ | 式 7.14;演算法 7.1 |
| GAM(迴歸) | $y = \beta_0 + \sum_j f_j(x_j) + \varepsilon$ | 式 7.15–7.16 |
| GAM(分類) | $\log\dfrac{p(X)}{1-p(X)} = \beta_0 + \sum_j f_j(X_j)$ | 式 7.18 |
本頁「預期輸出」逐字取自課程 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 第 7 章,正面是中文術語(附英文原名)。 先看正面、心裡默想定義,再翻面對答案;洗牌後再過一輪,直到每張都能不看答案講出來。