得剛剛好:從多項式到 GAM

ISLP 第 7 章 — 對應講義 07
多項式|階梯函數|基底函數|迴歸樣條|自然樣條|平滑樣條|局部迴歸|GAM
向下捲動開始互動
📌 本頁使用方式(ISLP Ch.7|講義 07)

照節次讀:每節先讀說明,動手玩互動元件——先預測結果,再按按鈕驗證。 ② 對照講義:每個 §徽章都標了 ISLP 節號與講義頁碼,細節與完整推導請回講義與課本。 ③ 每節做 quiz:答錯就回到該節重讀,不要往下跳;錯的選項也寫了「錯在哪」。 ④ 最後翻關鍵詞彙卡自測術語,並用 REF 總覽當速查表。

CONTENTS · 內容目錄
PROLOGUE · 開場

放寬線性假設,但不要放棄可解釋性 ISLP §7 開頭講義 07 · p.2–4

到目前為止的每一個模型都長成 $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 檢定,全部可以照用——只是預測變數換人了。

六條路線,一個框架 1. 多項式迴歸:加 x²、x³…,全域一條曲線。
2. 階梯函數:把 x 切段,每段一個常數。
3. 迴歸樣條:分段三次多項式,在節點上接得平滑。
4. 自然樣條:樣條再加兩端線性約束,邊界穩定得多。
5. 平滑樣條:不選節點,直接懲罰彎曲程度。
6. 局部迴歸:每個點只看鄰居,加權配一條線。
前四個是同一個基底函數框架的特例(§7.3),第 5、6 個是「損失+懲罰」與 「只看附近」兩種不同的想法。最後 §7.7 的 GAM 把它們推廣到多個變數。
方法彈性怎麼來的由什麼控制邊界行為一句話
多項式拉高次數 dd(整數)很糟,會暴衝最省事,但 d > 4 就別碰
階梯函數多切幾段切點數 K還可以(常數)沒有自然切點就會漏掉趨勢
立方樣條多加節點節點數 K(df = K+4)偏糟,信賴帶會爆開次數固定 3,靠節點取得彈性
自然樣條多加節點節點數 K(df = K+2),兩端是直線同樣的彈性、更穩的兩端
平滑樣條調 λ有效自由度 dfλ(連續)好(兩端線性)不必選節點,只要選 λ
局部迴歸調鄰域大小跨距 s偏糟(單邊資料)每次預測都要用到全部資料

這一章的講義有 54 頁,順序就是上面這張表。往下每一節都有一個可以拖、可以推的元件—— 先猜結果再按按鈕,猜錯的地方才是你真正學到東西的地方。

QUIZ · 為什麼它還算線性模型

ageage²age³age⁴ 一起丟進迴歸,為什麼書上還說這是「一般的線性迴歸模型」?

(A) 因為模型對係數是線性的;x 的轉換是事先固定、已知的,只是換了預測變數
(B) 因為配出來的曲線在資料範圍內仍然近似一條直線
(C) 因為 age² 與 age³ 跟 age 高度相關,實際上只有一個自由的預測變數
PART 01 · 多項式迴歸

加高次項:好用,但邊界會爆 ISLP §7.1講義 07 · p.5–7

最直接的放寬:把直線換成 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 就看得到。

把滑桿從 1 推到 15,盯著兩端的信賴帶(淡藍)而不是中間的曲線。
次數 d4
怎麼看這張圖 圖 7.1
上圖:灰點是 Wage 的 90 筆子樣本(只是背景),紅線與淡藍帶是用全部 3000 筆配出來的 d 次多項式與 95% 信賴帶。下圖:訓練 MSE(藍)一路往下,10-fold CV MSE(紅)在 d = 4 觸底之後回頭往上。
這個次數的成績
次數 d4
訓練 MSE
10-fold CV MSE
18 歲那端的帶寬
80 歲那端的帶寬
中央(約 49 歲)帶寬
重點在兩端的帶寬
d 從 1 推到 15,中央的帶寬只從 3.5 長到 7.3,但 80 歲那端從 10.0 長到 78.3——邊界是多項式付出代價的地方。ISLP 圖 7.7 用 degree 15 對照 15 個自由度的自然樣條,講的就是這件事。

講義完整實作:四次多項式與它的 t 檢定

