想像你在一個陌生的城市,想知道附近一間餐廳好不好吃。你不會去問整個城市的人——你只會問住在餐廳附近的人,而且鄰居的意見比遠方路人的意見更值得參考。這就是局部迴歸(Local Regression)的核心精神:在做預測時,只參考目標點附近的資料,離得越近的資料點權重越大。
課本對局部迴歸給出了清晰的操作步驟。以下是演算法 7.1 的完整描述:
選定比例 \(s = k/n\),找出訓練集中最接近 \(x_0\) 的 \(k\) 個點。只有這 \(k\) 個點會參與計算,其餘的 \(n-k\) 個點權重為零。
對每個鄰居 \(x_i\) 賦予權重 \(K_{i0} = K(x_i, x_0)\):離 \(x_0\) 越近的點權重越高,最遠的鄰居權重為零。這形成一個鐘形權重分布(如課本 Figure 7.9 中的黃色鐘形疊加層)。
用步驟 2 的權重 \(K_{i0}\) 對鄰居做加權線性迴歸,最小化:
在 \(x_0\) 處的預測值就是加權迴歸在 \(x_0\) 的擬合值: \[ \hat{f}(x_0) = \hat{\beta}_0 + \hat{\beta}_1 x_0 \]
局部迴歸有三個設計選擇:權重函數 \(K\) 的形式、迴歸階數(常數 / 線性 / 二次),以及 span \(s\)——用來計算每個 \(x_0\) 擬合值的鄰居比例。課本強調:span 是三者中最關鍵的參數。
把局部迴歸想像成用望遠鏡觀察一座山的地形:
課本 Figure 7.10 展示了用不同 span 值對 ISLP 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} 鄰居/點")
| 面向 | 局部迴歸 (§7.6) | 平滑樣條 (§7.5) |
|---|---|---|
| 核心機制 | 對每個 \(x_0\) 近鄰做加權迴歸 | 每個點都是節點 + λ 懲罰控制粗糙度 |
| 參數控制 | span \(s\)(鄰居比例) | λ 平滑參數(懲罰力度) |
| 自由度大小 | \(s\) 越小 → 越靈活(高 df) | λ 越小 → 越靈活(高 df) |
| 計算成本 | 每個預測點需重新擬合,適合個別查詢 | 一次性全域最佳化,適合密集預測 |
| CV 選參數 | CV 選 \(s\) | LOOCV 選 λ |
| 高維度表現 | p > 3~4 時急劇退化 | 可擴展為薄板樣條(thin-plate spline) |
| 記憶需求 | 高(記憶式方法) | 中等 |
兩者都是「看鄰居臉色」的非參數方法,但有本質差異:
打個比方:KNN 像「直接抄鄰居答案的平均」,局部迴歸像「觀察鄰居的變化規律,推測自己該落在趨勢線上的哪個點」。
一棟房子的價值,最相關的不是全城均價,而是同社區最近成交的類似物件。局部迴歸自然地做到:對每棟估價物件動態選擇最接近的成交案例做加權回歸,距離越近的案例權重越高。span \(s\) 決定「社區」有多大——s=0.1 只看同一條街,s=0.5 擴及整個行政區。
金融分析師常用 LOESS(Locally Estimated Scatterplot Smoothing,局部迴歸的統計名稱)來平滑股價走勢。相比簡單移動平均(所有點等權重),局部迴歸對最近的交易日賦予更高權重,而且能自動適應局部波動的劇烈程度。
基因表達數據通常是稀疏且雜訊大的時間序列。局部迴歸在每個時間點只參考附近的測量值來擬合趨勢,避免全域多項式對遠處不相關資料點的過度依賴。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})\) 做雙變量加權迴歸。地理空間資料(經緯度 × 房價)是典型應用。
局部迴歸的「近鄰權重衰減」機制,其實是 AI agent 系統中一個極具啟發性的設計模式:
實際應用場景:當 agent 需要從 50 輪歷史中決定哪些訊息對當前任務最相關時,可以用「輪次距離」做 tri-cube 加權,讓最近的上下文自然獲得更高的注意力,但又不完全丟棄較遠的資訊。