神經網路:把非線性一層一層疊起來

ISLP 第 10 章 — 本課未授課,補充自學
隱藏層|ReLU|softmax|卷積與池化|RNN|反向傳播+SGD|dropout|雙下降
向下捲動開始互動↓
📌 本頁使用方式(ISLP Ch.10 · 補充)

① 依序閱讀:每節先讀說明;遇到互動元件時,先預測結果,再操作驗證。 ② 對照課本:章節旁標示課本節號,正文保留核心公式與短例,細節與證明預設收合;可展開推導,並對照原書。 ③ 檢查理解:答錯時先看回饋,再回到相關說明;標為延伸的內容可留待第二輪。 ④ 最後翻關鍵詞彙卡自測術語,並用 重點速查與來源查閱。

教科書:An Introduction to Statistical Learning with Applications in Python(ISLP);
進階參考:The Elements of Statistical Learning(ESL)。章節旁的來源標記可點到本頁書目。

CONTENTS · 內容目錄
PROLOGUE · 開場

從線性模型理解神經網路的隱藏層 教科書 · 第 10 章開頭

本課沒有教第 10 章。它不在考試範圍, 也沒有對應的講義與 lab。放上來是因為前面九章把「線性模型 → 加彈性 → 加正則化 → 換基底 → 集成」這條線走完之後,深度學習正好是同一條線的下一步,可以銜接前面的內容。

ISLP 從模型形式介紹神經網路:把式子攤開來看,它就是 線性模型套一層非線性再線性組合一次。單層網路的預測函數長這樣:

$$f(X) = \beta_0 + \sum_{k=1}^{K} \beta_k \, g\!\left(w_{k0} + \sum_{j=1}^{p} w_{kj} X_j\right) \tag{10.1}$$

把 g 換成恆等函數,整條式子會塌回線性迴歸:兩層線性組合疊起來還是線性組合。 其中 g 提供非線性,其餘部分沿用線性組合。

這一章的六個主題 1. 單層網路(§10.1):一層隱藏層,非線性從哪裡進來。
2. 多層與 MNIST(§10.2):疊第二層、softmax 輸出、參數量快速增加。
3. 卷積網路(§10.3):權重共享與池化,把影像的結構寫進架構裡。
4. 文件分類與 RNN(§10.4–10.5):順序有意義的時候,詞袋不夠用。
5. 怎麼擬合(§10.7):反向傳播、SGD、dropout。
6. 雙下降(§10.8):參數比樣本還多,測試誤差為什麼還會再降一次。

還有一個貫串全章的問題,§10.6 專門處理:什麼時候該用深度學習、什麼時候不該。 ISLP 的立場很明確,Hitters 那個例子就是拿來說服你的——神經網路跟線性迴歸打平, 而線性模型看得懂、講得清。他們搬出奧坎剃刀:表現差不多的時候,挑簡單的那個。

線性迴歸神經網路
參數怎麼來閉式解,一次算完梯度下降,要迭代、要調
有幾個要調的東西幾乎沒有層數、寬度、學習率、批次大小、dropout、epoch 數…
係數能解讀嗎能,還有標準誤與 p 值不能,是黑盒子
什麼時候贏n 不大、訊號接近線性、要解釋n 很大、訊噪比高、輸入有空間或時間結構
QUIZ · 非線性從哪裡來

式 10.1 裡如果把 g(z) 換成恆等函數 g(z) = z,這個「神經網路」會變成什麼?

(A) 還是非線性的,因為有 K 個隱藏單元在加總
(B) 退化成 X₁, …, X_p 的線性模型,隱藏層完全白費
(C) 會變成 K 次多項式迴歸,因為有 K 個隱藏單元
PART 01 · 單層神經網路

一層隱藏層:非線性怎麼進來的 教科書 · 第 10.1 節進階參考 · 第 11.3 節

單層網路的結構只有三步:輸入做 K 組線性組合、每一組過同一個 g、 再把 K 個結果線性組合成輸出。中間那 K 個值 ISLP 叫 activation, 寫成 A_k:

$$A_k = h_k(X) = g\!\left(w_{k0} + \sum_{j=1}^{p} w_{kj} X_j\right), \qquad f(X) = \beta_0 + \sum_{k=1}^{K} \beta_k A_k \tag{10.2}$$

要估的參數是所有 w 與所有 β,總共 K(p+1) + (K+1) 個。 注意 w_{k0} 與 β₀ 這兩個截距在機器學習圈叫 bias—— ISLP 在註腳特別提醒,那跟偏差–變異取捨的「偏差」是兩回事,只是撞名。

g 的兩個常見選擇 Sigmoid:g(z) = eᶻ/(1+eᶻ),把任何實數壓進 (0, 1)。 就是邏輯斯迴歸那個函數。缺點是 |z| 一大曲線就平掉,導數趨近 0,梯度傳不回去。
ReLU:g(z) = max(0, z)。負的歸零、正的原樣過。導數取 0 或 1, 正值區的導數為 1,可減少飽和造成的梯度衰減;負值區的導數為 0,仍可能出現不再更新的單元。z=0 處不可微,實作會約定使用一個導數值。
›拖左邊四個輸入節點上下移動,看隱藏單元與輸出怎麼跟著動。
w₁₀0.0
模擬示意
怎麼玩
直接拖左邊四個輸入節點上下移動就能改 X₁–X₄。每個隱藏單元的圓圈大小代表 A_k 的值,連線粗細代表 |權重|、紅藍代表正負。權重是固定的(隨機給定並固定種子),這裡要看的是「同一組權重下,輸入怎麼變成輸出」。
目前的狀態
激發函數 gReLU
A₁ = g(w₁₀ + Σ w₁ⱼXⱼ)—
A₂—
A₃—
輸出 f(X)—
有幾個參數要估—
死掉的單元
ReLU 有一個代價:只要 w₁₀ + Σ w₁ⱼXⱼ 對所有訓練樣本都是負的,那個單元永遠輸出 0、導數也永遠是 0,就再也學不動了。把 w₁₀ 拉到很負就能看到 A₁ 變成灰色的 0。
觀念釐清
Q:隱藏單元的個數 K 該怎麼選?

沒有公式,它是超參數,跟第 6 章的 λ、第 8 章的樹深度同一個地位,用交叉驗證或驗證集選。

實務上也常先把 K 開大,再靠正則化(dropout、權重衰減、早停)把彈性收回來,這種做法的理由見 §10.8:過度參數化的網路配上適當的訓練與正則化,有時能比參數較少的網路表現更好;SGD 並不普遍保證最平滑或最小範數解。

Q:為什麼要標準化輸入?

跟 SVM 與嶺迴歸的理由一樣:所有輸入共用同一個學習率與同一個權重衰減,尺度差很多時,數值大的那個變數會主宰梯度,小的那個幾乎不動。

官方 lab 的 Hitters 例子在 lasso 那條路上用 StandardScaler 包進 Pipeline,神經網路那條路則是先把整個 X 標準化再切訓練測試。要注意的是:標準化的統計量只能從訓練資料算,否則就是第 5 章講過的資料洩漏。

完整實作:Hitters 的基準、網路與結果比較

官方 lab §10.9.1:先把線性基準立起來

