5.2 Bootstrap

📖 ISLP §5.2 📄 pp. 212–214 ★★★★☆ ⏱️ 約 25 分鐘
重抽樣 標準誤 Bootstrap 變異數估計 放回抽樣
← 5.1 交叉驗證 📑 課程首頁 5.3 Lab: CV & Bootstrap →

1. 什麼是 Bootstrap?

想像你是一間服飾店的店長,想知道這個月顧客平均消費金額的「誤差範圍」。傳統做法是重開 1,000 間分店,每間收集資料來算——但這不現實。Bootstrap 的答案是:從既有的一筆資料中,反覆「放回抽樣」,模擬出 1,000 個平行宇宙的資料,再用這些模擬資料來估計誤差範圍。

💡 直覺核心:Bootstrap 就像是拿著一個裝滿資料的袋子,隨手抓一把出來(放回、再抓),重複一千次。每次抓出來的樣本都不太一樣,這些差異就反映了估計量的變異程度
Bootstrap 是一種廣泛適用且極其強大的統計工具,用於量化某個估計量或統計學習方法的不確定性。它由 Bradley Efron 於 1979 年提出,被譽為 20 世紀最重要的統計發明之一。
— James, Witten, Hastie, Tibshirani (2023) ISLP, §5.2; Efron (1979) Annals of Statistics

1.1 為什麼需要 Bootstrap?

對線性迴歸係數來說,統計軟體(如 Rstatsmodels)會自動輸出標準誤——Bootstrap 在這裡沒有太大加分。但 Bootstrap 真正的威力在於:

📊 應用場景:金融投資組合風險評估

你想計算一個複雜投資組合的最大可能損失(VaR)。這個 VaR 的標準誤無法用簡單公式算出來——Bootstrap 就是完美解答:從歷史報酬中重複抽樣,模擬 10,000 種市場情境,直接從情境分佈計算 VaR 的信賴區間。

2. 資產配置範例

課本用一個簡單的投資問題來展示 Bootstrap 的動機。假設你想把一筆錢投資到兩種資產 XY,回報率是隨機變數。你將資金中的比例 \( \alpha \) 投入 X,其餘 \( 1 - \alpha \) 投入 Y。

最佳策略是:選擇 \( \alpha \) 讓整體投資的變異數(風險)最小。也就是最小化:

\[ \operatorname{Var}(\alpha X + (1-\alpha)Y) \quad \rightarrow \quad \min_{\alpha} \]

經過微積分推導,最優解是:

\[ \alpha = \frac{\sigma_Y^2 - \sigma_{XY}}{\sigma_X^2 + \sigma_Y^2 - 2\sigma_{XY}} \qquad \text{(5.6)} \]
其中 \(\sigma_X^2 = \operatorname{Var}(X)\), \(\sigma_Y^2 = \operatorname{Var}(Y)\), \(\sigma_{XY} = \operatorname{Cov}(X, Y)\)

現實中 \( \sigma_X^2, \sigma_Y^2, \sigma_{XY} \) 都是未知的,只能用歷史資料估計:

\[ \hat\alpha = \frac{\hat\sigma_Y^2 - \hat\sigma_{XY}}{\hat\sigma_X^2 + \hat\sigma_Y^2 - 2\hat\sigma_{XY}} \qquad \text{(5.7)} \]
🔍 關鍵問題:有了 \(\hat\alpha\) 還不夠——我們需要知道 \(\hat\alpha\) 有多準確?它的標準誤是多少?這正是 Bootstrap 要解決的問題。

2.1 傳統做法:重複模擬(理想但不可行)

課本先用 1,000 次真實模擬 來展示「標準答案」:從已知母體(設 \(\sigma_X^2=1, \sigma_Y^2=1.25, \sigma_{XY}=0.5\))重複抽樣 100 對 (X, Y),每次計算 \(\hat\alpha\)——共得 1,000 個 \(\hat\alpha_1, \hat\alpha_2, \ldots, \hat\alpha_{1000}\)。

