3.5 線性迴歸 vs K-最近鄰

📖 ISLP §3.5 📄 pp. 111–115 ⭐⭐⭐ 中級 ⏱️ 約 20 分鐘
線性迴歸 KNN 參數化 vs 非參數化 維度詛咒 Bias-Variance 模型選擇
← 3.4 行銷計畫 📑 課程首頁 3.6 Lab →

📚 理論基礎

James, G., Witten, D., Hastie, T., & Tibshirani, R. (2023). An Introduction to Statistical Learning with Applications in Python. Springer. §3.5, pp. 111–115.

在前幾節,我們學了線性迴歸——一種參數化方法。它假設 f(X) 有特定的函數形式(線性),然後用資料估計參數。但這不是唯一的選擇。K-最近鄰(KNN)是一種非參數化方法:不做任何函數形式假設,直接從資料本身做預測。

🎯 本節核心問題:參數化(線性迴歸)與非參數化(KNN)方法各有利弊。何時該選哪個?答案是:取決於資料的真實關係和維度。理解這個取捨,是統計學習的關鍵分水嶺。

參數化 vs 非參數化:兩種世界觀

特性參數化(線性迴歸)非參數化(KNN) 函數形式假設明確假設線性 f(X) = β₀ + β₁X無假設,讓資料說話 參數數量固定(p+1 個)隨資料增長(本質上無限制) 估計方法OLS 最小平方法鄰近點的平均 擬合速度快(閉式解)慢(每次預測需掃描全部資料) 預測速度極快(只算內積)慢(需找最近鄰) 解釋性高(每個 βⱼ 有明確意義)低(黑箱,無係數可解讀) 對離群值的敏感度高(離群值會拉動整條線)取決於 K(K=1 極敏感)

KNN 迴歸:鄰居決定你的價值

KNN 迴歸的核心思想很直覺:想知道一間房子的價格?看看附近最近賣掉的 K 間類似房子,取它們的平均價。不需要假設房間數和價格之間是線性還是曲線關係——直接問鄰居。

\[ \hat{f}(x_0) = \frac{1}{K} \sum_{x_i \in \mathcal{N}_0} y_i \]
公式 3.16 — KNN 迴歸:x₀ 的預測值是它 K 個最近鄰的 y 值平均

其中 \(\mathcal{N}_0\) 是離 \(x_0\) 最近的 K 個訓練觀測值的集合。K 是我們必須選擇的超參數——沒有公式能自動算出最佳 K。

K 值如何影響擬合?

想像你在畫一條穿過資料點的曲線:

🔑 Bias-Variance Tradeoff 再現:這正是我們在 §2.2 學到的取捨。K 越小 → 模型越靈活 → Variance 高、Bias 低。K 越大 → 模型越平滑 → Bias 高、Variance 低。最佳 K 在哪?交叉驗證告訴你。

線性迴歸 vs KNN:何時誰贏?

這不是一場宗教戰爭。兩種方法在不同情境下各有勝場:

情境線性迴歸KNN贏家 真實關係是線性的完美捕捉結構,低變異浪費自由度去擬合隨機噪音✅ 迴歸 真實關係是高度非線性的偏誤極大(強行用直線擬合曲線)能捕捉任意形狀(圖 3.19 下方)✅ KNN 少數預測變數(p 小)穩定、解釋性強表現不錯,鄰居仍有意義🟰 視需求而定 多數預測變數(p 大)表現穩定(參數固定)表現急遽惡化(圖 3.20)✅ 迴歸 需要解釋係數每個 β 有明確意義無係數可解釋✅ 迴歸 預測速度要求高O(p) 極快O(np) 每次都要掃描✅ 迴歸

維度詛咒:KNN 的致命弱點

這是 KNN 最殘酷的限制。想像你在一個大賣場找朋友:

在統計上,這意味著:當維度增加時,你需要指數級增長的資料,才能維持相同密度的「鄰居」。在圖 3.20 中,當 p=1 時 KNN 表現優於線性迴歸(對非線性資料),但 p 增加到 20 時,即使真實關係是非線性的,線性迴歸也開始勝出。

\[ \text{所需資料量} \propto C^p \]
維度詛咒的數學表述:要維持相同「鄰居密度」,資料量需隨 p 呈指數成長

Python 實作:用程式感受差異

try:
    from google.colab import drive
    drive.mount('/content/drive')
    DATA_PATH = '/content/drive/MyDrive/ISLP_data/'
except ImportError:
    DATA_PATH = '/tmp/'

import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsRegressor
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# ── 建立模擬資料:非線性關係 ──
np.random.seed(42)
n = 200
X = np.random.uniform(-3, 3, n).reshape(-1, 1)
y = X.ravel()**3 - 3*X.ravel() + np.random.normal(0, 2, n)  # 三次函數 + 噪音

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)

# ── 線性迴歸 ──
lr = LinearRegression().fit(X_train, y_train)
y_pred_lr = lr.predict(X_test)
mse_lr = mean_squared_error(y_test, y_pred_lr)