ISLP 的示範順序很值得學:先跑線性迴歸與 lasso,再跑神經網路。 沒有基準線的話,你不會知道那個神經網路到底是好是壞。資料是 Hitters, n = 263、p = 19,用三分之一當測試集。

lab §10.9.1 · 資料準備與線性迴歸基準
model = MS(Hitters.columns.drop('Salary'), intercept=False) X = model.fit_transform(Hitters).to_numpy() Y = Hitters['Salary'].to_numpy() (X_train, X_test, Y_train, Y_test) = train_test_split(X, Y, test_size=1/3, random_state=1) hit_lm = LinearRegression().fit(X_train, Y_train) Yhat_test = hit_lm.predict(X_test) np.abs(Yhat_test - Y_test).mean()
預期輸出
np.float64(259.7152883314627)

測試集上的平均絕對誤差是 259.72。這是最陽春的基準,後面每個模型都要跟它比。

來源:Ch10-deeplearning-lab.ipynb
lab §10.9.1 · lasso 基準(10 折 CV 選 λ)
X_s = scaler.fit_transform(X_train) n = X_s.shape[0] lam_max = np.fabs(X_s.T.dot(Y_train - Y_train.mean())).max() / n param_grid = {'lasso__alpha': np.exp(np.linspace(0, np.log(0.01), 100)) * lam_max} cv = KFold(10, shuffle=True, random_state=1) grid = GridSearchCV(standard_lasso, param_grid, cv=cv, scoring='neg_mean_absolute_error') grid.fit(X_train, Y_train); trained_lasso = grid.best_estimator_ Yhat_test = trained_lasso.predict(X_test) np.fabs(Yhat_test - Y_test).mean()
預期輸出
np.float64(235.6754837478029)

lasso 把 MAE 壓到 235.68,比最小平方好一截。注意 lam_max 那一行:它算出「剛好把所有係數壓成 0」的 λ,格點就從那裡往下取,這樣不必憑空猜範圍。

來源:Ch10-deeplearning-lab.ipynb

官方 lab §10.9.1:用 torch 寫一個單層網路

PyTorch 的寫法是繼承 nn.Module,在 __init__ 裡宣告要用的層、 在 forward 裡寫資料怎麼流過去。這個網路是 19 → 50(ReLU、dropout 0.4)→ 1。

lab §10.9.1 · HittersModel 與參數量
class HittersModel(nn.Module): def __init__(self, input_size): super(HittersModel, self).__init__() self.flatten = nn.Flatten() self.sequential = nn.Sequential( nn.Linear(input_size, 50), nn.ReLU(), nn.Dropout(0.4), nn.Linear(50, 1)) def forward(self, x): x = self.flatten(x) return torch.flatten(self.sequential(x)) summary(hit_model, input_size=X_train.shape, col_names=['input_size', 'output_size', 'num_params'])
預期輸出
===================================================================================================================
Layer (type:depth-idx)                   Input Shape               Output Shape              Param #
===================================================================================================================
HittersModel                             [175, 19]                 [175]                     --
├─Flatten: 1-1                           [175, 19]                 [175, 19]                 --
├─Sequential: 1-2                        [175, 19]                 [175, 1]                  --
│    └─Linear: 2-1                       [175, 19]                 [175, 50]                 1,000
│    └─ReLU: 2-2                         [175, 50]                 [175, 50]                 --
│    └─Dropout: 2-3                      [175, 50]                 [175, 50]                 --
│    └─Linear: 2-4                       [175, 50]                 [175, 1]                  51
===================================================================================================================
Total params: 1,051
Trainable params: 1,051
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 0.18
===================================================================================================================
Input size (MB): 0.01
Forward/backward pass size (MB): 0.07
Params size (MB): 0.00
Estimated Total Size (MB): 0.09
===================================================================================================================

1,000 + 51 = 1,051 個參數,訓練樣本只有 175 筆。模型包含 Dropout(0.4);這個 lab 固定訓練 50 個 epoch,沒有設定早停。單次結果不能把表現歸因於 dropout、雙下降或任何一項正則化機制。

來源:Ch10-deeplearning-lab.ipynb
lab §10.9.1 · 訓練 50 個 epoch 之後測試
hit_trainer = Trainer(deterministic=True, max_epochs=50, log_every_n_steps=5, logger=hit_logger, callbacks=[ErrorTracker()]) hit_trainer.fit(hit_module, datamodule=hit_dm) hit_trainer.test(hit_module, datamodule=hit_dm)
預期輸出
Testing DataLoader 0: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 225.66it/s]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       Test metric             DataLoader 0
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
        test_loss              107904.640625
        test_mae            221.83148193359375
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[{'test_loss': 107904.640625, 'test_mae': 221.83148193359375}]

test_mae = 221.83,比 lasso 的 235.68 好。但先別急著下結論——課本 Table 10.2 裡神經網路是輸的(MAE 257.4 對線性迴歸的 254.7)。同一份資料、同一個做法,換個切分與 epoch 數就換了名次,比較名次時也要交代這些設定。

來源:Ch10-deeplearning-lab.ipynb
QUIZ · 怎麼讀這組數字

官方 lab 跑出線性 259.72、lasso 235.68、神經網路 221.83;課本 Table 10.2 卻是線性 254.7、lasso 252.3、神經網路 257.4。該怎麼解釋?

(A) 官方 lab 的版本比較新,所以它的數字才是對的
(B) 兩邊是不同的隨機切分與訓練設定,這種量級的差距落在單一測試集的變異裡,不能當成「神經網路比較好」的證據
(C) 課本用了 dropout 而 lab 沒有,所以課本的神經網路比較差
EXERCISE 4 · ISLP 10.10 第 5 題

課本 Table 10.2 裡,三個模型依「平均絕對誤差」排出來的名次,跟依「測試 R²」排出來的名次不一樣。這怎麼可能?

(A) 兩個指標對大誤差的懲罰方式不同:R² 建立在平方誤差上,少數幾筆很大的殘差會嚴重拉低它,MAE 則是線性計入
(B) 因為 R² 有考慮參數量而 MAE 沒有,所以參數多的模型 R² 會被扣分
(C) 一定是其中一個數字算錯了,兩個指標衡量的是同一件事所以名次必須一致
PART 02 · 多層與 MNIST

多層網路與分類輸出 教科書 · 第 10.2 節進階參考 · 第 11.4–11.6 節

多加一層之後,第二層接收第一層的輸出 A^(1),由它繼續計算。 式子還是那個式子,只是套了兩次:

$$A^{(1)}_k = g\!\left(w^{(1)}_{k0} + \sum_j w^{(1)}_{kj} X_j\right), \qquad A^{(2)}_\ell = g\!\left(w^{(2)}_{\ell 0} + \sum_k w^{(2)}_{\ell k} A^{(1)}_k\right)$$

MNIST 是十類別的問題,所以輸出層要輸出十個數字再轉成機率。用的是 softmax:

$$f_m(X) = \Pr(Y = m \mid X) = \frac{e^{Z_m}}{\sum_{\ell=0}^{9} e^{Z_\ell}} \tag{10.13}$$

損失函數則是負的多項式對數概似,也就是交叉熵:

