Like Share Discussion Bookmark Smile

J.J. Huang   2026-03-27   Python OpenCV 07.物件偵測與辨識篇   瀏覽次數:次   DMCA.com Protection Status

Python | OpenCV 遷移學習與微調原理

📚 前言

在上一篇 模型選擇與訓練 的章節導覽中,我們了解了整體訓練流程。
這一篇先從最重要的基礎概念開始:遷移學習與微調原理 (Transfer Learning & Fine-Tuning)

理解這些原理,才能在實際訓練時做出正確的決策,例如「要凍結幾層?」、「學習率要設多少?」、「為什麼我的模型沒有收斂?」

💡 新手提示:這篇的程式碼需要基本的 PyTorch 知識。如果你對 PyTorch 還不熟悉,這篇先著重理解概念即可,不需要每行程式碼都看懂。概念通了,再回來看程式碼會容易很多。

🔎 什麼是遷移學習?

先用一個生活比喻來理解:

想像你請了一位已在頂級餐廳工作十年的廚師。他已經掌握了刀工、火候、調味等基本功。你不需要重新教他這些基礎,只需要讓他學習你餐廳的幾道特色料理即可。

深度學習的「遷移學習」就是這個概念。

預訓練模型(如 ResNet、MobileNet)是在 ImageNet 這類包含 120 萬張圖片的大型資料集上訓練而成的。
在訓練過程中,模型已經學會了豐富的視覺特徵:

1
2
3
淺層 → 學習邊緣、顏色、紋理等低階特徵(像廚師的刀工)
中層 → 學習形狀、輪廓、局部結構等中階特徵(像廚師的火候)
深層 → 學習物件部位、語意等高階特徵(像廚師對食材的判斷)

遷移學習的核心想法是:這些已經學好的特徵是通用的,不需要重新學習。
我們只需要在模型最後加上針對自己任務的分類頭,讓模型學習「如何用這些特徵來判斷我的類別」。

💡 什麼是「分類頭」? 模型的最後一層,負責把前面提取到的特徵轉換成「這張圖是貓還是狗」的判斷。預訓練模型的分類頭是針對 ImageNet 1000 個類別設計的,我們需要換成自己的類別數。

🧠 Feature Extraction vs Fine-Tuning

遷移學習有兩種主要策略,選擇哪一種取決於你的資料量與任務相似度:

策略 做法 適用情境
Feature Extraction 凍結所有預訓練層,只訓練新的分類頭 資料量少(每類 < 200 張)、任務與 ImageNet 相似
Fine-Tuning 凍結前幾層,解凍後幾層一起訓練 資料量中等、需要更高準確度
Full Training 解凍所有層一起訓練 資料量充足(萬張以上)、任務與 ImageNet 差異大
1
2
3
4
5
6
7
Feature Extraction:
[Conv1][Conv2]...[ConvN] → [新分類頭]
全部凍結 (requires_grad=False) 只訓練這裡

Fine-Tuning:
[Conv1][Conv2] | [Conv3]...[ConvN] → [新分類頭]
凍結前幾層 解凍後幾層一起訓練 一起訓練

🔎 預訓練模型的分類頭結構

以 ResNet18 為例,最後一層 fc(Fully Connected)就是分類頭:

1
2
3
4
5
6
7
# check_model_head.py
import torchvision.models as models

model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
print(model.fc)
# Linear(in_features=512, out_features=1000, bias=True)
# → in_features=512 是特徵維度,out_features=1000 是 ImageNet 的類別數


圖:載入 ResNet18 預訓練模型並印出最後一層 fc 的結構,確認輸入特徵維度

我們要把 out_features=1000 換成自己的類別數:

1
2
3
4
5
6
7
8
9
# replace_fc.py
import torch.nn as nn
import torchvision.models as models

num_classes = 3 # 例如:貓、狗、鳥
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
model.fc = nn.Linear(model.fc.in_features, num_classes)
print(model.fc)
# → Linear(in_features=512, out_features=3, bias=True)


圖:將 ResNet18 的分類頭替換為自訂類別數的線性層

以 MobileNetV2 為例,分類頭在 classifier[-1]

1
2
3
4
5
6
7
8
9
10
11
12
# replace_mobilenet_head.py
import torch.nn as nn
from torchvision.models import mobilenet_v2, MobileNet_V2_Weights

num_classes = 3 # 例如:貓、狗、鳥
model = mobilenet_v2(weights=MobileNet_V2_Weights.DEFAULT)
print(model.classifier[-1])
# Linear(in_features=1280, out_features=1000, bias=True)

model.classifier[-1] = nn.Linear(model.last_channel, num_classes)
print(model.classifier[-1])
# → Linear(in_features=1280, out_features=3, bias=True)