lab 07 · degree 4 多項式(Wage:age → wage)
poly_age = MS([poly('age', degree=4)]).fit(Wage) M = sm.OLS(y, poly_age.transform(Wage)).fit() summarize(M)
預期輸出
                            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
lab 07 · 用 ANOVA 逐步比較 degree 1 到 5
models = [MS([poly('age', degree=d)]) for d in range(1, 6)] Xs = [model.fit_transform(Wage) for model in models] anova_lm(*[sm.OLS(y, X_).fit() for X_ in Xs])
預期輸出
   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
lab 07 · 多項式邏輯斯迴歸:Pr(wage > 250)
X = poly_age.transform(Wage) high_earn = Wage['high_earn'] = y > 250 # shorthand glm = sm.GLM(y > 250, X, family=sm.families.Binomial()) B = glm.fit() summarize(B)
預期輸出
                           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
QUIZ · 多項式的次數

同一份 Wage 資料上,d 從 1 加到 15,訓練 MSE 一路從 1674 掉到 1585,但 10-fold CV MSE 在 d = 4 觸底(1596)之後回頭升到 1603。這說明什麼?

(A) d 超過 4 之後多出來的彈性只是在配噪音,所以測試誤差反而變差
(B) CV MSE 的估計本身有偏誤,所以它上升只是雜訊,該相信訓練 MSE
(C) 說明 Wage 資料裡 age 與 wage 的真實關係是四次多項式
PART 02 · 階梯函數

把 x 切段,每段給一個常數 ISLP §7.2講義 07 · p.8–10

多項式有個結構上的毛病:它是全域的。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}$$
係數怎麼讀 因為每個 x 只落在一段裡,$\beta_0$ 就是第一段的平均 y, $\beta_j$ 是第 j+1 段相對於第一段的增量。 這也意味著:只有 x 一個變數時,每一段的配適值就是那一段的樣本平均—— 下面的元件顯示的就是這個。
如果改成「保留全部 K+1 個指示變數、去掉截距」(lab 儲存格 39 的 pd.get_dummies() 就是這樣),係數會直接變成各段的平均值。 兩種編碼配出來的曲線完全一樣。
拖橘色的切點看每一段的平均怎麼變。用上面的選單改段數。
段數
怎麼玩 LIVE
拖動橘色的切點。每一段的水平紅線就是該段的樣本平均,也就是階梯函數的配適值。切點會被夾在鄰居之間(至少差 2 歲),拖不過去是刻意的。
每一段
整體
段數4
切點
RSS
為什麼第一段常常很難看
ISLP 圖 7.2 左的第一段完全錯過了「薪水隨年齡上升」的趨勢——一個常數沒辦法表達段內的斜率。沒有自然切點的時候,階梯函數會漏掉 action。不過流病學與生物統計很愛用它(例如固定的 5 歲一組),因為切點本身有現實意義。

講義完整實作:pd.qcut 切四段

lab 07 · 用分位數切點配階梯函數
cut_age = pd.qcut(age, 4) X_cut = pd.get_dummies(cut_age) model = sm.OLS(y, X_cut).fit() summarize(model)
預期輸出
                     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
QUIZ · 階梯函數

只有 age 一個預測變數時,階梯函數在每一段的配適值是什麼?

(A) 那一段內所有觀測值的 y 平均
(B) 那一段兩個端點上迴歸直線的中點
(C) 全體樣本平均,加上該段切點對應的偏移量
PART 03 · 基底函數框架

多項式與階梯其實是同一件事的兩個特例 ISLP §7.3講義 07 · p.10

停下來看一下前兩節做了什麼。多項式迴歸用的預測變數是 $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 檢定原封不動搬過來。

這一節是整章的骨架 多項式:$b_j(x) = x^j$
階梯函數:$b_j(x) = I(c_j \le x < c_{j+1})$
立方樣條:$x, x^2, x^3$ 再加每個節點一個 $(x - \xi_k)^3_+$
自然樣條、B-樣條:同一個函數空間的另外幾組基底
換基底就換方法,但配適的機器(最小平方)從頭到尾沒換過。 小波(wavelet)與傅立葉級數也是合法的選擇,只是這一章不講。

下面這個元件是本頁最重要的一個。左邊每一個核取鈕都是一個基底函數; 你選哪幾個,下面就用那幾個去配。三個預設按鈕分別把選擇切成 「三次多項式」、「立方樣條」、「階梯函數」——你會看到它們只是勾選項不同而已