$$-\sum_{i=1}^{n} \sum_{m=0}^{9} y_{im} \log f_m(x_i) \tag{10.14}$$
softmax 是過度參數化的 把每個 Z_m 同時加上一個常數 c,分子分母各多一個 e^c, 約掉之後機率完全不變(這正是習題 2a)。所以 softmax 的解不唯一。 但實務上不成問題:正則化與 SGD 會把解限制在一個特定的地方。 同樣的事在第 4 章的多元邏輯斯迴歸也發生過,那裡的處理方式是挑一個類別當基準。

參數量是這一節的重點。MNIST 的輸入是 28×28 = 784 個畫素, ISLP 用的架構是 784 → 256 → 128 → 10。光第一層就有 784×256 + 256 = 200,960 個參數, 三層加起來 235,146 個。對照組多元邏輯斯迴歸只需要 785×9 = 7,065 個, 差了 33 倍。

延伸計算:網路參數量與 softmax 性質
›拖滑桿改層寬,看參數量怎麼變。預設值就是 ISLP §10.2 的 784–256–128–10。
h₁256
h₂128
講義/課本重繪
怎麼玩
拖滑桿改每一層的寬度,右邊即時算出參數量。參數幾乎都集中在第一層,因為 784 這個輸入維度最大。把第一層從 256 降到 64,總參數量會減少四分之三,但測試準確率通常掉不了多少。這就是 CNN 想解決的浪費。
參數量
第 1 層 784 → h₁—
第 2 層 h₁ → h₂—
輸出層 h₂ → 10—
合計—
對照:多元邏輯斯 785×97,065
是它的幾倍—
怎麼算
一層從 a 個單元接到 b 個單元,權重 a×b 個、偏置 b 個,共 (a+1)×b 個。習題 1(d) 與 4(b) 考的就是這個,差別只在 CNN 的權重是共享的。
QUIZ · 參數量

一個網路是 784 → 256 → 128 → 10 的全連接架構(每層都有偏置)。第一層有幾個參數?

(A) 200,960 個:權重 784×256 = 200,704,加上 256 個偏置
(B) 200,704 個,偏置不算參數因為它不乘任何輸入
(C) 1,040 個:784 + 256 個單元各一個參數
EXERCISE 1 · ISLP 10.10 第 1 題(d)

一個網路有 p = 4 個輸入、第一層 2 個隱藏單元、第二層 3 個隱藏單元、單一輸出,每一層都有偏置。總共有幾個參數?

(A) 23 個
(B) 17 個
(C) 10 個
EXERCISE 2 · ISLP 10.10 第 2 題(a)

softmax 的定義是 f_m(X) = e^{{Z_m}} / Σ_ℓ e^{{Z_ℓ}}。把每一個 Z_ℓ 都加上同一個常數 c,機率會怎麼變?

(A) 每個機率都乘上 e^c,所以加總不再是 1
(B) 機率會變得更平均,因為指數函數把大的值壓縮了
(C) 完全不變,因為分子分母各多出一個 e^c,約掉了
完整實作:MNIST 網路與比較

官方 lab §10.9.2:MNIST 上的兩層網路

lab §10.9.2 · MNISTModel 與參數量
class MNISTModel(nn.Module): def __init__(self): super(MNISTModel, self).__init__() self.layer1 = nn.Sequential( nn.Flatten(), nn.Linear(28*28, 256), nn.ReLU(), nn.Dropout(0.4)) self.layer2 = nn.Sequential( nn.Linear(256, 128), nn.ReLU(), nn.Dropout(0.3)) self._forward = nn.Sequential( self.layer1, self.layer2, nn.Linear(128, 10)) def forward(self, x): return self._forward(x) summary(mnist_model, input_data=X_, col_names=['input_size', 'output_size', 'num_params'])
預期輸出
===================================================================================================================
Layer (type:depth-idx)                   Input Shape               Output Shape              Param #
===================================================================================================================
MNISTModel                               [256, 1, 28, 28]          [256, 10]                 --
├─Sequential: 1-1                        [256, 1, 28, 28]          [256, 10]                 --
│    └─Sequential: 2-1                   [256, 1, 28, 28]          [256, 256]                --
│    │    └─Flatten: 3-1                 [256, 1, 28, 28]          [256, 784]                --
│    │    └─Linear: 3-2                  [256, 784]                [256, 256]                200,960
│    │    └─ReLU: 3-3                    [256, 256]                [256, 256]                --
│    │    └─Dropout: 3-4                 [256, 256]                [256, 256]                --
│    └─Sequential: 2-2                   [256, 256]                [256, 128]                --
│    │    └─Linear: 3-5                  [256, 256]                [256, 128]                32,896
│    │    └─ReLU: 3-6                    [256, 128]                [256, 128]                --
│    │    └─Dropout: 3-7                 [256, 128]                [256, 128]                --
│    └─Linear: 2-3                       [256, 128]                [256, 10]                 1,290
===================================================================================================================
Total params: 235,146
Trainable params: 235,146
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 60.20
===================================================================================================================
Input size (MB): 0.80
Forward/backward pass size (MB): 0.81
Params size (MB): 0.94
Estimated Total Size (MB): 2.55
===================================================================================================================

兩層的 dropout 比例不一樣(0.4 與 0.3):靠近輸入的層通常丟得多一點。nn.Flatten() 把 28×28 攤成 784 的向量,這個動作把畫素的空間關係整個丟掉了,下一節的卷積網路就是為了把它找回來。

來源:Ch10-deeplearning-lab.ipynb
lab §10.9.2 · 測試準確率
mnist_trainer.test(mnist_module, datamodule=mnist_dm)
預期輸出
/Users/jonathantaylor/git-repos/ISLP/ISLP_labs/.venv/lib/python3.12/site-packages/pytorch_lightning/utilities/_pytree.py:21: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       Test metric             DataLoader 0
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      test_accuracy         0.9620000123977661
        test_loss           0.15120187401771545
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[{'test_loss': 0.15120187401771545, 'test_accuracy': 0.9620000123977661}]

test_accuracy = 0.9620,錯誤率 3.8%。課本 Table 10.1 報的是 1.8%(dropout 版),差別在訓練得比較久。

來源:Ch10-deeplearning-lab.ipynb
lab §10.9.2 · 拿多元邏輯斯迴歸當對照
class MNIST_MLR(nn.Module): def __init__(self): super(MNIST_MLR, self).__init__() self.linear = nn.Sequential(nn.Flatten(), nn.Linear(784, 10)) def forward(self, x): return self.linear(x) mlr_model = MNIST_MLR() mlr_module = SimpleModule.classification(mlr_model, num_classes=10) mlr_logger = CSVLogger('logs', name='MNIST_MLR') mlr_trainer.test(mlr_module, datamodule=mnist_dm)
預期輸出
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       Test metric             DataLoader 0
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      test_accuracy          0.916100025177002
        test_loss           0.3469300866127014
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[{'test_loss': 0.3469300866127014, 'test_accuracy': 0.916100025177002}]

同一個 torch 框架,把中間兩層拿掉就是多元邏輯斯迴歸,nn.Linear(784, 10) 加 softmax 而已。準確率 0.9161,錯誤率 8.4%,是兩層網路的兩倍多。這個例子裡多的那兩層是真的有用的,跟 Hitters 那個「打平」的結論不一樣。

來源:Ch10-deeplearning-lab.ipynb
PART 03 · 卷積神經網路

