📢 公告:OpenCV 系列文章重構完成(75%)。專案實作篇仍在製作中,完成時間未定,敬請期待!→ 查看文章索引

熱門系列
Like Share Discussion Bookmark Smile

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

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

📚 前言

在上一篇 模型選擇與訓練 的章節導覽中,我們了解了整體訓練流程。
這一篇先從最根本的問題開始:深度學習模型到底長什麼樣子?它是怎麼運作的?

只有先搞清楚模型的結構與運作原理,才能理解「遷移學習」到底在做什麼,以及為什麼這樣做。

🧠 深度學習模型長什麼樣?

📌 模型的基本結構

一個深度學習模型可以想像成一條流水線:資料進去,一層一層處理,最後輸出結果。


圖:模型的基本結構 ─ 從輸入圖片開始,經過多層卷積層逐步提取特徵,最後由分類頭輸出結果

📌 每一層在做什麼?

越前面的層,學到的是越基礎、越通用的特徵;越後面的層,學到的是越複雜、越針對特定任務的特徵。


圖:模型每一層在學習什麼? ─ 淺層學習邊緣與顏色,中層學習形狀與紋理,深層學習物件語意

📌 什麼是「分類頭」?

分類頭就是模型的最後一層,它的工作是:拿到前面所有層提取到的特徵,輸出「這張圖屬於哪個類別」的答案。

例如一個在 ImageNet 上訓練的模型,分類頭的輸出是 1000 個類別的機率,因為 ImageNet 有 1000 種物件。

📌 「訓練」在做什麼?

訓練的本質是:不斷調整每一層的參數(權重),讓模型的輸出跟正確答案越來越接近。

1
2
3
4
5
6
給模型一張貓的圖片
→ 模型說「我覺得是狗(60%)、貓(40%)」
→ 跟正確答案比較,計算差距(Loss)
→ 根據差距反向調整每層的參數
→ 反覆進行幾千、幾萬次
→ 模型慢慢學會正確判斷

訓練需要大量的標註資料時間計算資源(GPU)

📊 不同的學習方式有哪些?

既然訓練這麼耗資源,有沒有更聰明的方法?答案是有的。根據起點不同,有以下幾種學習方式:


圖:常見的模型學習方式比較 ─ 從零訓練、特徵提取、微調、Zero-shot 的資料需求與適用情境

🔎 什麼是遷移學習?

生活比喻

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

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

預訓練模型(如 ResNet、MobileNet)是在 ImageNet 這類包含 120 萬張圖片的大型資料集上訓練而成的。它的前幾層已經學會了豐富的通用視覺特徵:邊緣、紋理、形狀……

遷移學習的核心做法

  1. 拿一個已經訓練好的模型
  2. 把最後的分類頭換成自己任務的類別
  3. 讓模型在自己的資料上繼續學習(而不是從頭學起)

這樣前面幾層「刀工、火候」的基本功就不需要重學,只需要讓模型學會「你餐廳的特色料理」。

遷移學習的三種策略

依照你的資料量與需求,遷移學習有三種做法:


圖:遷移學習的三種策略比較 ─ 特徵提取、微調、全面訓練的適用情境與差異

⚠ 為什麼要凍結前幾層?因為前幾層學到的「邊緣、紋理」是通用的基礎特徵,不需要因為換了任務而重新學習,貿然更動反而可能破壞這些已學好的知識。

如何選擇策略?

1
2
3
4
5
6
7
8
9
10
我的每類資料大約有幾張?

< 200 張
└─→ Feature Extraction(只訓練分類頭)

200 ~ 2000 張
└─→ Fine-Tuning(解凍後幾層 + 分類頭)

> 2000 張,且任務與一般圖片差很多(如醫療影像、衛星圖)
└─→ 考慮 Full Training(解凍全部層,語法同 Fine-Tuning,移除凍結迴圈即可,需要較多 GPU 資源)

💡 新手建議:不確定時,先從 Feature Extraction 開始。確認流程跑通後,再試 Fine-Tuning 看準確度是否提升。

💻 實作一:查看與替換分類頭

開始動手前,先看清楚模型的分類頭長什麼樣子。


圖:如何替換分類頭 ─ 以 ResNet18(model.fc)和 MobileNetV2(model.classifier[-1])為例,並列出其他常見模型的分類頭位置

🔸 以 ResNet18 為例:

最後一層 fc(Fully Connected)就是分類頭:

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

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

輸出

1
2
Linear(in_features=512, out_features=1000, bias=True)
# → in_features=512 是特徵維度,out_features=1000 是 ImageNet 的類別數

out_features=1000 換成自己的類別數:

1
2
3
4
5
6
7
8
# 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)

輸出

1
Linear(in_features=512, out_features=3, bias=True)


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

🔸 MobileNetV2

分類頭位置不同,在 classifier[-1]

1
2
3
4
5
6
7
8
9
10
# 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])

model.classifier[-1] = nn.Linear(model.last_channel, num_classes)
print(model.classifier[-1])

輸出

1
2
Linear(in_features=1280, out_features=1000, bias=True)
Linear(in_features=1280, out_features=3, bias=True)


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

💻 實作二:凍結策略

Feature Extraction(全部凍結,只訓練分類頭)


圖:Feature Extraction(特徵提取)示意圖 ─ 預訓練層全部凍結,只有分類頭會被訓練

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# feature_extraction.py
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

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

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


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

Fine-Tuning(解凍後幾層 + 分類頭)


圖:Fine-Tuning 微調策略 ─ 大部分層凍結,只解凍 Layer4 並替換分類頭,使用不同學習率訓練

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
# 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', ..., 'fc.weight', 'fc.bias']


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

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

🧠 學習率的設定原則

學習率決定每次更新參數的幅度。幅度太大會破壞預訓練層已學好的特徵,幅度太小則收斂緩慢。


圖:學習率設定原則 ─ 不同訓練階段建議使用的學習率範圍與原因

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


圖:如何判斷訓練是否正常? ─ 常見現象、可能原因與解決方式(附 Loss 與 Val Loss 白話說明)

⚠️ 注意事項

  • 不要一開始就解凍太多層:解凍的層數愈多,模型需要的訓練資料量也愈多。資料不足時解凍太多層,反而會把已學好的特徵破壞掉,準確率不升反降。建議先從 Feature Extraction 跑出基準結果,再逐步嘗試解凍。
  • 預訓練層的學習率必須比分類頭小:分類頭是全新的層,需要較大的學習率快速收斂;預訓練層已有學好的特徵,學習率過大會把這些特徵更新掉。Fine-Tuning 時請務必為兩者設定不同的學習率(參考上方 optimizer 的寫法)。

🎯 結語

這篇從模型的基本結構出發,說明分類頭的作用與替換方式,再介紹遷移學習的三種策略以及如何根據資料量做選擇。
把握「資料量決定策略、預訓練層學習率要小」這兩個原則,就能在不同情境下正確套用遷移學習。

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

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

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