勾選基底函數,看它們怎麼組合成一條曲線。三個預設按鈕在最右邊。
怎麼玩 LIVE
上圖是被選中的每一個基底函數的形狀(各自正規化到同高,否則 x³ 會把別人壓扁)。下圖是資料與它們線性組合出來的曲線 $\hat f(x) = \sum_k \hat\beta_k b_k(x)$。節點固定在 33.75 與 51(Wage 的 25% 與 75% 分位數)。
目前的模型
選了幾個基底4
參數個數4
RSS
三個預設分別是什麼
三次多項式=1, x, x², x³(4 個參數)
立方樣條=上面再加 (x−33.75)³₊、(x−51)³₊(6 個參數=K+4)
階梯函數=1, I(x≥33.75), I(x≥51)(3 段,3 個參數)
按下去看曲線怎麼變。三種「不同的方法」在程式裡只差幾個勾。

講義完整實作:把樣條的次數調成 0,就是階梯函數

lab 07 · degree=0 的 B-樣條 ≡ 分段常數
bs_age0 = MS([bs('age', df=3, degree=0)]).fit(Wage) Xbs0 = bs_age0.transform(Wage) summarize(sm.OLS(y, Xbs0).fit())
預期輸出
                               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
QUIZ · 基底函數框架

基底函數框架要求 $b_1(\cdot), \ldots, b_K(\cdot)$ 必須「事先選定、固定且已知」。這個要求為什麼這麼重要?

(A) 因為這樣模型才對參數線性,第 3 章的最小平方與推論工具(標準誤、F 檢定)才能原封不動使用
(B) 因為固定的基底函數保證配出來的曲線一定平滑
(C) 因為只有固定的基底函數才能讓設計矩陣可逆
PART 04 · 迴歸樣條

分段三次多項式,在節點上接得平滑 ISLP §7.4.1–7.4.3講義 07 · p.11–20

多項式的毛病是全域、次數一高就在邊界暴衝。階梯函數的毛病是段內沒有斜率。 把兩個想法縫起來:分段配低次多項式。節點(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 次樣條。

拖節點、切換連續性層級,看曲線從斷裂變平滑,同時看自由度從 8 掉到 5。
怎麼玩 圖 7.3
拖動橘色的節點 ξ,再用下面的選項切換要求到第幾階連續。曲線會從斷裂 → 折角 → 平滑。每加一個約束,右邊的參數個數就少 1。
目前狀態
節點 ξ50.0
約束層級二階導數連續
參數個數(自由度)5
RSS
基底是怎麼被拿掉的
完整的 8 個基底是 1, x, x², x³ 再加四個截斷項 I(x>ξ), (x−ξ)₊, (x−ξ)²₊, (x−ξ)³₊。要求連續=拿掉 I(x>ξ)(它是唯一會讓函數值跳的),再要求一階導數連續=再拿掉 (x−ξ)₊再要求二階導數連續=再拿掉 (x−ξ)²₊。剩下的 5 個就是單節點立方樣條的基底,K + 4 = 5。

那要怎麼真的把約束配進去?不必解限制式最小平方——換基底就好。 從三次多項式的基底 $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)。

觀念釐清
Q:為什麼三次樣條剛好要求到二階導數連續?多一階或少一階會怎樣?

先看少一階。只要求函數值與一階導數連續,二階導數可以跳——二階導數是「斜率變化的速度」,也就是曲率。曲率突然改變,人眼看得出來:曲線在節點附近會有一個「彈一下」的感覺。所以只到一階不夠。

再看多一階。三次多項式的三階導數是常數 $6d$;如果連三階導數也要求連續,那 $d$ 在節點兩側必須相同,二階導數又是 $2c + 6dx$,連續加上 $d$ 相同就迫使 $c$ 也相同…一路推下去,兩段會退化成同一個三次多項式——節點等於不存在,整條曲線變回全域三次多項式,彈性全部消失。所以三次的極限就是二階。

這也是「d 次樣條=分段 d 次多項式 + 導數連續到 d−1 階」這個定義的來源:d−1 階是還能留下彈性的最高要求。書上還補了一句很實用的話:三階導數的不連續人眼幾乎偵測不到,所以立方樣條「看起來」就是平滑的——這就是為什麼實務上 degree 3 幾乎是唯一的選擇,更高次只是多花自由度,看起來沒有更平滑。