模擬結果(課本 Figure 5.10 左圖):

模擬結果摘錄自 ISLP Figure 5.10(數值為近似)

但現實中你只有一組資料,無法回到母體重抽——這就是 Bootstrap 登場的時機。

3. Bootstrap 的核心機制

Bootstrap 的做法極其簡潔,像一個「資料重複利用術」:

🔄 Bootstrap 演算法(一句話版):
從原始資料中放回抽樣(with replacement)n 筆,形成一個「偽樣本」;對偽樣本計算估計量;重複 B 次,用這 B 個估計值的標準差當作標準誤。

3.1 放回抽樣(Sampling with Replacement)

「放回抽樣」是什麼意思?想像原始資料是三張卡片:🟦 🟩 🟥。你要抽三張出來(n 不變),但每次抽完會把卡片放回去:

原始資料 Z = {🟦, 🟩, 🟥}
↓ 放回抽樣 ↓
Z*₁ = {🟥, 🟥, 🟦} ← 🟥重複了!🟩沒被抽到
Z*₂ = {🟦, 🟦, 🟦} ← 全部一樣也可以
Z*₃ = {🟩, 🟥, 🟦} ← 剛好跟原始一樣(機率很小)

每個 Bootstrap 樣本都是一個「平行宇宙」——資料結構類似,但具體樣本不同。對於每個 Z*ᵦ,計算 \(\hat\alpha^*_{\beta}\)。B 次之後,我們就有 B 個估計值。

3.2 Bootstrap 標準誤公式

\[ \text{SE}_B(\hat\alpha) = \sqrt{\frac{1}{B - 1} \sum_{r=1}^{B} \left( \hat\alpha^*_r - \frac{1}{B}\sum_{r'=1}^{B} \hat\alpha^*_{r'} \right)^2} \qquad \text{(5.8)} \]
Bootstrap 標準誤估計

換句話說:就是 B 個 Bootstrap 估計值的樣本標準差。課本中,從單一資料集做出的 Bootstrap SE 是 0.087——與用真實母體重複模擬得到的 0.083 非常接近!

課本 Figure 5.10 三部曲:

ISLP Figure 5.10 —— Bootstrap 模擬的分佈與真實抽樣分佈幾乎無法區分,證明 Bootstrap 有效。

4. Python 實作:Bootstrap 估計標準誤

# %% [markdown]
# ## §5.2 The Bootstrap — Python Demo
# 展示 Bootstrap 如何從單一資料集估計 \(\hat\alpha\) 的標準誤

# %% [markdown]
# ### 0. 環境設定

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

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

np.random.seed(42)

# %% [markdown]
# ### 1. 模擬母體資料(已知真實參數)
# 設定:σ²_X = 1, σ²_Y = 1.25, σ_XY = 0.5 → 真實 α = 0.6

# %%
# 母體參數
sigma2_X, sigma2_Y, sigma_XY = 1.0, 1.25, 0.5
true_alpha = (sigma2_Y - sigma_XY) / (sigma2_X + sigma2_Y - 2 * sigma_XY)
print(f"真實 α = {true_alpha:.4f}")

# 從母體產生一組樣本(n=100)——這就是我們唯一擁有的「真實資料」
n = 100
cov_matrix = np.array([[sigma2_X, sigma_XY], [sigma_XY, sigma2_Y]])
pop_data = np.random.multivariate_normal([0, 0], cov_matrix, size=n)
X_data, Y_data = pop_data[:, 0], pop_data[:, 1]

# %% [markdown]
# ### 2. 定義 \(\hat\alpha\) 的計算函數

# %%
def estimate_alpha(x, y):
    """從 (X,Y) 樣本估計最優投資比例 α"""
    s2_x = np.var(x, ddof=1)
    s2_y = np.var(y, ddof=1)
    s_xy = np.cov(x, y, ddof=1)[0, 1]
    return (s2_y - s_xy) / (s2_x + s2_y - 2 * s_xy)