圖:載入 MobileNetV2 預訓練模型並替換 classifier[-1] 為自訂類別數的線性層

💻 凍結策略的實作

💡 什麼是「凍結」? 告訴模型在訓練時不要修改這些層的參數,保留預訓練時學到的特徵。在 PyTorch 中,把 requires_grad 設為 False 即可讓該層在反向傳播時被略過,不更新權重。

全部凍結(Feature Extraction)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# feature_extraction.py
import torchvision.models as models
import torch.nn as nn

num_classes = 3 # 自訂類別數,例如:貓、狗、鳥

model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)

# 凍結所有層(requires_grad=False → 訓練時不更新這些參數)
for param in model.parameters():
param.requires_grad = False

# 替換分類頭(新建的層預設 requires_grad=True,會被訓練)
model.fc = nn.Linear(model.fc.in_features, num_classes)

# 確認只有分類頭的參數會被訓練
# named_parameters() 會列出每一層的名稱與參數
trainable = [name for name, p in model.named_parameters() if p.requires_grad]
print(f"可訓練參數層:{trainable}")
# → ['fc.weight', 'fc.bias']


圖:凍結所有預訓練層並替換分類頭,確認只有 fc 層的參數可被訓練

解凍後幾層(Fine-Tuning)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# finetune.py
import torch
import torchvision.models as models
import torch.nn as nn

num_classes = 3

model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)

# 第一步:先全部凍結
for param in model.parameters():
param.requires_grad = False

# 第二步:解凍最後一個 layer block(layer4),讓它也參與訓練
for param in model.layer4.parameters():
param.requires_grad = True

# 第三步:替換分類頭
model.fc = nn.Linear(model.fc.in_features, num_classes)

# 為不同部位設定不同學習率
# layer4 是預訓練過的,學習率要小,避免破壞已有的特徵
# fc 是全新的,學習率可以大一點,讓它快速收斂
optimizer = torch.optim.Adam([
{"params": model.layer4.parameters(), "lr": 1e-4}, # 解凍層用較小學習率
{"params": model.fc.parameters(), "lr": 1e-3}, # 新分類頭用較大學習率
])

# 確認可訓練的參數層
trainable = [name for name, p in model.named_parameters() if p.requires_grad]
print(f"可訓練參數層:{trainable}")
# → ['layer4.0.conv1.weight', 'layer4.0.bn1.weight', ..., 'fc.weight', 'fc.bias']


圖:凍結前層後解凍 layer4 與分類頭,並為不同層設定不同學習率進行 Fine-Tuning

💡 解凍層數愈多,需要的資料量也愈多。建議先用 Feature Extraction 跑出基準結果,再逐步解凍更多層。

🧠 學習率的設定原則

遷移學習中學習率的設定非常關鍵:

訓練對象 建議學習率
只訓練分類頭 1e-3 ~ 1e-2
解凍後幾層 1e-4 ~ 1e-3
全部解凍 1e-5 ~ 1e-4

學習率過大會「破壞」預訓練權重學到的特徵,導致訓練不穩定甚至發散。

🔎 如何判斷訓練是否正常?

現象 可能原因 解決方式
Loss 下降但 Val Loss 上升 過擬合 資料增強、Dropout、減少解凍層數
Loss 幾乎不下降 學習率太小或太大 調整學習率,檢查資料前處理
Val Acc 很低但 Train Acc 高 資料分布不一致 確認訓練集與驗證集的前處理一致
Loss 為 NaN 學習率過大或資料有問題 大幅降低學習率,檢查資料是否有異常值

⚠️ 注意事項

  • 前處理必須與預訓練模型一致:使用 torchvision 預訓練模型時,圖片必須以相同的均值與標準差正規化([0.485, 0.456, 0.406] / [0.229, 0.224, 0.225]),否則特徵提取的效果會大打折扣。
  • Batch Normalization 的行為:凍結層中的 BatchNorm 在 model.eval() 時使用訓練時的統計量,而非當前 batch 的統計量,訓練時需注意呼叫 model.train()model.eval() 的時機。
  • 類別不均衡:若各類別的訓練資料數量差異大,可在 Loss 中加入 weight 參數平衡影響。

🎯 結語

理解遷移學習的原理,是做出正確訓練決策的基礎。
掌握「凍結幾層」、「學習率設多少」這兩個關鍵之後,就能有效率地微調模型,而不是靠試錯。

下一步將進入實際的 PyTorch 微調範例,把這些原理付諸實作。

📖 如在學習過程中遇到疑問,或是想了解更多相關主題,建議回顧一下 Python | OpenCV 系列導讀,掌握完整的章節目錄,方便快速找到你需要的內容。

註:以上參考了
PyTorch 官方文件 — Transfer Learning Tutorial
PyTorch 官方文件 — torchvision.models
CS231n — Transfer Learning