講義完整實作:BSplinebs()

lab 07 · 三個內部節點的立方樣條
bs_ = BSpline(internal_knots=[25,40,60], intercept=True).fit(age) bs_age = bs_.transform(age) bs_age.shape bs_age = MS([bs('age', internal_knots=[25,40,60])]) Xbs = bs_age.fit_transform(Wage) M = sm.OLS(y, Xbs).fit() summarize(M)
預期輸出
                                            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
lab 07 · 用 df 代替節點位置
BSpline(df=6).fit(age).internal_knots_
預期輸出
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右下折線
QUIZ · 樣條的自由度

age 上配一個有 5 個內部節點的立方樣條(含截距),要估幾個參數?

(A) 9 個(K + 4 = 5 + 4)
(B) 24 個(6 段各 4 個參數)
(C) 5 個(節點數就是自由度)
PART 05 · 自然樣條

在兩端加線性約束,邊界的變異就收斂了 ISLP §7.4.4–7.4.5講義 07 · p.21–23

立方樣條看起來很漂亮,但有一個藏起來的問題:兩端的變異很大。 在最小節點以左、最大節點以右,資料只從單邊來,三次多項式卻還有完整的四個自由度可以亂扭。 ISLP 圖 7.4 就在示範這件事——三個節點的立方樣條,兩端的信賴帶「appear fairly wild」。

自然樣條(natural spline)的修法很直接:多加兩個邊界約束, 要求函數在兩端的區域是線性的。線性只需要 2 個參數(截距 + 斜率), 而三次要 4 個,所以每一端省下 2 個自由度,兩端合計省 4 個。 K 個內部節點的自然立方樣條因此只用 K + 2 個參數(含截距)。

圖表需要連網載入 Chart.js。此圖的重點:同樣三個節點(25/40/60),立方樣條在 80 歲那端的 95% 信賴帶寬 65.8,自然樣條只有 37.1——線性約束把邊界的變異砍掉一半。
兩條曲線用同樣的三個節點。比較的是兩端信賴帶的寬度。
怎麼看這張圖 圖 7.4
兩條實線是配適曲線,兩片淡色帶是各自的 95% 信賴帶。兩者用完全相同的三個內部節點(25、40、60),差別只在自然樣條多了兩端的線性約束。中間幾乎重疊,兩端差很多
實測(Wage 全體 n = 3000)
立方樣條 參數個數7
自然樣條 參數個數5
18 歲端帶寬(立方 / 自然)
80 歲端帶寬(立方 / 自然)
內部節點25 · 40 · 60
代價是什麼
自然樣條放棄了「兩端也可以彎」的能力。如果真實函數在邊界確實彎得厲害,自然樣條會配不出來(有偏差)。但邊界的資料本來就少,那裡的「彎」通常是噪音而不是訊號——所以這個交換在實務上幾乎總是划算的。

ISLP 圖 7.7 把這件事推到極端:15 個自由度的自然樣條 vs 15 次多項式。 兩者複雜度相同,但多項式在尾端狂野擺盪,自然樣條還很體面。 這就是「樣條通常優於多項式」的理由:樣條靠加節點取得彈性、把次數鎖在 3; 多項式只能靠拉高次數,而高次的代價全部集中在邊界。

觀念釐清
Q:節點該放幾個?放哪裡?

放哪裡:理論上該把節點放在「函數變化快」的地方,變化慢的地方少放。實務上幾乎沒人這樣做,因為你事先不知道哪裡變化快。標準做法是指定自由度,讓軟體把對應數量的節點放在資料的均勻分位數上——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() 配自然樣條