# 從我們的單一資料集計算 \(\hat\alpha\)
alpha_hat_orig = estimate_alpha(X_data, Y_data)
print(f"單一資料集的 α̂ = {alpha_hat_orig:.4f}")
print(f"與真實值的差距 = {alpha_hat_orig - true_alpha:.4f}")

# %% [markdown]
# ### 3. Bootstrap!從單一資料集重抽 B=1000 次

# %%
B = 1000
alpha_boot = np.zeros(B)

for b in range(B):
    # ponytail: naive loop ok for B=1000, n=100; for B>10k use np.random.choice on axis
    idx = np.random.choice(n, size=n, replace=True)  # 放回抽樣
    alpha_boot[b] = estimate_alpha(X_data[idx], Y_data[idx])

boot_mean = np.mean(alpha_boot)
boot_se = np.std(alpha_boot, ddof=1)
print(f"Bootstrap α̂ 平均 = {boot_mean:.4f}")
print(f"Bootstrap SE(α̂) = {boot_se:.4f}")

# %% [markdown]
# ### 4. 繪圖:Bootstrap 分佈 vs 真實 α

# %%
fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))

# 左圖:Bootstrap 分佈的直方圖
axes[0].hist(alpha_boot, bins=30, color='#58a6ff', edgecolor='#0d1117', alpha=0.8)
axes[0].axvline(true_alpha, color='#d2991d', linewidth=2.5, linestyle='-',
                label=f'真實 α = {true_alpha:.1f}')
axes[0].axvline(alpha_hat_orig, color='#3fb950', linewidth=2, linestyle='--',
                label=f'原始 α̂ = {alpha_hat_orig:.3f}')
axes[0].set_xlabel('α̂', fontsize=12)
axes[0].set_ylabel('次數', fontsize=12)
axes[0].set_title(f'Bootstrap 分佈 (B={B})', fontsize=13)
axes[0].legend(fontsize=10)
axes[0].set_facecolor('#161b22')
axes[0].tick_params(colors='#c9d1d9')

# 右圖:盒鬚圖
bp = axes[1].boxplot(alpha_boot, vert=True, widths=0.5,
                     patch_artist=True,
                     boxprops=dict(facecolor='#58a6ff', alpha=0.7),
                     medianprops=dict(color='white', linewidth=2))
axes[1].axhline(true_alpha, color='#d2991d', linewidth=2, linestyle='-',
                label=f'真實 α = {true_alpha:.1f}')
axes[1].set_ylabel('α̂', fontsize=12)
axes[1].set_title(f'Bootstrap 盒鬚圖 (SE ≈ {boot_se:.3f})', fontsize=13)
axes[1].legend(fontsize=10)
axes[1].set_facecolor('#161b22')
axes[1].tick_params(colors='#c9d1d9')

fig.patch.set_facecolor('#0d1117')
plt.tight_layout()
plt.savefig('/tmp/bootstrap_alpha_demo.png', dpi=100, facecolor='#0d1117')
plt.show()

# %% [markdown]
# ### 5. 不同 B 值對 Bootstrap SE 的影響

# %%
B_values = [10, 50, 100, 500, 1000, 5000]
se_values = []

for B_try in B_values:
    alphas = np.array([estimate_alpha(
        X_data[np.random.choice(n, size=n, replace=True)],
        Y_data[np.random.choice(n, size=n, replace=True)]
    ) for _ in range(B_try)])
    se_values.append(np.std(alphas, ddof=1))

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(B_values, se_values, 'o-', color='#58a6ff', linewidth=2, markersize=8)
ax.axhline(0.083, color='#d2991d', linestyle='--', linewidth=1.5,
           label='真實 SE ≈ 0.083(模擬)')