# ── KNN 迴歸:測試不同 K 值 ──
results = []
for K in [1, 3, 9, 25, 50]:
    knn = KNeighborsRegressor(n_neighbors=K).fit(X_train, y_train)
    y_pred_knn = knn.predict(X_test)
    mse_knn = mean_squared_error(y_test, y_pred_knn)
    results.append({'K': K, 'MSE': mse_knn})

print("=== 測試集 MSE 比較 ===")
print(f"線性迴歸 MSE: {mse_lr:.4f}")
for r in results:
    print(f"KNN (K={r['K']:2d}) MSE: {r['MSE']:.4f}")

# ── 視覺化:不同 K 值的擬合曲線 ──
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
X_plot = np.linspace(-3, 3, 300).reshape(-1, 1)
y_plot_lr = lr.predict(X_plot)

Ks = [1, 9, 50]
titles = ['K=1 (過度擬合)', 'K=9 (適中)', 'K=50 (過度平滑)']
for ax, K, title in zip(axes, Ks, titles):
    knn = KNeighborsRegressor(n_neighbors=K).fit(X_train, y_train)
    y_plot_knn = knn.predict(X_plot)
    
    ax.scatter(X_train, y_train, alpha=0.3, s=10, label='訓練資料')
    ax.plot(X_plot, y_plot_lr, 'r-', linewidth=2, label='線性迴歸')
    ax.plot(X_plot, y_plot_knn, 'g-', linewidth=2, label=f'KNN(K={K})')
    ax.set_title(title)
    ax.set_xlabel('X')
    ax.set_ylabel('y')
    ax.legend(fontsize=8)

plt.tight_layout()
plt.savefig('/tmp/knn_vs_lr.png', dpi=100, bbox_inches='tight')
plt.show()
print("\n→ K=1 完美記住訓練資料但泛化差,K=50 太平滑漏掉結構")

# ── 維度詛咒實證 ──
print("\n=== 維度詛咒模擬 ===")
for p in [1, 2, 5, 10, 20]:
    np.random.seed(42)
    n_train = 100
    Xp = np.random.normal(0, 1, (n_train, p))
    # 真實關係:只有第一個變數有非線性效果
    yp = Xp[:, 0]**3 - 3*Xp[:, 0] + np.random.normal(0, 2, n_train)
    
    Xp_test = np.random.normal(0, 1, (500, p))
    yp_test = Xp_test[:, 0]**3 - 3*Xp_test[:, 0] + np.random.normal(0, 2, 500)
    
    knn_p = KNeighborsRegressor(n_neighbors=5).fit(Xp, yp)
    lr_p = LinearRegression().fit(Xp, yp)
    
    mse_knn = mean_squared_error(yp_test, knn_p.predict(Xp_test))
    mse_lr = mean_squared_error(yp_test, lr_p.predict(Xp_test))
    
    winner = "KNN" if mse_knn < mse_lr else "LR "
    print(f"p={p:2d} | LR MSE: {mse_lr:.4f} | KNN MSE: {mse_knn:.4f} | → {winner}")

應用場景

🏠 場景:房價預測

你想估一間房子的市場價。兩種做法:

🩺 場景:醫療診斷輔助

根據病患的多項指標預測糖尿病風險:

優缺點對照

✅ 線性迴歸的優點

✅ KNN 的優點

方法比較總表

維度線性迴歸KNN 迴歸 類型參數化非參數化 假設f(X) 線性無 超參數無(或正則化參數 λ)K(鄰居數) 預測公式ŷ = Xβ̂(閉式解)ŷ = 鄰居 y 值的平均 Bias高(若真實關係非線性)低(可擬合任意形狀) Variance低取決於 K(K↓→Var↑) 維度詛咒不受影響嚴重(p↑→表現↓) 解釋性高低 適用情境p 大、需解釋、線性關係p 小、非線性、不需解釋
參數化方法把偏誤(bias)當作交換籌碼,換取較低的變異(variance)和更好的解釋性;非參數化方法則放棄了函數形式的假設,代價是在維度升高時付出沉重代價。選擇哪一個,取決於你的資料維度、對解釋性的需求,以及真實關係的複雜度。 — ISLP §3.5 核心精神

🧠 自我內化:對 Hermes 架構的啟發

參數化 vs 非參數化 → Agent 策略選擇:

線性迴歸的「固定參數+強假設」策略對應於基於規則的 agent(明確的策略腳本);KNN 的「無假設+資料驅動」對應於完全自主的 LLM agent(每次推理都是從零開始)。

課程啟發:最佳實踐是混合——像彈性網(Elastic Net)結合 L1/L2 正則化一樣,Hermes 也應該在結構化工作流(skill 系統、固定管線)和自主推理(LLM 自由發揮)之間找到平衡。這正是我們目前的架構:cron job 提供固定結構(=參數化),delegate_task 提供靈活應變(=非參數化)。

← 3.4 行銷計畫 📑 課程首頁 3.6 Lab →