6.4 高維度資料的考量

📖 ISLP §6.4 📄 pp. 263–266 ★★★☆☆ ⏱️ 約 25 分鐘
高維度 p > n 維度詛咒 正則化 過度擬合 多重共線性 lasso
← 6.3 PCR 與 PLS 📑 課程首頁 6.5 Lab →(即將推出)

6.4.0 前言:特徵太多會怎樣?

想像你要從 3 個嫌疑犯中找出真兇,你有 500 條線索。聽起來很好對吧?但如果你仔細看——其中 490 條線索根本是路邊撿到的垃圾資訊,跟案件毫無關係。最可怕的是:只要線索夠多,你永遠能「完美解釋」已知的犯罪現場,但這個解釋拿去預測下一個案件時會完全失靈。這,就是高維度的陷阱。

在統計學習中,當特徵數量 \(p\) 接近或超過觀測數 \(n\)(即 \(p \ge n\) 或 \(p \approx n\)),我們就進入了高維度設定(high-dimensional setting)。這在現代資料科學中極其常見:基因表達資料(\(p \approx 20,000\),\(n \approx 100\))、文本分類(詞袋模型的 \(p\) 等於詞彙量)、甚至是簡單的 One-Hot 編碼都可能讓 \(p\) 輕鬆超越 \(n\)。

James, Witten, Hastie, Tibshirani (2023) An Introduction to Statistical Learning with Python, §6.4, pp. 263–266.
相關文獻:Hastie, Tibshirani & Friedman (2009) The Elements of Statistical Learning, §18(高維度問題的完整數學分析);Bühlmann & van de Geer (2011) Statistics for High-Dimensional Data

6.4.1 什麼是高維度資料?

課本明確定義:高維度就是 \(p > n\) 的情況。但實際上,即使 \(p\) 只是略小於 \(n\)(例如 \(p = 90\),\(n = 100\)),高維度的問題也已經開始浮現。以下是幾個典型的高維度場景:

應用場景典型 \(n\)典型 \(p\)\(p/n\) 比例
基因微陣列分析~100 位病患~20,000 基因200×
文本情感分類~1,000 篇評論~50,000 詞彙50×
金融高頻交易~500 天~2,000 技術指標
醫學影像 (radiomics)~200 位病患~1,500 特徵7.5×
One-Hot 編碼(多類別)~5,000 筆~10,000 虛擬變數
💡 關鍵認知:高維度不是「很多特徵」而已——是特徵數超過了樣本數。這打破了古典統計學的基本假設(\(n \gg p\)),讓最小平方法(OLS)直接失效。

6.4.2 高維度下會出什麼問題?

完美擬合的幻覺

課本的 Figure 6.22 用一個極簡例子說明問題:當只有 \(n = 2\) 個觀測值、要估計截距和斜率(\(p = 2\) 個參數),無論這兩個點在哪,回歸線都會完美穿過它們——殘差為零、\(R^2 = 1\)。但這條線拿去預測新資料時完全無用。

數學上,這是因為當 \(p \ge n\) 時,設計矩陣 \(\mathbf{X}\) 的秩不足,\(\mathbf{X}^T\mathbf{X}\) 不可逆,正規方程 \(\hat{\boldsymbol{\beta}} = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{y}\) 有無限多組解——其中任意一組都能讓 training error 歸零。

🎯 應用場景:金融詐欺檢測的陷阱

某 FinTech 公司有 50 筆歷史詐欺案例,卻收集了 200 個特徵(交易時間、IP 地理位置、裝置指紋、字體渲染特徵……)。用 OLS 訓練後,training AUC = 1.0——「完美模型!」上線三個月後發現:false positive rate 高達 40%,大量正常交易被誤攔。這就是高維度完美擬合的經典後果。

\(R^2\) 與 Training MSE 會騙人

課本 Figure 6.23 展示了一個震撼的模擬:\(n = 20\),逐步加入完全與反應變數無關的雜訊特徵。結果:

⚠️ 致命教訓:在高維度下,永遠不要只看 \(R^2\) 或 training error。它們只會說你想要的謊言。真正的審判官只有一個——獨立的測試集。

傳統模型選擇指標也失靈

更糟的是,\(C_p\)、AIC、BIC 這些我們在第 6.1 節學到的調整指標,在高維度下也會崩潰。原因是它們都依賴 \(\hat{\sigma}^2\) 的估計——而當 \(p \ge n\) 時,\(\hat{\sigma}^2 = 0\)(因為殘差平方和為 0)。Adjusted \(R^2\) 同樣可以輕鬆騙到 1.0。

6.4.3 高維度下的回歸:正則化是救星

好消息是:第 6 章前面介紹的方法——forward stepwise selection、ridge regression、lasso、PCR——恰好就是高維度回歸的解決方案。它們的共同策略是:用比 OLS 更不靈活(less flexible)的擬合方式來避免過度擬合。

課本 Figure 6.24 用 lasso 在不同 \(p\) 下的表現說明三個關鍵點:

📊 Figure 6.24 的三個啟示:
1. 正則化(regularization)在高維度問題中至關重要——不施加約束的模型會自爆
2. 調參(tuning parameter selection)決定預測好壞——\(\lambda\) 太大欠擬合、太小過擬合
3. 測試誤差隨維度增加而上升——除非新增的特徵真的與反應變數有關

維度詛咒(Curse of Dimensionality)

課本強調:增加特徵是一把雙面刃。訊號特徵(真正與 \(Y\) 有關的)能降低 test error;但雜訊特徵只會增加維度、加劇過度擬合風險。更關鍵的是——即使特徵真的有關聯,估計其係數所帶來的變異數膨脹,可能超過它帶來的偏差減少(bias-variance tradeoff 的維度版本)。

🧬 應用場景:基因表達與癌症預後

研究人員有 150 位乳癌病患的 20,000 個基因表達值。只有約 50 個基因真正與預後相關。若直接用全部基因建模型,test error 極高(雜訊基因淹沒訊號)。使用 lasso 後,自動選出約 30-80 個基因,test error 大幅下降。關鍵是——選出的基因並非「唯一正確答案」,不同訓練集可能選出不同基因組合(這就是 §6.4.4 要討論的詮釋問題)。

6.4.4 高維度下的結果詮釋:極度謹慎

課本在最後一節提出了一個深刻警告:在高維度下,即使 lasso 或 ridge 給出了係數估計,我們也無法確知哪些變數「真正」是預測因子

原因是極端的多重共線性(multicollinearity):當 \(p\) 很大時,模型中任何一個變數都可以用其他所有變數的線性組合來近似表示。這意味著:

🔬 這對 Hermes 系統設計的啟發:高維度下的「係數不可識別性」直接類比於多 agent 系統中的貢獻歸因問題。當多個子 agent 協作完成任務,我們無法確知哪個 agent「真正」貢獻了成功——因為它們的輸出行為高度相關(一個 agent 的結果依賴於前一個 agent 的輸出)。解決方案:像 lasso 一樣,對 agent 間的通訊施加稀疏性約束——不讓每個 agent 都看到所有資訊,只暴露與其任務相關的上下文子集。這既是正則化,也是可歸因性的前提。

📰 應用場景:新聞推薦系統的 A/B 測試困境

某新聞 App 有 500 個推薦特徵(閱讀歷史、搜尋關鍵字、地點、時間、裝置型號……),但只有 200 位活躍用戶參與 A/B 測試。Lasso 選出了 35 個「重要」特徵——但產品經理不能就此宣稱「這 35 個特徵驅動了點擊率」,因為可能有其他高度相關的特徵才是真正的因果因子。正確的態度是:「這些特徵與點擊率相關,但因果關係需要額外的實驗設計來驗證」。

程式碼示範:高維度的陷阱與救贖

示範 1:當 \(p \ge n\) 時 OLS 的完美擬合幻覺

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np

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

np.random.seed(42)

# 模擬:n=20 觀測值,逐步增加完全無關的雜訊特徵
n = 20
X_true = np.random.randn(n, 1)  # 只有 1 個真正訊號特徵
beta_true = np.array([3.0])
y = X_true @ beta_true + np.random.randn(n) * 0.5

# 加入雜訊特徵,觀察 R^2 和 MSE 的變化
p_noise_list = [0, 5, 10, 15, 20, 25]
r2_train, mse_train, mse_test = [], [], []

X_test = np.random.randn(200, 1)
y_test = X_test @ beta_true + np.random.randn(200) * 0.5

for p_noise in p_noise_list:
    X_noise = np.random.randn(n, p_noise)
    X = np.hstack([X_true, X_noise])
    X_test_full = np.hstack([X_test, np.random.randn(200, p_noise)])

    # OLS via pseudo-inverse (handles p >= n)
    beta_hat = np.linalg.lstsq(X, y, rcond=None)[0]
    y_pred_train = X @ beta_hat
    y_pred_test = X_test_full @ beta_hat

    ss_res = np.sum((y - y_pred_train)**2)
    ss_tot = np.sum((y - np.mean(y))**2)
    r2_train.append(1 - ss_res/ss_tot if ss_tot > 0 else 1.0)
    mse_train.append(np.mean((y - y_pred_train)**2))
    mse_test.append(np.mean((y_test - y_pred_test)**2))

# 繪圖
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
axes[0].plot(p_noise_list, r2_train, 'o-', color='#58a6ff', markersize=8)
axes[0].axhline(y=1, color='#3fb950', linestyle='--', label='R²=1 (完美)')
axes[0].set_xlabel('雜訊特徵數'); axes[0].set_ylabel('Training R²')
axes[0].set_title('R² 的謊言'); axes[0].legend()

axes[1].plot(p_noise_list, mse_train, 'o-', color='#d2991d', markersize=8)
axes[1].set_xlabel('雜訊特徵數'); axes[1].set_ylabel('Training MSE')
axes[1].set_title('Training MSE → 0')

axes[2].plot(p_noise_list, mse_test, 's-', color='#f85149', markersize=8)
axes[2].set_xlabel('雜訊特徵數'); axes[2].set_ylabel('Test MSE')
axes[2].set_title('Test MSE 暴增!')

