§7.6 局部迴歸

📖 ISLP §7.6 📄 pp. 303–305 ★★★☆☆ ⏱️ 約 20 分鐘
局部迴歸 記憶式方法 加權最小平方法 span 參數 非線性建模 核函數
← §7.5 平滑樣條 📑 課程首頁 §7.7 廣義可加模型 →

核心概念:近鄰的智慧

想像你在一個陌生的城市,想知道附近一間餐廳好不好吃。你不會去問整個城市的人——你只會問住在餐廳附近的人,而且鄰居的意見比遠方路人的意見更值得參考。這就是局部迴歸(Local Regression)的核心精神:在做預測時,只參考目標點附近的資料,離得越近的資料點權重越大。

局部迴歸(Local Regression)是一種非線性函數擬合方法:對於每個目標點 \(x_0\),只使用最接近的 \(k\) 筆訓練資料來計算該點的擬合值 \(\hat{f}(x_0)\)。與其擬合一條全體適用的曲線,局部迴歸「見招拆招」——每個點都用最相關的局部鄰居單獨計算。

演算法 7.1:局部迴歸的四步驟

課本對局部迴歸給出了清晰的操作步驟。以下是演算法 7.1 的完整描述:

Algorithm 7.1 Local Regression At \(X = x_0\) — ISLP pp. 304–305

步驟 1:蒐集鄰居

選定比例 \(s = k/n\),找出訓練集中最接近 \(x_0\) 的 \(k\) 個點。只有這 \(k\) 個點會參與計算,其餘的 \(n-k\) 個點權重為零。

步驟 2:賦予權重

對每個鄰居 \(x_i\) 賦予權重 \(K_{i0} = K(x_i, x_0)\):離 \(x_0\) 越近的點權重越高,最遠的鄰居權重為零。這形成一個鐘形權重分布(如課本 Figure 7.9 中的黃色鐘形疊加層)。

步驟 3:加權最小平方法

用步驟 2 的權重 \(K_{i0}\) 對鄰居做加權線性迴歸,最小化:

\[ \sum_{i=1}^{n} K_{i0} \, (y_i - \beta_0 - \beta_1 x_i)^2 \]
方程式 (7.14):局部加權最小平方法
注意:雖然求和寫的是 \(i=1\) 到 \(n\),但實際上只有 \(k\) 個鄰居有非零權重。

步驟 4:取擬合值

在 \(x_0\) 處的預測值就是加權迴歸在 \(x_0\) 的擬合值: \[ \hat{f}(x_0) = \hat{\beta}_0 + \hat{\beta}_1 x_0 \]

記憶式方法:局部迴歸被稱為「記憶式程序(memory-based procedure)」,因為每次要預測一個新點 \(x_0\),就必須重新執行整組步驟 1–4。這與 K-最近鄰(KNN)類似的特性——沒有真正的「訓練階段」,所有的訓練資料在每次預測時都派得上用場。

關鍵參數:Span s

局部迴歸有三個設計選擇:權重函數 \(K\) 的形式、迴歸階數(常數 / 線性 / 二次),以及 span \(s\)——用來計算每個 \(x_0\) 擬合值的鄰居比例。課本強調:span 是三者中最關鍵的參數

Span 的比喻:望遠鏡的視野

把局部迴歸想像成用望遠鏡觀察一座山的地形:

\[ s = \frac{k}{n} \quad \text{其中 } k \text{ 是每個目標點使用的鄰居數量} \]
Span 的定義:使用的訓練資料比例

課本實例:Wage 資料

課本 Figure 7.10 展示了用不同 span 值對 ISLP Wage 資料做局部線性迴歸的結果:

📚 James, Witten, Hastie, Tibshirani (2023). An Introduction to Statistical Learning with Python. Springer. §7.6, pp. 303–305, Figure 7.10.

Python 實作:Wage 資料的局部迴歸