把「哪裡」的資訊丟掉一部分:卷積與池化 教科書 · 第 10.3 節

上一節把 28×28 攤平成 784 的那一刻,「這兩個畫素相鄰」的資訊就沒了。 對影像來說這很浪費:一隻貓的耳朵出現在左上角還是右下角,都應該算是耳朵。 卷積網路把這個先驗直接寫進架構,做法有兩個。

CNN 的兩個動作 卷積層:拿一個小濾波器(例如 3×3)掃過整張圖, 每個位置算一次內積。同一個濾波器在所有位置共用同一組權重, 所以參數量跟影像大小無關,只跟濾波器大小有關。
池化層:把相鄰一小塊(例如 2×2)濃縮成一個值,最大池化取最大者。 它降解析度,也讓結果對小幅平移不敏感:刻意丟掉一些「在哪裡」,換取「有沒有」的穩健。
›拖列與欄,直接檢查任一卷積視窗和對應輸出。
概念示意
怎麼玩
直接拖「列/欄」選擇 3×3 濾波器目前對齊的位置。左邊框線標出輸入視窗,右邊同步標出對應的特徵圖格子與內積。換濾波器看看:垂直邊緣只對左右明暗交界有反應。
目前這一步
濾波器垂直邊緣
對齊位置—
這一格的內積—
特徵圖大小—
這一層要估幾個參數—
換成全連接要幾個—
為什麼參數少這麼多
3×3 濾波器不管掃 8×8 還是 1024×1024,都只有 9 個權重加 1 個偏置。同樣的輸出如果用全連接層做,每個輸出單元都要自己一組權重,那就是習題 4(d) 在算的東西。
觀念釐清
Q:池化為什麼要「丟掉資訊」?這樣有什麼好處?

丟掉的是位置的精度,留下的是「這個特徵存不存在」。對分類來說後者才是重點——你要判斷這是不是一隻貓,不需要知道耳朵精確在第 137 個畫素。

更實際的理由是它讓後面的層看得更廣。經過一次 2×2 池化,同樣一個 3×3 濾波器在原圖上涵蓋的範圍就變成兩倍。堆疊「卷積+池化」等於一步步擴大視野:前面的層看邊緣與紋理,後面的層看得到整隻動物。官方 lab 的 CIFARModel 疊了四組,32×32 因此縮成 2×2。

Q:資料擴增算正則化嗎?

算,而且 ISLP §10.3.4 就是這樣定位它的。把訓練影像隨機翻轉、平移、縮放之後標籤不變,等於在告訴模型「這些變化不該影響答案」。這是一個很強的約束,跟嶺迴歸用懲罰項限制係數是同一種事,都在縮小模型能選的函數空間。

差別在於它是用資料表達的約束。好處是很容易加進任何模型;壞處是你得知道哪些變化是無害的(影像可以左右翻,手寫數字的 6 跟 9 就不能上下翻)。

完整實作與計算:CNN、參數量及預訓練模型

官方 lab §10.9.3:CIFAR-100 上的 CNN

lab §10.9.3 · 一個 building block 疊四次
class BuildingBlock(nn.Module): def __init__(self, in_channels, out_channels): super(BuildingBlock, self).__init__() self.conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=(3,3), padding='same') self.activation = nn.ReLU() self.pool = nn.MaxPool2d(kernel_size=(2,2)) def forward(self, x): return self.pool(self.activation(self.conv(x))) class CIFARModel(nn.Module): def __init__(self): super(CIFARModel, self).__init__() sizes = [(3,32), (32,64), (64,128), (128,256)] self.conv = nn.Sequential(*[BuildingBlock(in_, out_) for in_, out_ in sizes]) self.output = nn.Sequential(nn.Dropout(0.5), nn.Linear(2*2*256, 512), nn.ReLU(), nn.Linear(512, 100)) def forward(self, x): val = self.conv(x) val = torch.flatten(val, start_dim=1) return self.output(val)

BuildingBlock 就是「卷積 → ReLU → 最大池化」三個步驟。通道數一路 3 → 32 → 64 → 128 → 256 加倍,空間尺寸則被池化一路砍半:32 → 16 → 8 → 4 → 2。解析度換通道數是 CNN 的標準節奏。

來源:Ch10-deeplearning-lab.ipynb
lab §10.9.3 · 測試準確率
cifar_trainer.test(cifar_module, datamodule=cifar_dm)
預期輸出
/Users/jonathantaylor/git-repos/ISLP/ISLP_labs/.venv/lib/python3.12/site-packages/pytorch_lightning/utilities/_pytree.py:21: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       Test metric             DataLoader 0
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      test_accuracy         0.4269999861717224
        test_loss            2.45865797996521
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[{'test_loss': 2.45865797996521, 'test_accuracy': 0.4269999861717224}]

0.4270。一百個類別的隨機猜測是 1%,所以 43% 其實不算差,但也遠稱不上好:CIFAR-100 每類只有 500 張訓練影像,這種規模對 CNN 太少了。下一張卡的做法才是資料不多時該走的路。

來源:Ch10-deeplearning-lab.ipynb
lab §10.9.4 · 直接用預訓練的 ResNet50
labs = json.load(open('imagenet_class_index.json')) class_labels = pd.DataFrame([(int(k), v[1]) for k, v in labs.items()], columns=['idx', 'label']) class_labels = class_labels.set_index('idx') class_labels = class_labels.sort_index() for i, imgfile in enumerate(imgfiles): img_df = class_labels.copy() img_df['prob'] = img_probs[i] img_df = img_df.sort_values(by='prob', ascending=False)[:3] print(f'Image: {imgfile}') print(img_df.reset_index().drop(columns=['idx']))
預期輸出
Image: book_images/Cape_Weaver.jpg
      label      prob
0   jacamar  0.297499
1     macaw  0.068107
2  lorikeet  0.051105
Image: book_images/Flamingo.jpg
            label      prob
0        flamingo  0.609515
1       spoonbill  0.013586
2  American_egret  0.002132
Image: book_images/Hawk_Fountain.jpg
            label      prob
0            kite  0.184682
1           robin  0.084021
2  great_grey_owl  0.061274
Image: book_images/Hawk_cropped.jpg
            label      prob
0            kite  0.453833
1  great_grey_owl  0.015914
2             jay  0.012210
Image: book_images/Lhasa_Apso.jpg
             label      prob
0            Lhasa  0.260317
1         Shih-Tzu  0.097196
2  Tibetan_terrier  0.032820
Image: book_images/Sleeping_Cat.jpg
         label      prob
0  Persian_cat  0.163070
1        tabby  0.074143
2    tiger_cat  0.042578

這個模型在 ImageNet 上訓練過,完全沒看過這幾張照片。紅鶴 0.61、拉薩犬 0.26 都對;但第一張織巢鳥被猜成 jacamar(鶲䴕),而且最高機率只有 0.30。看機率不要只看排名——0.30 這種數字本身就在說「我不太確定」。

來源:Ch10-deeplearning-lab.ipynb
QUIZ · 卷積的參數量

輸入是 32×32 的灰階影像,一個卷積層有三個 5×5 濾波器、不做邊界填補。這一層要估幾個參數?

