iPAS AI 應用規劃師 中級 科目三 機器學習技術與應用

VGG16 遷移學習怎麼凍結卷積層只訓練全連接層?

原題 45

VGG16 連題組第四題:在實務應用中,我們常使用遷移學習(transfer learning)技巧,即載入預訓練模型(如 VGG16),凍結部分層的參數,只針對特定任務重新訓練最後幾層,這種做法可節省訓練時間並提升模型效能。假設你要對 VGG16 進行遷移學習(transfer learning),希望凍結卷積層的參數,只訓練最後全連接層(classifier)。下列哪段程式碼寫法正確?

白話

動物園影像辨識專案的工程師想用 VGG16 預訓練模型做遷移學習,計畫凍結卷積層(model.features)讓它不更新,只重新訓練後面的全連接層(model.classifier)來適應動物分類任務。題目給了四段程式碼,每段都用不同方式嘗試實現「凍結 features,只訓練 classifier」,但只有一段是正確的。

問你:哪一段程式碼正確地「凍結卷積層、只讓全連接層更新」?

點選你的答案。

01 總結

一句話總結

遷移學習凍結卷積層的正確方法是選項 B:對 model.features.parameters() 設定 requires_grad = False,只凍結 features 部分,然後換掉 classifier[6]——這樣 features 不更新,新的 classifier[6] 預設 requires_grad = True,會正常訓練

02 情境

先感受問題:只讓「特定員工」去上新課

動物園工程師有一個 VGG16 預訓練模型,已經在 ImageNet 上學過 1,000 種物件的特徵。現在他只需要分類 10 種動物,不需要從頭訓練 1.38 億個參數。

策略:把卷積層(features)的「學習模式」關掉——它已經學得很好了,凍結保留;換掉最後一個全連接層,改成輸出 10 類;然後只訓練全連接層(classifier)。

在 PyTorch 裡,「學習模式」的開關是 requires_grad:True 表示要更新這個參數,False 表示凍結不動。關鍵是要精準地「只把 features 的 requires_grad 設為 False」,而不是整個模型、也不是 classifier。

03 對照

凍結錯對象,遷移學習會有什麼後果?

  1. 凍結全部(選項 A 的問題):把所有參數都設為 requires_grad = False,然後換掉 classifier[6]。換後的新層預設 requires_grad = True,但原本的 classifier[0-5] 已被凍結。這樣只有新換的最後一層在訓練,其他全連接層的權重都不更新,效果比選項 B 差。
  2. 凍結 classifier(選項 C):把正確應該訓練的那些層(classifier)全部凍結,反而讓 features 繼續更新,這完全相反:破壞了預訓練的特徵提取器,新任務也沒學到。
  3. 對 model 設屬性(選項 D):model.requires_grad = False 設的是 Python 物件屬性,不是 PyTorch 的 requires_grad 機制。PyTorch 的梯度控制是針對各個 Parameter 的,這行程式碼實際上不會凍結任何參數,是無效操作。
  4. requires_grad 是參數層級的設定:凍結必須針對每個 nn.Parameter,要用 .parameters() 迭代每個參數,不能直接對模型物件設定。
  5. 新換的層預設 requires_grad = True:`model.classifier[6] = nn.Linear(4096, 10)` 建立的新層,其參數預設是可訓練的,只要不對它設 False 就會自動更新。
04 解法

精準凍結 features,讓 classifier 繼續學習

動物園工程師的正確做法(選項 B):

import torch
import torchvision.models as models

model = models.vgg16(pretrained=True)

# Step 1:只凍結 features(卷積部分)
for param in model.features.parameters():
    param.requires_grad = False

# Step 2:換掉最後一層,輸出改為 10 類(動物)
model.classifier[6] = torch.nn.Linear(4096, 10)
# 新建的 Linear 層預設 requires_grad = True,會自動訓練

執行後的結果:features 裡的 13 層 Conv2d 全部凍結(約 14.7M 參數不更新);classifier 裡的 Linear-33、Linear-36 以及新的 classifier[6] 都是可訓練的(約 123.6M 參數會更新,其中主要是前兩個全連接層)。