以下展示用兩種不同 span 值對 Wage 資料做局部線性迴歸,重現課本 Figure 7.10 的核心概念。

# §7.6 局部迴歸:Wage 資料示範
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from ISLP import load_data

# 載入 Wage 資料
wage = load_data('Wage')
age = wage['age'].values
wage_vals = wage['wage'].values

# --- 局部線性迴歸函數 ---
def local_linear(x, y, x_grid, span):
    """對每個 x_grid 上的點做局部線性迴歸。
    x, y: 訓練資料
    x_grid: 要預測的點(排序後)
    span: 使用的鄰居比例 (0~1)
    """
    n = len(x)
    k = int(n * span)
    k = max(k, 2)  # 至少 2 點才能擬合線性
    y_hat = np.zeros(len(x_grid))

    for j, x0 in enumerate(x_grid):
        # Step 1: 找 k 個最近鄰居
        dist = np.abs(x - x0)
        neighbors = np.argpartition(dist, k)[:k]

        x_nbr = x[neighbors]
        y_nbr = y[neighbors]
        d_max = np.max(np.abs(x_nbr - x0))

        # Step 2: tri-cube 權重(課本常用核函數)
        u = np.abs(x_nbr - x0) / d_max
        u[u > 1] = 1
        w = (1 - u**3)**3  # tri-cube weight

        # Step 3: 加權最小平方法
        X_w = np.column_stack([np.ones(len(x_nbr)), x_nbr])
        W = np.diag(w)
        beta = np.linalg.lstsq(X_w.T @ W @ X_w, X_w.T @ W @ y_nbr, rcond=None)[0]

        # Step 4: 取 x0 的擬合值
        y_hat[j] = beta[0] + beta[1] * x0

    return y_hat

# 對 age 排序後的網格做預測
age_grid = np.linspace(age.min(), age.max(), 200)
y_fit_07 = local_linear(age, wage_vals, age_grid, span=0.7)
y_fit_02 = local_linear(age, wage_vals, age_grid, span=0.2)

# 繪圖
fig, axes = plt.subplots(1, 2, figsize=(13, 5))

for ax, y_fit, span, color in zip(
    axes, [y_fit_07, y_fit_02], [0.7, 0.2],
    ['#3fb950', '#d2991d']
):
    ax.scatter(age, wage_vals, s=6, alpha=0.3, color='#8b949e', label='data')
    ax.plot(age_grid, y_fit, color=color, linewidth=2.5, label=f'span={span}')
    ax.set_xlabel('Age', fontsize=11)
    ax.set_ylabel('Wage (k$)', fontsize=11)
    ax.set_title(f'Local Linear Regression · span = {span}', fontsize=12, color=color)
    ax.legend(fontsize=9)
    ax.set_facecolor('#0d1117')
    ax.tick_params(colors='#8b949e')
    for spine in ax.spines.values():
        spine.set_color('#30363d')

plt.tight_layout()
plt.savefig('/tmp/local_regression_wage.png', dpi=120, facecolor='#0d1117', bbox_inches='tight')
plt.show()

print(f"Span 0.7: {len(age)*0.7:.0f} 鄰居/點")
print(f"Span 0.2: {len(age)*0.2:.0f} 鄰居/點")

理論深入:局部迴歸與其他方法的連結

局部迴歸 vs 平滑樣條

面向 局部迴歸 (§7.6) 平滑樣條 (§7.5)
核心機制 對每個 \(x_0\) 近鄰做加權迴歸 每個點都是節點 + λ 懲罰控制粗糙度
參數控制 span \(s\)(鄰居比例) λ 平滑參數(懲罰力度)
自由度大小 \(s\) 越小 → 越靈活(高 df) λ 越小 → 越靈活(高 df)
計算成本 每個預測點需重新擬合,適合個別查詢 一次性全域最佳化,適合密集預測
CV 選參數 CV 選 \(s\) LOOCV 選 λ
高維度表現 p > 3~4 時急劇退化 可擴展為薄板樣條(thin-plate spline)
記憶需求 高(記憶式方法) 中等