ax.set_xlabel('B (Bootstrap 重複次數)', fontsize=12)
ax.set_ylabel('Bootstrap SE', fontsize=12)
ax.set_title('B 值增加 → SE 估計趨於穩定', fontsize=13)
ax.legend(fontsize=10)
ax.set_xscale('log')
ax.set_facecolor('#161b22')
ax.tick_params(colors='#c9d1d9')
fig.patch.set_facecolor('#0d1117')
plt.tight_layout()
plt.savefig('/tmp/bootstrap_B_convergence.png', dpi=100, facecolor='#0d1117')
plt.show()

print("B=10  SE:", f"{se_values[0]:.4f}")
print("B=50  SE:", f"{se_values[1]:.4f}")
print("B=100 SE:", f"{se_values[2]:.4f}")
print("B=500 SE:", f"{se_values[3]:.4f}")
print("B=1000 SE:", f"{se_values[4]:.4f}")
print("B=5000 SE:", f"{se_values[5]:.4f}")

print("\n✅ Bootstrap 完成!SE ≈", f"{boot_se:.4f}(與真實 SE=0.083 非常接近)")

5. 方法比較

5.1 Bootstrap vs 交叉驗證 vs 傳統公式

方法 用途 假設需求 計算成本 適用對象
Bootstrap 估計標準誤、信賴區間 幾乎無(non-parametric) 中(需 B × 模型重擬合) 任何估計量
交叉驗證 估計測試誤差 資料 i.i.d. 中高(k × 模型重擬合) 預測模型
傳統公式 SE 估計標準誤 需推導出公式(如常態假設) 極低(一次性計算) 有閉合解的估計量

5.2 優缺點

✅ 優點

⚠️ 限制

6. 實務指南:B 要設多大?

用途 建議 B 說明
初步探索 50–200 快速得到 SE 的概略值,適合互動式分析
正式報告 1,000–5,000 SE 估計穩定,課本建議的標準範圍
信賴區間建構 5,000–10,000 需要分佈尾端(如 2.5% 和 97.5% 分位數)的穩定估計
高精度(如 p-value) 10,000+ 需要非常精確的尾端機率時
🎯 經驗法則:B=1,000 是絕大多數情況的好選擇。課本的 SE 公式是除以 B-1 而非 B——這是無偏估計的標準做法。

7. 更多應用場景

🏥 醫療:新藥效果的信心區間

臨床試驗中,治療效果的中位數(而非平均數)往往更重要(因為極端值影響)。沒有公式可以直接算中位數的標準誤——Bootstrap 是最可靠的解法。

📈 行銷:ROI 的信賴區間

A/B 測試的 ROI 提升是兩個隨機變數的比值。比值的標準誤沒有簡單公式,Bootstrap 可以直接從歷史轉換率分佈中模擬出 ROI 的信賴區間。

🤖 AI / ML:模型指標的變異性

F1-score、AUC-ROC 這類複雜指標的標準誤不容易公式化。Bootstrap 可以從測試集中放回抽樣,建立這些指標的信賴區間,判斷模型改進是否「真的」顯著。

8. 🧠 對 AI Agent 設計的啟發

Bootstrap 思維 → Agent 不確定性量化:正如 Bootstrap 用「放回抽樣」來估計估計量的變異性,多 Agent 系統也可以用 重複抽樣 + 多模投票 來量化輸出結果的不確定性。如果一個任務被 5 個子 agent 各自獨立處理(類似 Bootstrap 的 B 個偽樣本),答案的一致性就是「信賴區間」——結果分散則需要人工介入。

這個設計原則已經體現在 multi-model-code-review skill 中:4 個模型交叉驗證程式碼,≥2 模型共識才標記為通過。這就是 Bootstrap 精神的工程化應用

從單一資料中「模擬」出無數個平行宇宙的抽樣結果——這就是 Bootstrap 的魔法。它不需要回到母體、不需要複雜的機率推導、不需要特定的分佈假設。你只需要放回抽樣、重複計算,然後看這些計算結果有多分散。 — 改寫自 ISLP §5.2
← 5.1 交叉驗證 📑 課程首頁 5.3 Lab: CV & Bootstrap →