plt.tight_layout()
plt.savefig(DATA_PATH + 'highdim_demo.png', dpi=100, bbox_inches='tight')
plt.show()
print(f'p=25 時 Training R²={r2_train[-1]:.3f}, Test MSE={mse_test[-1]:.1f}')

示範 2:Lasso 在高維度下的救贖

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import Lasso, LassoCV
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

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

np.random.seed(42)

# 高維度模擬:n=80, p=200,只有 10 個訊號特徵
n, p = 80, 200
n_signal = 10
X = np.random.randn(n, p)
beta_true = np.zeros(p)
beta_true[:n_signal] = np.random.choice([2, -2, 1.5, -1.5], n_signal)
y = X @ beta_true + np.random.randn(n) * 0.8

# 標準化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 分割
X_train, X_test, y_train, y_test = train_test_split(
    X_scaled, y, test_size=0.3, random_state=42
)

# OLS (pseudo-inverse) — 會過度擬合
beta_ols = np.linalg.lstsq(X_train, y_train, rcond=None)[0]
ols_train_mse = np.mean((y_train - X_train @ beta_ols)**2)
ols_test_mse = np.mean((y_test - X_test @ beta_ols)**2)

# Lasso with CV
lasso_cv = LassoCV(cv=5, random_state=42, max_iter=10000,
                    alphas=np.logspace(-3, 1, 30))
lasso_cv.fit(X_train, y_train)
lasso_train_mse = np.mean((y_train - lasso_cv.predict(X_train))**2)
lasso_test_mse = np.mean((y_test - lasso_cv.predict(X_test))**2)
n_nonzero = np.sum(lasso_cv.coef_ != 0)

print(f'OLS:  Train MSE={ols_train_mse:.4f}, Test MSE={ols_test_mse:.2f}')
print(f'Lasso: Train MSE={lasso_train_mse:.4f}, Test MSE={lasso_test_mse:.2f}')
print(f'Lasso best alpha={lasso_cv.alpha_:.3f}, non-zero coefs={n_nonzero}')
print(f'True signal features: {n_signal}')

# 繪圖:比較係數
fig, axes = plt.subplots(1, 2, figsize=(12, 4))

axes[0].stem(range(p), beta_ols, linefmt='#58a6ff', markerfmt='o', basefmt='k-')
axes[0].axhline(y=0, color='#30363d', linestyle='-')
axes[0].set_title('OLS 係數(過度擬合)')
axes[0].set_xlabel('特徵 index'); axes[0].set_ylabel('Coefficient')

axes[1].stem(range(p), lasso_cv.coef_, linefmt='#3fb950', markerfmt='o', basefmt='k-')
axes[1].axhline(y=0, color='#30363d', linestyle='-')
for i in range(n_signal):
    axes[1].axvline(x=i, color='#d2991d', linestyle='--', alpha=0.5, linewidth=0.8)
axes[1].set_title(f'Lasso 係數 (α={lasso_cv.alpha_:.3f}, {n_nonzero} non-zero)')
axes[1].set_xlabel('特徵 index'); axes[1].set_ylabel('Coefficient')

plt.tight_layout()
plt.savefig(DATA_PATH + 'highdim_lasso.png', dpi=100, bbox_inches='tight')
plt.show()

優缺點對照:高維度下的應對策略

✅ 正則化方法的優勢

⚠️ 注意事項與限制

方法比較:高維度回歸策略一覽

方法\(p > n\) 可用?特徵選擇係數收縮計算複雜度適合情境
OLS(最小平方法)❌ 不可\(O(p^3)\)僅限 \(n \gg p\)
Forward Stepwise⚠️ 有限✅ 有中等\(p\) 不太大時可嘗試
Ridge Regression✅ 可❌ 全部保留✅ 均勻收縮\(O(p^3)\)所有特徵都有微弱訊號
Lasso✅ 可✅ 稀疏選擇✅ 軟閾值中等稀疏訊號(少數特徵重要)
PCR(主成分回歸)✅ 可❌ 線性組合間接(截斷)\(O(p^3)\)特徵高度相關
PLS(偏最小平方)✅ 可❌ 監督組合間接(截斷)中等\(Y\) 與特徵方向相關

本章概念地圖

第 6 章從子集選擇(§6.1)出發,經過收縮方法(§6.2:Ridge + Lasso),到維度縮減(§6.3:PCR + PLS),最後抵達高維度(§6.4)——每節都在回答同一個問題的子問題:

節次核心問題核心方法關鍵取捨
§6.1 子集選擇選哪些變數?Best subset, Forward, Backward搜尋廣度 vs 計算成本
§6.2 收縮方法如何懲罰複雜度?Ridge (L2), Lasso (L1)偏差 vs 變異數
§6.3 維度縮減如何壓縮變數?PCR (無監督), PLS (監督)資訊保留 vs 預測相關性
§6.4 高維度\(p \ge n\) 時怎麼辦?上述方法的極限測試模型複雜度 vs 可詮釋性
在資料比問題還少的時代,謙虛是最好的模型——承認你不知道哪些變數重要,比假裝知道更能做出好預測。 — 改寫自 ISLP §6.4.4 的核心訊息