這就是選項 B 講的:for param in model.features.parameters(): param.requires_grad = False

技術版:PyTorch 遷移學習的 requires_grad 機制

中級考試大概率會考程式碼跟公式,所以這部分你還是要學。但如果現在學起來很痛苦,可以先跳過,等讀完其他題目回頭再來。

Step 1 純故事版(不出現程式碼)

想像 VGG16 是一個有 16 個部門的大公司。前 13 個部門(卷積層,features)是資深員工,他們已經在大公司學了 10 年,現在要換到動物園工作,這些技能(邊緣偵測、紋理分析)還是通用的,不需要重新訓練,讓他們「凍結在原本的知識狀態」。

後 3 個部門(全連接層,classifier)是分類決策部門,原本在分辨 1000 種物件,現在要改成辨識 10 種動物,這些人需要重新培訓(更新參數)。

PyTorch 的 requires_grad = False 就像在每個資深員工的胸牌上貼「免培訓」,梯度下降算法看到這個標籤就跳過不更新。

Step 2 中文 ↔ 程式碼對照
白話說法程式碼
凍結卷積層(features)for p in model.features.parameters(): p.requires_grad = False
換掉最後分類層model.classifier[6] = nn.Linear(4096, 10)
確認哪些參數需要訓練params = [p for p in model.parameters() if p.requires_grad]
只把可訓練參數傳給優化器optimizer = torch.optim.Adam(params, lr=1e-4)
凍結全部(選項 A 的錯誤)for p in model.parameters(): p.requires_grad = False
Step 3 符號角色表
requires_grad
PyTorch 張量的屬性,True 表示需要計算梯度(會被反向傳播更新),False 表示凍結不動
model.features
VGG16 的卷積部分(nn.Sequential),包含 13 個 Conv2d 和相關 ReLU、MaxPool
model.classifier
VGG16 的全連接部分(nn.Sequential),classifier[6] 是最後一層輸出層
model.parameters()
迭代模型所有參數(包含 features 和 classifier)
model.features.parameters()
只迭代 features 部分的參數,不含 classifier
Step 4 完整公式對應
選項 A 的問題:
  model.parameters() 包含所有 features + classifier 的參數
  都設 False 後,只有新換的 classifier[6] 可訓練
  classifier[0-5] 被凍結 → 訓練不充分

選項 B(正確):
  model.features.parameters() 只包含 13 層 Conv2d 的參數
  設 False 後,classifier[0-5] 和新的 classifier[6] 仍可訓練
  符合「凍結卷積層、只訓練全連接層」的需求

選項 C 的問題:
  model.classifier.parameters() 只包含全連接層的參數
  設 False → 全連接層被凍結,features 卻繼續更新 → 完全反了

選項 D 的問題:
  model.requires_grad = False 是設 Python 物件屬性,不是 nn.Parameter
  PyTorch 的反向傳播不看這個屬性,沒有效果
Step 5 自我複述
  1. requires_grad = False 和 torch.no_grad() 有什麼差異?各自用在什麼場景?
  2. 選項 A 和選項 B 有什麼差異?兩者訓練的參數數量各是多少?
  3. 遷移學習為什麼通常凍結卷積層而不是全連接層?
  4. 如果只想訓練最後兩層全連接層,程式碼應該怎麼改?
  5. 把 optimizer 只傳入 requires_grad=True 的參數,有什麼好處?
05 陷阱

為什麼其他選項是錯的

選項 A for param in model.parameters(): param.requires_grad = False

字面在說什麼:凍結模型全部參數,再換最後一層。

為什麼不對:model.parameters() 包含 features 和 classifier 的所有參數。全部凍結後,只有新換的 classifier[6](建立時預設 requires_grad=True)是可訓練的,classifier[0] 到 classifier[5](前兩個全連接層)都被凍結了。題目要求「只訓練 classifier」,但 A 選項實際上只訓練了最後一層,效果遠不如同時訓練整個 classifier 的選項 B。

誰會選錯:認為「凍結全部再換最後一層」等同於「只訓練最後一層」,沒注意到「只訓練全連接層」應該是指整個 classifier 模組,不只是最後一層。