(A) 2,352 個,因為輸出有 3 × 28 × 28 個隱藏單元
(B) 2,408,448 個,每個隱藏單元都要連到全部 1,024 個畫素
(C) 78 個:3 × (5×5 + 1)
EXERCISE 3 · ISLP 10.10 第 4 題(b)(d)

輸入是 32×32 灰階影像,一個卷積層有三個 5×5 濾波器、不做邊界填補。「這一層的參數量」與「拿掉權重共享後的權重數」分別是多少?

(A) 78 個與 78 個,因為權重共享不改變參數量
(B) 75 個與 2,352 個
(C) 78 個與 2,408,448 個
進一步應用:資料增強與移轉學習

資料增強與移轉學習

資料增強(data augmentation)在訓練時對影像隨機平移、縮放、小角度旋轉或適當翻轉, 讓模型對應保留類別意義的變化。它為同一訓練影像建立附近的變體,可視為一種正則化,並未產生新的獨立受試者或獨立場景。

增強必須維持標籤合理:左右翻轉某些物體照片可行,旋轉數字 6 卻可能改成另一類。 先切分原始影像,再對訓練部分做隨機增強,避免同一原圖的不同版本分散到訓練與測試集。 評估使用明確固定的前處理;若用測試時增強,須另外說明合併預測的程序。

預訓練模型還可作移轉學習:保留已學到的卷積特徵,凍結部分權重,只用新資料訓練後幾層。 這與上面直接使用原本的 ResNet50 分類器不同;新任務需有自己的標籤、輸出層與獨立評估。

來源:ISLP §10.3.4–10.3.5,印刷 pp.411–412;官方 Lab §10.9.4 展示直接使用預訓練分類器,未宣稱執行了本站所述移轉訓練。

PART 04 · 文件分類與 RNN

文件分類中的詞袋表示與順序資訊 教科書 · 第 10.4 節教科書 · 第 10.5 節

影像的結構是空間上的,文字與股價的結構是順序上的。 ISLP 用 IMDB 影評的正負評分類當例子,先示範最簡單的做法:詞袋。 把每篇評論表示成「字典裡每個詞出現了沒有」的長向量, 然後就當成普通的分類問題丟給 lasso 或神經網路。

詞袋完全丟掉詞序,所以「這部片一點都不好看」跟「這部片好看一點都不」是同一個向量。 詞袋在情感分類這種任務上仍可有良好表現,因為有沒有出現 terrible 這個詞,本身就是很強的訊號。

完整實作:詞袋網路
lab §10.9.5 · 詞袋 + 兩層網路的 IMDB 準確率
test_results = imdb_trainer.test(imdb_module, datamodule=imdb_dm) test_results
預期輸出
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       Test metric             DataLoader 0
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      test_accuracy         0.8450000286102295
        test_loss            1.216800332069397
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[{'test_loss': 1.216800332069397, 'test_accuracy': 0.8450000286102295}]

0.8450。這是詞袋能到的水準,沒有用到任何詞序資訊。

來源:Ch10-deeplearning-lab.ipynb

要把順序找回來,就要換成遞迴神經網路。 它依序讀入 X₁, X₂, …, X_L,每讀一個就用同一組權重更新一個隱藏狀態:

$$A_\ell = g\!\left(W X_\ell + U A_{\ell-1} + b\right), \qquad O_\ell = \beta_0 + \beta^{\top} A_\ell$$

W、U、b 在每個時間點都一樣。這跟 CNN 在空間上共享權重是同一個念頭, 只是換成在時間上共享。序列多長都用同一組參數。

RNN 在各時間點共用參數 每個時間點都套用同一組 W、U、b:目前輸入 Xℓ 與上一個狀態 Aℓ−1 一起產生新狀態 Aℓ。這些權重由訓練資料學得。
時間讀入共用的更新保留下來的資訊
ℓ = 1X₁A₁ = g(WX₁ + UA₀ + b)第一個詞的脈絡
ℓ = 2X₂A₂ = g(WX₂ + UA₁ + b)X₁ 與 X₂ 的摘要
……同一組 W、U、b狀態沿序列傳遞
ℓ = LX_LA_L = g(WX_L + UA_{L−1} + b)供最後輸出使用的序列摘要
架構與模型表現 這裡只解釋 ISLP §10.5 的 recurrence 結構;模型表現回到下面官方 lab 的 LSTM 與詞袋實跑結果比較。公式說明架構,lab 結果提供這次擬合的表現。
完整實作:LSTM、NYSE 與模型表現比較

官方 lab §10.9.6:LSTM 與時間序列

lab §10.9.6 · LSTM 版的 IMDB 分類
class LSTMModel(nn.Module): def __init__(self, input_size): super(LSTMModel, self).__init__() self.embedding = nn.Embedding(input_size, 32) self.lstm = nn.LSTM(input_size=32, hidden_size=32, batch_first=True) self.dense = nn.Linear(32, 1) def forward(self, x): val, (h_n, c_n) = self.lstm(self.embedding(x)) return torch.flatten(self.dense(val[:,-1])) lstm_trainer.test(lstm_module, datamodule=imdb_seq_dm)
預期輸出
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       Test metric             DataLoader 0
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      test_accuracy         0.8400400280952454
        test_loss            0.849097490310669
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[{'test_loss': 0.849097490310669, 'test_accuracy': 0.8400400280952454}]

0.8400——比詞袋的 0.8450 還低一點點。ISLP 的這個結果顯示:情感分類這個任務,詞序帶來的好處沒有想像中大。nn.Embedding 那一層把每個詞的整數編號映射成 32 維的稠密向量,是這類模型的標準第一步。

來源:Ch10-deeplearning-lab.ipynb
lab §10.9.6 · NYSE 交易量的自迴歸
M = LinearRegression() M.fit(X[train], Y[train]) M.score(X[~train], Y[~train]) M.fit(X_day[train], Y[train]) M.score(X_day[~train], Y[~train])
預期輸出
0.4595563133053273

落後 5 期的線性自迴歸模型測試 R² 是 0.4129,加上星期幾這個因子之後變成 0.4596。對照組是 RNN 的 0.4150與非線性自迴歸的 0.4660。四個模型全部落在 0.41 到 0.47 之間。

來源:Ch10-deeplearning-lab.ipynb
QUIZ · 詞袋輸給 RNN 了嗎

IMDB 上詞袋加兩層網路是 0.8450,LSTM 是 0.8400。下面哪一句最站得住腳?

(A) 這個任務的訊號多半在「有沒有出現某些詞」,詞序能加的資訊有限,所以兩者打平不奇怪
(B) LSTM 比較差,代表遞迴架構不適合處理文字
(C) 因為 LSTM 只訓練了很少的 epoch,訓練久一點就會贏
延伸:時間序列輸入、落後期與評估

將時間序列整理成訓練樣本

NYSE 例子以 $v_t$ 表示當日對數交易量,$r_t$、$z_t$ 為另外兩個歷史量測。 用前 L 天預測第 t 天,則一筆輸入與目標為:

$$X_t=\begin{bmatrix}v_{t-L}&r_{t-L}&z_{t-L}\\\vdots&\vdots&\vdots\\ v_{t-1}&r_{t-1}&z_{t-1}\end{bmatrix},\qquad Y_t=v_t.$$