lab 07 · 五個自由度的自然樣條
ns_age = MS([ns('age', df=5)]).fit(Wage) M_ns = sm.OLS(y, ns_age.transform(Wage)).fit() summarize(M_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
QUIZ · 自然樣條

自然樣條相對於立方樣條,多了什麼約束、換到了什麼?

(A) 多了「兩端區域必須是線性」的約束,換到邊界處明顯較窄的信賴帶
(B) 多了「節點必須放在分位數上」的約束,換到不必自己選節點位置
(C) 多了「二階導數在節點連續」的約束,換到更平滑的曲線
PART 06 · 平滑樣條

換個想法:直接懲罰彎曲程度,用有效自由度描述複雜度 ISLP §7.5講義 07 · p.24–27

迴歸樣條的流程是:選節點 → 造基底 → 最小平方。平滑樣條(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}$$
這是「損失+懲罰」,跟脊迴歸同一個套路 第一項是損失,逼 g 貼近資料; 第二項是懲罰,逼 g 平滑。λ ≥ 0 是調整參數:
λ = 0:懲罰失效,g 會穿過每一個點(內插),RSS = 0,完全過度配適。
λ → ∞:懲罰無限重,只有 $\int g''^2 = 0$ 的函數活得下來—— 也就是直線。這時損失項退化成普通的線性最小平方, 所以 g 就是那條最小平方直線。
中間的 λ 給你介於兩者之間的東西。λ 控制的就是偏差–變異取捨。

令人意外的是這個無限維最佳化有漂亮的解:使 (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$$
圖表需要連網載入 Chart.js。此圖的重點:df = 2 幾乎是直線,df = 19 抖得離譜;pygam 依 GCV 選出 df = 5.64,跟課本圖 7.8 用 LOOCV 選出的 6.8 很接近。
推滑桿改有效自由度。df = 2 是直線,df 愈大愈抖。
df5.0
怎麼看這張圖 圖 7.8
灰點是 90 筆子樣本(背景),曲線是用全部 3000 筆配的平滑樣條。滑桿的刻度是有效自由度 dfλ,不是 λ 本身——因為 df 才是人看得懂的尺度(λ = 5.2×10⁹ 對上 df = 2)。
這個 df 的成績
dfλ5.0
對應的 λ
GCV
解釋的離差比例
gridsearch 選出來的是什麼
pygamgridsearch()GCV(廣義交叉驗證)挑 λ,在這份資料上選出 λ = 251.19、dfλ = 5.64、GCV = 1596.88。課本圖 7.8 的 6.8 是用 LOOCV 挑的,兩者的準則不同、答案自然不會完全一樣——但都落在 5~7,結論一致:這份資料不需要 16 個自由度。
觀念釐清
Q:「自由度」在這一章出現了三次,它們是同一回事嗎?

不完全是。前兩個是同一回事,第三個是另一種東西。

(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 個」。

講義完整實作:用 df 反解 λ

lab 07 · approx_lam:指定自由度,反解懲罰參數
age_term = gam.terms[0] lam_4 = approx_lam(X_age, age_term, 4) age_term.lam = lam_4 degrees_of_freedom(X_age, age_term)
預期輸出
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
QUIZ · 平滑樣條的懲罰

$\lambda \to \infty$ 時,(7.11) 的解 $\hat g$ 會變成什麼?

(A) 最小平方直線——因為只有二階導數恆為 0 的函數才不被懲罰
(B) 常數函數 $\hat g(x) = \bar y$——因為懲罰項要求函數完全不變化
(C) 在每個 xᵢ 上都有節點的自然立方樣條——因為解永遠是自然立方樹條
PART 07 · 局部迴歸

只看附近的點,加權配一條線 ISLP §7.6講義 07 · p.28–33

再換一個想法。前面所有方法都在配一個全域的函數形式 (就算是分段的,段的邊界也是事先定死的)。局部迴歸(local regression)說: 要預測 $x_0$ 上的值,就只用 $x_0$ 附近的點,配一條加權直線,取它在 $x_0$ 的值。 換一個 $x_0$,重配一次。

ISLP 演算法 7.1 · 在 X = x₀ 的局部迴歸 1. 取出 x 離 x₀ 最近的 s = k/n 比例的訓練點。
2. 給鄰域內每個點一個權重 $K_{i0}$:最近的最重,最遠的那個剛好是 0, 鄰域外全部是 0。
3. 用這些權重做加權最小平方,找 $\hat\beta_0, \hat\beta_1$ 使 $\sum_{i=1}^{n} K_{i0}\left(y_i - \beta_0 - \beta_1 x_i\right)^2$ 最小。
4. 在 x₀ 的配適值就是 $\hat f(x_0) = \hat\beta_0 + \hat\beta_1 x_0$。

要做的選擇有三個:權重函數 K 怎麼定、第 3 步配常數/直線/二次、以及 跨距(span)s 取多少。前兩個影響不大, s 才是關鍵——它扮演的角色跟平滑樣條的 λ 一樣。 s 小則鄰域窄、曲線抖;s 大則鄰域寬、接近全域配適。s 一樣可以用交叉驗證選。

推滑桿改 span,按「開始」讓 x₀ 從左掃到右,曲線一點一點長出來。
span s0.30
虛擬碼 CODE
for x0 in 格點: d = |x - x0|;取最近的 k = s·n 個 w = tricube(d / d_max) b0, b1 = 加權最小平方(x, y, w) f[x0] = b0 + b1 * x0
這個目標點
跨距 s0.30
鄰域點數 k
目標點 x₀
局部斜率 b₁
配適值 f̂(x₀)
圖上的元素 圖 7.9
橘色垂直線是目前的 x₀,橘點是它的鄰域,黃色鐘形是 tricube 權重(越高越重),短橘線段是這一次的加權迴歸直線,紅色粗曲線是掃過去累積出來的 f̂。淡紅細線是這個 span 的完整曲線。
兩個要記住的限制
1. 記憶式(memory-based):每做一次預測都要用到全部訓練資料,沒有一組係數可以存下來。模型體積 = 資料體積。
2. 維度一高就垮:p 超過 3 或 4,x₀ 附近幾乎找不到點(跟第 3 章 KNN 的維度詛咒同一個病)。所以局部迴歸主要用在 1~2 維,或者當 GAM 的單變數積木。
span s鄰域曲線ISLP 圖 7.10 標的有效自由度
0.220% 的資料抖,跟著局部起伏16.4
0.770% 的資料平滑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 完全對上—— 它示範的是機制,不是重現套件的輸出。

講義完整實作:statsmodelslowess()

lab 07 · 兩個 span 的局部線性迴歸
lowess = sm.nonparametric.lowess fig, ax = subplots(figsize=(8,8)) ax.scatter(age, y, facecolor='gray', alpha=0.5) for span in [0.2, 0.5]: fitted = lowess(y, age, frac=span, xvals=age_grid) ax.plot(age_grid, fitted, label='{:.1f}'.format(span), linewidth=4) ax.set_xlabel('Age', fontsize=20) ax.set_ylabel('Wage', fontsize=20); ax.legend(title='span', fontsize=15);

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
QUIZ · 局部迴歸

為什麼局部迴歸被叫做「記憶式(memory-based)」方法?

(A) 因為每算一次預測都要重新用到全部訓練資料,沒有一組固定的係數可以存起來
(B) 因為它把每個訓練點的預測值先算好存起來,查表時直接取用
(C) 因為它需要記住每個節點的位置與對應的係數
PART 08 · GAM

每個變數各配一條曲線,再加起來 ISLP §7.7講義 07 · p.34–48

前面七節都只處理一個預測變數。現在把它們裝到多變數上。 多元線性迴歸是 $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。

逆向配適在做什麼 輪流更新每一個 $f_j$:更新第 j 個的時候把其他項固定, 對偏殘差(partial residual)做一次單變數平滑。以 p = 3、更新 $f_3$ 為例:
$r_i = y_i - \hat f_1(x_{i1}) - \hat f_2(x_{i2})$, 然後把 $r_i$ 當反應變數、對 $X_3$ 配一個平滑樣條,得到新的 $\hat f_3$。
換下一個變數,重複,直到不再變動。好處是你只要有單變數的配適工具, 就能配任意多變數的加法模型。ISLP §7.9 第 11、12 題用線性迴歸版本讓你手動跑一遍。
圖表需要連網載入 Chart.js。f̂(year):薪水隨年份微幅上升,幾乎是直線(可能是通膨)。
圖表需要連網載入 Chart.js。f̂(age):中年最高,年輕與年長都低——這一項明顯需要非線性。
圖表需要連網載入 Chart.js。f̂(education):學歷愈高薪水愈高,五個層級各一個常數。
推兩個滑桿改 age 與 year 的自由度,右邊看模型整體的成績怎麼變。
age df5.0year df5.0
怎麼看這三張圖 圖 7.11–7.12
每一張都是一個 $\hat f_k$ 的偏依賴圖:其他變數固定在平均值,只讓這一個變數在格點上跑。虛線是逐點 95% 信賴帶。三張圖的縱軸都是「對 wage 的效果」,所以可以直接比高低——age 與 education 的擺幅遠大於 year。
這一組 df 的成績
age 的 df5.0
year 的 df5.0
模型總有效自由度
deviance(=RSS)
Pseudo R²
GCV
為什麼 education 沒有滑桿
education 是類別變數,在 pygam 裡用 f_gam(2, lam=0) 指定——類別項就是每個層級一個常數,沒有「平滑程度」可以調,而且 lam=0 表示完全不收縮。它的自由度固定是「層級數 − 1」。
加 df 換到多少
age 的 df 從 2 加到 10,Pseudo R² 只從 0.263 爬到 0.294;year 的 df 從 2 加到 6,R² 從 0.292 到 0.293——year 幾乎不需要非線性。lab 儲存格 96 的 ANOVA 就是在檢定這件事,結論是「year 需要線性項(p = 1.7e−07)但不需要非線性」。
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)。這是很典型的一課: 模型爆掉的時候先去看列聯表,不要先怪演算法。

觀念釐清
Q:GAM 保住了什麼、放棄了什麼?

保住的是加法可解釋性。因為模型是 $\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 配 GAM

lab 07 · 模型規格與用 df 設定平滑程度
gam_full = LinearGAM(s_gam(0) + s_gam(1, n_splines=7) + f_gam(2, lam=0)) Xgam = np.column_stack([age, Wage['year'], Wage['education'].cat.codes]) gam_full = gam_full.fit(Xgam, y) age_term = gam_full.terms[0] age_term.lam = approx_lam(Xgam, age_term, df=4+1) year_term = gam_full.terms[1] year_term.lam = approx_lam(Xgam, year_term, df=4+1) gam_full = gam_full.fit(Xgam, y)

s_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

講義完整實作:GAM 的摘要,以及它自己承認的 bug

lab 07 · gam_full.summary()
gam_full.summary()
預期輸出
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.1129Pseudo R² 0.2928——上面元件的「回到 lab 的設定」右側顯示的就是這三個數字,由 tools/frames/gen_nonlin.py 用同一組設定重算,對到小數第四位。
然後注意那三段警告。pygam 自己說:「平滑參數是估出來的時候,這裡的 p 值會比該有的小很多,不要拿它做推論」pyGAM issue #163)。這是很好的示範——套件印給你的東西不代表你可以引用它。要檢定就用 lab 儲存格 96 的 anova_gam() 比巢狀模型。

來源:Ch07-nonlin-lab-zh.ipynb · 儲存格 98
QUIZ · GAM 的加法性

如果真實情況是「大學畢業者的薪水在 40 歲達到高峰,高中畢業者在 30 歲」,(7.16) 這個 GAM 會發生什麼事?

(A) 抓不到——它只會給一條平均起來的 f̂(age),因為加法性假設 age 的效果與 education 無關
(B) 會抓到——因為 f̂(age) 是非線性的,非線性本身就包含了交互作用
(C) 會抓到——因為 education 是類別變數,f̂₃ 會為每個層級各配一條 age 曲線
EXERCISES · 練習

動手驗證:ISLP 第 7 章精選題 ISLP §7.9 習題

下面幾題取自 ISLP §7.9 的課後習題,題號都對得回課本。先自己想過再點選項; 每個選項——包含錯的——都寫了為什麼。想看完整解答再對照下面3個站。

EXERCISE 1 · ISLP 7.9 第 1 題(c)(d)(e)

第 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_+$ 這一項不會破壞 ξ 上的連續性與一、二階導數連續?

(A) 因為 $(x-\xi)^3$ 本身與它的一階、二階導數在 $x = \xi$ 都等於 0,只有三階導數會跳
(B) 因為 $\beta_4$ 通常估出來很小,所以造成的跳躍可以忽略
(C) 因為指示函數 $I(x > \xi)$ 在 ξ 上有定義,所以函數在該點連續
EXERCISE 2 · ISLP 7.9 第 2 題(d)

第 2 題把懲罰換成 $\lambda \int \left[g^{{(m)}}(x)\right]^2 dx$,要你畫出各種 (λ, m) 下的 $\hat g$。當 $\lambda = \infty$、$m = 3$ 時,$\hat g$ 長什麼樣?

(A) 一條二次曲線(最小平方拋物線)
(B) 一條直線(最小平方迴歸線)
(C) 一條穿過每一個資料點的內插曲線
EXERCISE 3 · ISLP 7.9 第 3 題

第 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]$ 的曲線。下面哪個描述是對的?

(A) $X < 1$ 是斜率 1 的直線(在 $X=1$ 到達 2);$X \ge 1$ 變成開口向下的拋物線,在 $X = 1.25$ 到最高的 2.125,$X = 2$ 掉回 1;整條曲線的值與斜率在 $X=1$ 都連續
(B) $X \ge 1$ 時從 2 單調下降到 1,因為 $\hat\beta_2 = -2$ 是負的
(C) 曲線在 $X = 1$ 有一個跳躍,因為指示函數 $I(X \ge 1)$ 在那裡開關
EXERCISE 4 · ISLP 7.9 第 5 題(a)

第 5 題比較兩個平滑器:$\hat g_1$ 懲罰 $\int \left[g^{{(3)}}\right]^2$,$\hat g_2$ 懲罰 $\int \left[g^{{(4)}}\right]^2$。$\lambda \to \infty$ 時,哪一個的訓練 RSS 較小?

(A) $\hat g_2$,因為它被逼成三次多項式,比被逼成二次多項式的 $\hat g_1$ 更有彈性
(B) $\hat g_1$,因為懲罰的導數階數較低,代表約束較弱
(C) 兩者相同,因為 $\lambda \to \infty$ 時兩個懲罰項都趨近於 0,模型退化成同一個
REFERENCE · 總覽

超越線性速查表 ISLP Ch.7

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

七種方法對照

方法基底/機制彈性由什麼控制自由度邊界選參數的辦法
多項式$x, x^2, \ldots, x^d$次數 dd + 1ANOVA 或 CV
階梯函數$I(c_k \le x < c_{k+1})$切點數 KK + 1尚可CV
線性樣條$x, (x-\xi_k)_+$節點數 KK + 2尚可CV
立方樣條$x, x^2, x^3, (x-\xi_k)^3_+$節點數 KK + 4差(帶會爆)CV(圖 7.6 右)
自然樣條立方樣條 + 兩端線性節點數 KK + 2CV(圖 7.6 左)
平滑樣條全部 x 當節點 + 二階導數懲罰λdf$_\lambda$(連續,2 到 n)LOOCV 捷徑 / GCV
局部迴歸鄰域內加權最小平方跨距 s以等效 df 表示差(單邊資料)CV

Wage 上的實測數字(本頁元件用的就是這些)

多項式次數 d124815
訓練 MSE1674.11597.81590.51587.91585.1
10-fold CV MSE1676.71600.81596.01597.21603.3
80 歲端 95% 帶寬10.023.353.576.878.3
中央(49 歲)帶寬3.53.74.45.67.3
樣條(三個內部節點 25/40/60)參數個數18 歲端帶寬80 歲端帶寬
立方樣條 bs()7 = K + 437.165.8
自然樣條 ns()5 = K + 220.237.1

平滑樣條:pygamgridsearch() 依 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 + 4K 個內部節點,含截距
自然樣條的自由度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
三個一定要記住的觀念 1. 多項式、階梯函數、樣條都是同一個基底函數框架的特例。 換掉 $b_k(\cdot)$ 就換掉方法,配適的機器(最小平方)從頭到尾沒變, 第 3 章的推論工具全部照用。
2. 樣條把次數鎖在 3、靠加節點取得彈性;多項式只能拉高次數,代價全落在邊界。 自然樣條再加兩端線性約束,實測把 80 歲那端的信賴帶從 65.8 砍到 37.1。
3. 「自由度」在這一章有兩種身分。 多項式的 d 與樣條的 K + 4 是老實的參數計數;平滑樣條的 df$_\lambda$ 是平滑矩陣的跡, 是連續值。它們可以放在同一把尺上比較彈性,但不是同一個東西。

本頁「預期輸出」逐字取自課程 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 的儲存格編號,可以直接回去對。

CARDS · 關鍵詞彙卡

關鍵詞彙卡:點卡片翻面 課程題庫 · 28 張

詞彙卡取自本章講義與 ISLP 第 7 章,正面是中文術語(附英文原名)。 先看正面、心裡默想定義,再翻面對答案;洗牌後再過一輪,直到每張都能不看答案講出來。