局部迴歸 vs KNN 迴歸

兩者都是「看鄰居臉色」的非參數方法,但有本質差異:

打個比方:KNN 像「直接抄鄰居答案的平均」,局部迴歸像「觀察鄰居的變化規律,推測自己該落在趨勢線上的哪個點」。

應用場景

🏠 不動產估價

一棟房子的價值,最相關的不是全城均價,而是同社區最近成交的類似物件。局部迴歸自然地做到:對每棟估價物件動態選擇最接近的成交案例做加權回歸,距離越近的案例權重越高。span \(s\) 決定「社區」有多大——s=0.1 只看同一條街,s=0.5 擴及整個行政區。

📈 股市技術分析(LOESS 平滑)

金融分析師常用 LOESS(Locally Estimated Scatterplot Smoothing,局部迴歸的統計名稱)來平滑股價走勢。相比簡單移動平均(所有點等權重),局部迴歸對最近的交易日賦予更高權重,而且能自動適應局部波動的劇烈程度。

🧬 基因表達時間序列

基因表達數據通常是稀疏且雜訊大的時間序列。局部迴歸在每個時間點只參考附近的測量值來擬合趨勢,避免全域多項式對遠處不相關資料點的過度依賴。Varying coefficient models 更可以讓某些共變量的效應隨時間平滑變化。

優缺點對照

✅ 優點

⚠️ 缺點

推廣與限制

變係數模型(Varying Coefficient Models)

局部迴歸可以巧妙地推廣到多變量場景:讓某些變數全域線性但某些變數局部變動。例如時間序列模型中,可以讓截距項隨時間局部變化,但其他共變量的係數保持全域: \[ Y = \beta_0(t) + \beta_1 X_1 + \beta_2 X_2 + \cdots + \epsilon \] 其中 \(\beta_0(t)\) 是時間 \(t\) 的局部迴歸函數。這種設計在自適應學習率概念漂移(concept drift)場景中特別有用。

二維局部迴歸

局部迴歸可以自然地擴展到兩個變數 \(X_1, X_2\):用二維歐氏距離定義鄰域,對每個目標點 \((x_{10}, x_{20})\) 做雙變量加權迴歸。地理空間資料(經緯度 × 房價)是典型應用。

維度天花板:雖然理論上可以推到任意 p 維,但 p > 3 或 4 時,高維空間中「鄰近」的概念失去意義——幾乎所有點之間的距離都差不多。這就是維度詛咒(Curse of Dimensionality),也是局部迴歸和 KNN 共同的天敵。

自我內化:對 AI Agent 系統設計的啟發

局部迴歸的「近鄰權重衰減」機制,其實是 AI agent 系統中一個極具啟發性的設計模式:

Context Window 的時間局部性:就像局部迴歸對靠近 \(x_0\) 的點賦予高權重、遠點賦予低權重,AI agent 在處理長對話時也應該對近期訊息賦予更高的重要性。這不是一個新的想法——但局部迴歸提供了數學上嚴謹的加權框架:不應該硬截斷(等權滑動窗口),也不應該全域等權(耗盡 context window),而應該用隨距離衰減的核函數來加權歷史訊息。

實際應用場景:當 agent 需要從 50 輪歷史中決定哪些訊息對當前任務最相關時,可以用「輪次距離」做 tri-cube 加權,讓最近的上下文自然獲得更高的注意力,但又不完全丟棄較遠的資訊。

全域模型告訴你「平均而言世界長怎樣」,局部模型告訴你「你站的地方長怎樣」——在一個不平均的世界裡,後者往往更重要。 —— §7.6 局部迴歸 · 核心精神
← §7.5 平滑樣條 📑 課程首頁 §7.7 廣義可加模型 →