X 的形狀是 L×3。T 天資料可建立 T−L 筆樣本;L=5、T=6,051 時為 6,046 筆。 相鄰樣本會共用部分日期,不能當成互相獨立的抽樣,更不能隨機打亂後宣稱是在預測未來。

RNN 依時間順序處理五個三維向量;線性自迴歸把同一批歷史量展平成 15 個欄位,連同截距擬合:

$$\hat v_t=\beta_0+\sum_{\ell=1}^L(\beta_{v\ell}v_{t-\ell}+\beta_{r\ell}r_{t-\ell}+\beta_{z\ell}z_{t-\ell}).$$

訓練日期早於評估日期,標準化與參數估計只使用訓練部分。 逐日的一步預測可以使用已經實際觀察到的前一天測試期交易量;若要在同一天一次預測未來很多天,就不能偷用尚未觀察到的數值。 星期幾可由日曆提前知道,所以與當天未知交易量的可用性不同。

來源:ISLP §10.5.2,印刷 pp.421–424、式 10.20–10.22;官方 Lab 時間序列段使用相同落後期構造。這些是歷史資料的教學示例。

PART 05 · 怎麼擬合

反向傳播、SGD、dropout:神經網路的訓練流程 教科書 · 第 10.7 節進階參考 · 第 11.4 節 · 進階

前面都在講模型長什麼樣,這一節講怎麼把參數估出來。目標函數以量化反應為例:

$$R(\theta) = \frac{1}{2} \sum_{i=1}^{n} \left(y_i - f_\theta(x_i)\right)^2 \tag{10.25}$$

問題在於這個 R(θ) 不是凸函數。線性迴歸有閉式解、 邏輯斯迴歸的對數概似是凹的,但唯一有限解還需要可識別性與不存在分離等條件。神經網路通常沒有閉式解或凸性保障: 它有很多局部極小,而我們只能用梯度下降慢慢滑下去、 以找到足夠好的解為目標,但無法保證找到全域最佳解。

數值實驗:起點與步長如何影響梯度下降
圖表需要連網載入 Chart.js。此圖的重點:同一個函數、同一個學習率,只是起點不同,梯度下降就會滑進不同的局部極小,這是非凸最佳化可能出現的結果。
›拖滑桿改起點與學習率,按「開始」看軌跡。
β⁰2.3
ρ0.10
講義/課本重繪
怎麼玩 習題 6
這是 ISLP 習題 6 的函數 R(β) = sin(β) + β/10。拖滑桿改起點 β⁰ 與學習率 ρ,看軌跡往哪裡收斂。β⁰ = 2.3 會滑到 β ≈ 4.61,β⁰ = 1.4 卻掉到 β ≈ −1.67——兩個不同的谷底,這正是 6(c) 與 6(d) 要你比較的事。提高學習率時,軌跡可能在極小點兩側交替靠近;振盪不等於發散。
這次的軌跡
起點 β⁰2.30
學習率 ρ0.10
走了幾步—
收斂到 β ≈—
那裡的 R(β)—
落在哪個極小點附近—
更新規則
每一步做 β ← β − ρ·R′(β),其中 R′(β) = cos(β) + 1/10。導數為 0 的地方就是駐點。收斂需要步長與目標函數等條件;即使收斂到駐點,不保證是最低的那個。
三個讓它跑得動的技巧 反向傳播:用連鎖律由輸出往回逐層算梯度, 重複利用前一層算好的中間量, 成本跟一次前向傳播同一個量級。
隨機梯度下降:每一步只用一小批(minibatch)樣本估梯度。 除了便宜,取樣雜訊會改變最佳化軌跡,有時有助於泛化或離開某些區域,但不保證跳出所有不好的局部極小。
Dropout:訓練時隨機把一部分單元設成 0,剩下的按比例放大。 每一批看到的都是不同的子網路,沒有單元能依賴特定同伴。 ISLP 說它的精神接近隨機森林,都靠隨機性打散相關性。

另一個常用的正則化手段是早停。 訓練誤差通常隨訓練下降,但 SGD 的單步或單一 epoch 不保證單調;驗證誤差可能先降後升;在轉折點停下來, 效果跟加懲罰項類似。ErrorTracker 回呼只記錄驗證曲線;官方 Hitters lab 仍跑滿 max_epochs=50,沒有 EarlyStopping 回呼或依驗證結果選 epoch。

觀念釐清
Q:非凸最佳化會受局部極小影響,為什麼仍可用來訓練模型?

局部極小會影響結果,但仍有幾個因素有助於找到可用的解。

第一,實務上在高維空間裡,會嚴重影響結果的局部極小相對較少:也可能遇到鞍點;在適當條件下,隨機擾動有助於離開鞍點附近。第二,學習的目標是降低測試誤差;訓練誤差的全域最小值本身不足以衡量泛化;訓練誤差的全域最小很可能就是嚴重過度擬合的那個點。

此外,訓練結果會受隨機性影響。同一份資料換一個隨機種子,跑出來的網路不一樣、測試誤差也不一樣。官方 lab 每次訓練前都呼叫 seed_everything,並在 Trainer 裡設 deterministic=True,就是為了讓結果可重現。

Q:dropout 為什麼要「按比例放大」?

因為訓練與預測時的行為必須對得起來。訓練時如果丟掉 40% 的單元,下一層收到的加總平均就只剩 60%;預測時所有單元都在,加總會突然變大 1/0.6 倍,模型等於面對一個沒見過的輸入分布。

解法是訓練時把留下來的值除以 0.6,讓期望值維持不變,預測時就什麼都不用做。這叫 inverted dropout,也是 nn.Dropout 的做法。順帶一提,這就是為什麼 model.eval() 很重要——它會關掉 dropout;忘了呼叫的話預測會帶著隨機性。

QUIZ · minibatch 的大小

把批次大小從 32 改成整個訓練集(也就是每一步都用全部資料算梯度),會發生什麼?

(A) 完全一樣,因為梯度的期望值不變
(B) 一個 epoch 只更新一次,使用完整資料梯度並移除批次抽樣雜訊
(C) 會更快,因為少了每個批次的額外成本
計算細節:反向傳播與正則化

反向傳播:每一個參數要往哪裡更新?

先看一筆輸入 $x_i$、反應 $y_i$ 與一層隱藏層。令 $z_{ik}=w_{k0}+\sum_jw_{kj}x_{ij}$、$a_{ik}=g(z_{ik})$, $f_i=\beta_0+\sum_k\beta_ka_{ik}$,單筆損失 $R_i=\tfrac12(f_i-y_i)^2$。 以 $e_i=f_i-y_i$ 記錄預測誤差;在 g 可微處,四種梯度是:

$$\frac{\partial R_i}{\partial\beta_0}=e_i,\qquad \frac{\partial R_i}{\partial\beta_k}=e_i a_{ik},$$ $$\frac{\partial R_i}{\partial w_{k0}}=e_i\beta_k g'(z_{ik}),\qquad \frac{\partial R_i}{\partial w_{kj}}=e_i\beta_k g'(z_{ik})x_{ij}.$$

每個參數收到的梯度,包含輸出誤差、沿途權重、激發函數導數與輸入。 對完整資料的加總損失,將各筆梯度相加;若目標採平均損失,就再除以 n。 所有梯度都用同一個更新前的參數版本計算,最後才一起做 $\theta\leftarrow\theta-\rho\nabla R$。