選項 C for param in model.classifier.parameters(): param.requires_grad = False

字面在說什麼:凍結全連接層(classifier)的參數,再換最後一層。

為什麼不對:這完全把需求搞反了。凍結 classifier 讓全連接層無法更新,反而讓 features(卷積層)繼續接受梯度更新。題目說「凍結卷積層」,結果凍結的是全連接層。訓練出來的模型會破壞預訓練的特徵提取能力,分類效果很差。

誰會選錯:讀題時漏掉「凍結卷積層」這個關鍵,看到 classifier 就覺得「最後幾層才需要訓練」,倒過來操作了。

選項 D model.requires_grad = False

字面在說什麼:直接對 model 物件設定 requires_grad 屬性來凍結參數。

為什麼不對:model 是 nn.Module 的子類,model.requires_grad = False 只是在這個 Python 物件上新增一個名為 requires_grad 的屬性,對 PyTorch 的梯度計算完全沒有影響。PyTorch 的凍結機制必須針對每個 nn.Parameter 物件設定其 requires_grad 屬性,只有對 parameter.requires_grad = False 才有效。這是典型的「語法看起來合理但語意完全錯誤」。

誰會選錯:不熟悉 PyTorch 的 Parameter 機制,以為對 Module 物件設定 requires_grad 就能控制所有參數的梯度,混淆了 Module 和 Parameter 的區別。

06 變形

同個考點下次怎麼變形

變形 1 部分凍結

直覺:如果只想凍結 VGG16 前 7 層卷積(features 的前半段),只訓練後半段卷積加全連接層,程式碼怎麼寫?

答案:`for i, layer in enumerate(model.features): if i < 14: for p in layer.parameters(): p.requires_grad = False`。features 模組的索引 0-13 對應前 7 組操作(Conv+ReLU 各兩個),索引 14 之後繼續訓練。實際上常見的策略是只凍結前幾個 Block,讓後幾個 Block 和全連接層一起 fine-tune。

變形 2 確認凍結狀態

直覺:怎麼驗證選項 B 執行後,features 確實凍結了?

答案:`for name, param in model.named_parameters(): print(name, param.requires_grad)`。features 的參數應顯示 False,classifier 的參數應顯示 True(除了舊有的 classifier[6] 已被替換)。或用 `sum(p.numel() for p in model.parameters() if p.requires_grad)` 確認可訓練參數數量 ≈ 123.6M(不含 features 的 14.7M)。

變形 3 優化器設定

直覺:凍結 features 後,優化器應該怎麼設定才能節省記憶體和計算量?

答案:只把可訓練參數傳給優化器:`optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4)`。如果把凍結的參數也傳給優化器,反向傳播時仍然不會更新這些參數,但 Adam 仍會為它們儲存 m/v,浪費記憶體。正確做法是只傳需要更新的參數。

變形 4 requires_grad vs no_grad

直覺:requires_grad = False 和 with torch.no_grad() 有什麼差異?

答案:requires_grad = False 是「這個參數不需要梯度,訓練時跳過」,是持久設定;torch.no_grad() 是一個 context manager,關閉當前程式碼塊內所有張量的梯度計算,常用於推論時節省記憶體(不儲存計算圖)。前者控制「某些參數是否更新」,後者控制「整個前向傳播是否計算梯度」。

變形 5 换模型場景

直覺:ResNet-50 的遷移學習,要凍結所有卷積層,程式碼怎麼寫?

答案:ResNet-50 的結構不同,卷積層分散在多個 layer 中。常見做法是凍結除最後一層 fc 以外的所有參數:`for name, param in model.named_parameters(): if 'fc' not in name: param.requires_grad = False`,然後換 fc:`model.fc = nn.Linear(2048, num_classes)`。ResNet 只有一個全連接層,不像 VGG16 有 features/classifier 的清晰分組。

07 延伸

想再往下看,這 5 個

出處

iPAS 經濟部產業人才能力鑑定 ・ 114 年第二梯次 iPAS AI 應用規劃師 中級 科目三 機器學習技術與應用 第 45 題

查看官方原文 PDF