證明:單隱藏層的連鎖律

輸出損失的導數為 $\partial R_i/\partial f_i=f_i-y_i=e_i$。 沿著 $\beta_k\to f_i\to R_i$ 的路徑:

$$\frac{\partial R_i}{\partial\beta_k}=\frac{\partial R_i}{\partial f_i}\frac{\partial f_i}{\partial\beta_k}=e_ia_{ik}.$$

沿著 $w_{kj}\to z_{ik}\to a_{ik}\to f_i\to R_i$:

$$\frac{\partial R_i}{\partial w_{kj}} =\frac{\partial R_i}{\partial f_i}\frac{\partial f_i}{\partial a_{ik}} \frac{\partial a_{ik}}{\partial z_{ik}}\frac{\partial z_{ik}}{\partial w_{kj}} =e_i\beta_kg'(z_{ik})x_{ij}.$$

截距的最後一個導數為 1,因此移除相應輸入因子便得到截距梯度。 計算時重用前向傳播儲存的 z、a 與輸出,不必對每個參數重新走完整個網路。

多層網路沿同一規則往回算

令 $z^{(\ell)}=W^{(\ell)}a^{(\ell-1)}+b^{(\ell)}$、$a^{(\ell)}=g_\ell(z^{(\ell)})$, 並定義 $\delta^{(\ell)}=\partial L/\partial z^{(\ell)}$。逐元素相乘記作 $\odot$,則:

$$\delta^{(\ell)}=(W^{(\ell+1)})^\top\delta^{(\ell+1)}\odot g_\ell'(z^{(\ell)}),$$ $$\frac{\partial L}{\partial W^{(\ell)}}=\delta^{(\ell)}(a^{(\ell-1)})^\top, \qquad\frac{\partial L}{\partial b^{(\ell)}}=\delta^{(\ell)}.$$

以 softmax 機率 p 與 one-hot 類別 y 的交叉熵作輸出損失時,輸出層的 δ 就是 p−y。 這也是為什麼擬合要保留原始 logits:例如 PyTorch 的 CrossEntropyLoss 已包含 log-softmax,不要先做一次 softmax 再當作 logits 傳入。

證明:softmax 交叉熵的輸出梯度

$p_m=e^{z_m}/\sum_re^{z_r}$,因此 $\partial p_m/\partial z_k=p_m(\mathbf1_{m=k}-p_k)$。 對 $L=-\sum_my_m\log p_m$ 微分,並使用 $\sum_my_m=1$:

$$\frac{\partial L}{\partial z_k}=-\sum_my_m(\mathbf1_{m=k}-p_k)=p_k-y_k.$$

隱藏層 δ 的公式則把每個後續單元的影響加總,再乘上本層的激發函數導數。

正則化、minibatch 與停止時機

Ridge 懲罰把權重平方和加進目標,例如對多類別分類:

$$R(\theta;\lambda)=-\sum_i\sum_my_{im}\log p_m(x_i)+\lambda\sum_{j\in J}\theta_j^2, \qquad\lambda\ge0.$$

J 是決定要懲罰的參數集合;不一定包含所有截距或輸出層權重。 受懲罰參數的梯度多出 $2\lambda\theta_j$。不同軟體的損失採加總或平均、懲罰有無 1/2,會影響 λ 的數值對應。

若定義 $R=\frac1n\sum_iR_i$,隨機抽取大小 B 的批次,以 $\frac1B\sum_{i\in\mathcal B}\nabla R_i$ 估計梯度。 一個 epoch 表示大致使用完整訓練集一次;48,000 筆資料、批次 128,約更新 375 次。 層數、層寬、學習率、批次大小、λ、dropout 比率與 epoch 數,都應用訓練/驗證資料決定,測試集留作最後評估。

來源:ISLP §10.7.1–10.7.4,印刷 pp.428–431,式 10.26–10.31;多層 δ 與 softmax 微分是依同一連鎖律展開的本站補充。 PyTorch CrossEntropyLoss 輸入規格。

圖中的 $R(\beta)=\sin\beta+\beta/10$ 滿足 $|R''(\beta)|\le1$,滑桿 0.02–1.6 在下降步長範圍內。 但它在整條實數線上沒有下界;β 趨向負無限時 R 也趨向負無限。 圖中兩個谷底的比較只限顯示區間,不能稱為整條實數線上的全域最小值。

PART 06 · 雙下降與該不該用

參數比樣本多還會變好?以及如何評估模型 教科書 · 第 10.8 節教科書 · 第 10.6 節

模型更大時,測試誤差不一定只升不降;但模型大小也不能代替驗證。先建立簡單基準,再用獨立資料判斷增加複雜度是否有幫助。

進階:內插、最小範數與雙下降

從第 2 章開始,這本書一直在講同一件事:測試誤差對模型彈性是 U 形的, 因此把訓練誤差壓到 0 後,仍需檢查泛化表現。§10.8 進一步討論雙下降現象。

設定很簡單,用的還是第 7 章的自然樣條,跟神經網路無關: n = 20 筆資料來自 Y = sin(X) + ε,X 均勻分布在 [−5, 5]、ε 的標準差是 0.3。 然後擬合自由度 d 的自然樣條,讓 d 從 2 一路加到 100。

d = 20 時自由度等於樣本數,樣條剛好穿過每一個點,訓練誤差變成 0: 這就是內插門檻。d 再往上,最小平方解不再唯一(有無限多組係數都能穿過), 課本挑的是係數平方和最小的那一組,也就是最小範數解。

圖表需要連網載入 Chart.js。此圖的重點:測試誤差在 d = n = 20 附近衝到最高,然後隨著自由度繼續增加又降下來,這就是雙下降。訓練誤差則在 d = 20 之後一直是 0。
›紅線是測試誤差、藍線是訓練誤差。注意 d = 20 那根尖峰。
模擬示意
怎麼看這張圖 圖 10.20/10.21
橫軸是自然樣條的自由度 d,縱軸是誤差(對數刻度)。藍線訓練誤差在 d = 20 之後貼著 0;紅線測試誤差先 U 形、在 d = 20 衝高、然後第二次下降。按「看擬合曲線」切換到課本圖 10.21 的四個面板。
幾個位置
d = 8(U 形谷底)—
d = 19—
d = 20(內插門檻)—
d = 42—
d = 100—
訊噪比 Var(f)/σ²—
兩個要注意的地方
d = 20 只有一種方式穿過 20 個點,而那一種很極端;d = 42 有無限多種,挑出的平滑解有較小的起伏。切到擬合曲線會看到 Σβ² 在 d = 20 最大、之後反而變小。
另外,訓練誤差在 d ≥ 20 之後真的是 0(10⁻²⁸ 量級),但對數軸畫不出 0,所以藍線被壓在 10⁻⁵ 的底線上。
雙下降沒有推翻偏差–變異取捨 ISLP 提醒讀者:橫軸畫的是「基底函數的個數」, 而那並沒有正確反映內插模型實際的彈性。d = 42 的最小範數樣條, 變異其實比 d = 20 的樣條小,它只是用比較多的基底去表達一個比較平滑的函數。
另外兩件要記住的事:這本書裡大多數方法不會出現雙下降 (正則化方法通常不內插資料);而且在這個例子裡, 如果改用嶺迴歸擬合樣條並好好選懲罰項,測試誤差還會更低。 判斷模型大小時,仍需比較正則化與測試誤差。

這跟深度學習的關係在最後一段:訓練神經網路時我們常常用遠多於樣本數的參數, 而且真的能把訓練誤差壓到 0 還表現得不錯。ISLP 的解釋是 最佳化方法的隱含偏好也會影響最後選到的內插解。例如平方損失的線性模型從零初始化,採適當步長的梯度下降收斂時可得到最小歐氏範數解;一般神經網路的 SGD 不保證得到最小範數或最平滑解。 是否泛化良好仍要由資料、模型、正則化與獨立評估判斷。

順帶把 §10.6 的建議收在這裡。ISLP 對「該不該用深度學習」的答案是: 先跑簡單模型當基準,讓深度學習去證明自己值得。 Hitters 那個例子就是示範:三個模型表現相近時,選看得懂的那個。 CIFAR 的影像具有空間結構,適合發揮 CNN 的優勢;文字分類仍要實測詞袋的線性基準。 ISLP 圖 10.11 的 IMDB 比較中,lasso 邏輯斯迴歸與兩隱藏層網路的測試準確率就相近。

QUIZ · 雙下降在說什麼

下面哪一句符合 ISLP §10.8 的說明?

(A) 偏差–變異取捨在深度學習裡不成立,模型愈大測試誤差就愈低
(B) 測試誤差的第二次下降,是因為 d > n 時我們挑了最小範數解,而它比 d = n 的唯一內插解平滑得多
(C) 只要把訓練誤差壓到 0,測試誤差就會開始下降
EXERCISES · 練習

動手驗證:教科書第 10 章精選題 教科書 · 第 10.10 節 習題

下面幾題取自 ISLP §10.10 的課後習題,題號都對得回課本。先自己想過再點選項; 每個選項都有解說;需要完整解答時,可查閱下面2個站。

參數計算、softmax 性質與模型結果的練習,已放在各節相應的收合延伸中。第一輪先確認你能說明各種架構保留什麼資訊,以及訓練和驗證各負責什麼。

重點速查與來源

深度學習(補充)速查表 教科書 · 第 10 章 · 補充

來源:完整書目與章節定位

本頁書目與章節定位

  • 教科書:An Introduction to Statistical Learning with Applications in Python。James、Witten、Hastie、Tibshirani、Taylor,2023。原書與下載資訊
    本頁對應:教科書 · 第 10 章開頭;教科書 · 第 10.1 節;教科書 · 第 10.2 節;教科書 · 第 10.3 節;教科書 · 第 10.4 節;教科書 · 第 10.5 節;教科書 · 第 10.7 節;教科書 · 第 10.8 節;教科書 · 第 10.6 節。
  • 進階參考:The Elements of Statistical Learning。Hastie、Tibshirani、Friedman,第二版。原書與下載資訊
    本頁對應:進階參考 · 第 11.3 節;進階參考 · 第 11.4–11.6 節;進階參考 · 第 11.4 節 · 進階。

需要核對完整公式、比較表或實作時,再展開下方速查。

查閱完整速查與延伸提醒

考前把這一頁掃過去就好。這一章不在考試範圍,但它補充了 ISLP 的神經網路主題。

四種架構對照

架構適合什麼輸入關鍵構造權重共享嗎ISLP 節次
單層/多層全連接沒有特殊結構的表格資料隱藏層 + 激發函數沒有§10.1–10.2
卷積網路(CNN)影像等有空間結構的資料卷積 + 池化有,在空間上§10.3
遞迴網路(RNN/LSTM)文字、時間序列等有順序的資料隱藏狀態逐步更新有,在時間上§10.5
詞袋 + 全連接文件(丟掉詞序)出現與否的長向量不適用§10.4

官方 lab 的實跑數字

資料模型指標數值
Hitters線性迴歸測試 MAE259.72
Hitterslasso(10 折 CV)測試 MAE235.68
Hitters單層網路(50 單元、dropout 0.4)測試 MAE221.83
MNIST多元邏輯斯迴歸測試準確率0.9161
MNIST兩層網路(256/128)測試準確率0.9620
CIFAR-100四組卷積區塊測試準確率0.4270
IMDB詞袋 + 兩層網路測試準確率0.8450
IMDBLSTM測試準確率0.8400
NYSE線性 AR(落後 5 期)測試 R²0.4129
NYSE線性 AR + 星期幾測試 R²0.4596
NYSERNN(12 維隱藏狀態)測試 R²0.4150
NYSE非線性 AR測試 R²0.4660

這些是官方 lab 的實跑結果, 跟課本表格的數字不一樣:Table 10.1 的 MNIST 錯誤率是 1.8%(此處 3.8%), Table 10.2 的 Hitters MAE 是線性 254.7/lasso 252.3/神經網路 257.4。 差別來自切分、epoch 數與套件版本。引用時要說清楚是哪一組。

公式速查

名稱式子備註
單層網路$f(X) = \beta_0 + \sum_k \beta_k g(w_{k0} + \sum_j w_{kj}X_j)$式 10.1
ReLU$g(z) = \max(0, z)$式 10.5
Sigmoid$g(z) = e^z/(1+e^z)$式 10.3,就是邏輯斯函數
Softmax$f_m(X) = e^{Z_m} / \sum_\ell e^{Z_\ell}$式 10.13,加常數不變
交叉熵$-\sum_i \sum_m y_{im} \log f_m(x_i)$式 10.14,M = 2 時退回邏輯斯
平方誤差目標$R(\theta) = \tfrac12 \sum_i (y_i - f_\theta(x_i))^2$式 10.25,一般非凸
梯度下降更新$\theta \leftarrow \theta - \rho \nabla R(\theta)$式 10.27,ρ 是學習率
全連接層參數量$(a+1) \times b$a 個輸入接到 b 個輸出,+1 是偏置
卷積層參數量$(k^2 c_{in} + 1) \times c_{out}$跟影像大小無關
重點回顧 1. 神經網路是線性模型套非線性再組合一次。 把 g 拿掉就退回線性迴歸;非線性只能從 g 進來,加總本身不會產生它。
2. CNN 與 RNN 都使用權重共享。 一個在空間上共享、一個在時間上共享,兩者都透過架構指定權重共享的方式。
3. 雙下降需要連同解的選擇方式一起看。 第二次下降與最小範數解比較平滑有關;增加參數後如何選解也需交代; 同一個例子改用嶺迴歸還會更好。
使用時的檢查 先跑基準再跑網路。ISLP 每個例子都先給線性模型或 lasso 的數字, 沒有基準線你不會知道網路是好是壞。輸入一定要標準化,統計量只能從訓練資料算。 預測前記得 model.eval(),否則 dropout 還開著、 預測會帶隨機性。固定隨機種子:非凸最佳化換個種子就換個答案, 官方 lab 用 seed_everything 加 deterministic=True。
CARDS · 關鍵詞彙卡

關鍵詞彙卡:點卡片翻面 課程詞彙

詞彙卡整理本章講義與 ISLP 第 10 章中常用的統計或機器學習專有名詞,正面附英文名稱。 先看正面的術語,試著說出定義,再翻面核對。可以洗牌複習,也可以搭配本章例子練習解釋。

複習:展開關鍵詞彙卡