第一堂課 / 共四堂
Vision 101

電腦怎麼看圖?
從 CNN 到 YOLO 的奇幻旅程

這是一本互動筆記本。十四個主題、多個互動玩具,慢慢操作就能搞懂這些模型在做什麼。

120 min 📚 14 個互動 demo · 🎯 從基礎到 YOLOv8 · ✏️ 作業:做一個自己的 YOLO
! 看到 紅色波浪底線的字 就把滑鼠移上去,有詳細解釋跟外部連結。慢慢來,不用一次看懂。

開場:為什麼需要 CNN?

如果你今天要寫一個程式,告訴電腦「這張圖裡有沒有貓」,你會怎麼寫?

直覺做法?
「找到尖尖的耳朵 → 貓!」但尖耳朵的東西太多了:狐狸、兔子、紙做的角…
顏色判斷?
「橘色毛 → 貓!」那黑貓呢?那橘色的枕頭呢?人寫規則,永遠寫不完。
Template Matching?
存一張「標準貓」去比對?但貓有各種姿勢、各種距離、各種品種…
答案:讓機器自己學!
給大量「有貓 / 沒貓」的圖片,讓模型自己學出什麼特徵代表貓。這就是 CNN。
傳統方法 vs Deep Learning
❌ 傳統方法

人類手動設計特徵 (SIFT, HOG, Haar) → 每換一個任務就要重新設計 → 永遠有 edge case → 規則越多越脆弱

✓ CNN / Deep Learning

模型自動學習特徵 (end-to-end) → 只要有足夠的資料 → 特徵會自動變得越來越抽象 → 第 1 層學邊緣 → 中間學紋理 → 最後學物件

CNN 為什麼是答案?

1. 局部連接 (Local Connectivity) — 不需要看完整張圖
人類看圖也是先看局部:先注意到「尖尖的形狀」、「毛茸茸的紋理」、「圓圓的眼睛」。 CNN 的 kernel 也是一小塊一小塊地看,非常符合圖片的性質。比起 FC 把所有像素混在一起,Conv 保留了「空間關係」。
2. 權重共享 (Weight Sharing) — 一個偵測器用遍全圖
「貓的眼睛」不管出現在圖的左上角還是右下角,都應該被同一個偵測器抓到。 CNN 的同一個 kernel 滑過整張圖 = 相當於共享權重 = 大幅減少參數量。 一張 224×224 的圖,如果用 FC 需要 5000 萬參數;用 Conv 3×3 只需要 9 個。
3. 階層式特徵 (Hierarchical Features) — 從簡單到複雜
多層 Conv 堆疊後:第一層學到邊緣和顏色,第二層組合出紋理和角,第三層以後逐漸出現「耳朵」「眼睛」等部件,最深層才是「貓」這個完整概念。這跟人類視覺皮層 V1→V2→V4→IT 的處理過程驚人地相似。
4. 平移不變性 (Translation Invariance) — 貓在哪都是貓
因為權重共享加上 pooling,CNN 天生具有一定的平移不變性。貓移到圖片另一邊,模型照樣認得出來。 這是 FC 完全做不到的 — FC 必須從零學習「同一個 pattern 出現在不同位置」的所有情況。
本堂課目標
從最基本的卷積開始,一路走到YOLO 物件偵測。 每一個概念都有互動 demo 可以玩。重點不是背公式,而是建立直覺

點上面的 Tab 開始探索吧!建議順序就是從左到右。
小測驗

CNN 相比 FC (全連接層) 最大的優勢是什麼?

精度永遠比較高
參數量大幅減少,利用了圖片的空間結構
訓練速度一定更快
不需要任何標記資料

卷積遊樂場

自己拖一個 3×3 kernel 的數值,看它對圖片做了什麼。 CNN 第一層就是在做這件事,只是 kernel 的值是模型自己學出來的。 新增了 stride 和 padding 可以調,還有 receptive field 動畫。

什麼是卷積?
一個小窗戶在圖上滑,每滑一次做一次點積運算,輸出一張 feature map。
為什麼是 3×3?
VGG 論文證明兩個 3×3 等於一個 5×5,但參數更少。3×3 從此成為共識。
什麼是 feature map?
每個 kernel 滑完整張圖後產生的 2D 矩陣,代表「某種特徵在哪裡出現」
Padding & Stride
Padding 是邊緣補幾圈 0,stride 是一次滑幾格。下面可以調看看。
Stride 步長1
每次滑幾格;stride=2 → 輸出尺寸減半
Padding 補邊1
邊緣補幾圈 0;padding=1 + 3×3 + stride=1 → 尺寸不變
顯示 Receptive Field
看 kernel 怎麼滑過整張圖
Input · Kernel
3×3 Kernel(可以改數字)
Output Feature Map
Input
200 × 200
Output
200 × 200
Kernel
3 × 3
Params
9 + 1
PyTorch:
nn.Conv2d(1, 1, kernel_size=3, stride=1, padding=1)

Receptive Field 計算器

堆疊層看感受野怎麼長大

加層看 receptive field 怎麼成長。公式: RFnew = RFold + (kernel − 1) × stride_product

Current RF
1 × 1
Stride Product
1
Layers
0
紅色區域 = 輸出一個 pixel 能「看到」的原圖範圍
為什麼這個很重要?
Sobel 是經典邊緣偵測器,人類花了幾十年才想出來。但 CNN 訓練幾個 epoch 後, 第一層的 kernel 自己就會長得很像 Sobel。 這就是 deep learning 的魔法 — 把 feature engineering 交給模型自己做。

池化的真相

Pooling(池化)是 CNN 裡很常見的操作,但很多人只會背「會降低尺寸」,不知道為什麼。 下面動手調 window 看 MaxAvgGAP 的差別。

為什麼要 Pool?
三個原因:減少計算量、增加 receptive field、提供平移不變性。
Max Pooling
取窗戶內最大值。最常用,因為保留最強特徵。「有偵測到 = 訊號強 = 數值大」。
Avg Pooling
取平均。比較少用於分類,但用在 GAP(全部平均成一個值)取代 FC 層。
趨勢
現代很多模型用 stride=2 conv 取代 pooling — 一樣下採樣但有可學參數。
Pool Type
不同 pool 對相同資料會輸出不同結果
Window Size2 × 2
2×2 是最常用的設定
Input(滑鼠移上看哪些 cell 在同一個 window)
黃色 = 當前 window · 紅色 = 被選中的 max
Output
Input shape
6 × 6
Output shape
3 × 3
Operation
Max
Params
0
PyTorch:
nn.MaxPool2d(2, 2)

三種 Pool 的視覺對比

同一張 feature map,經過三種 pool 後輸出的差別:

三種 pool 在做什麼
Max pool 會保留最尖銳的特徵(像把音量調到只聽最大聲那一刻)。 Avg pool 比較平滑,不會強調極值。 GAP 直接把整張圖壓成一個值,通常用在最後一層當分類前的「打包」。
小測驗

為什麼現代架構傾向用 stride=2 conv 取代 Max Pooling?

因為 Max Pooling 會讓圖片模糊
stride=2 conv 有可學參數,讓模型自己決定怎麼下採樣
因為 Max Pooling 計算量太大
stride=2 conv 不會減少尺寸

激活函數動物園

神經網路一定要有激活函數(activation function),不然多少層都跟一層一樣 (因為線性的疊加還是線性)。下面看每個 activation 長什麼樣子、套用在實際資料上會發生什麼。

為什麼要非線性?
沒有非線性,N 層 linear 化簡後跟 1 層 linear 一樣。神經網路的能力來自非線性的疊加
ReLU 為什麼紅
計算超快(就是 if x<0 return 0),不會像 sigmoid 那樣飽和導致梯度消失。
Dying ReLU
ReLU 負半邊梯度為 0,如果某個 neuron 一直輸出負值就「死了」。Leaky ReLU 解決這個。
現代趨勢
YOLOv4 用 Mish,YOLOv5+、Transformer 用 SiLU/Swish。都是 ReLU 的平滑版本。
選擇激活函數(可以多選比較)
函數曲線(input → output)
x 軸 = input · y 軸 = output · 滑鼠移上去看數值
梯度曲線(d/dx output)
這就是 backward 時會用到的東西
梯度 ≈ 0 = 訓練不動了

套用在 feature map 上

Input(模擬 Conv 輸出)
After ReLU
💡 ReLU 把所有負值砍成 0,你會看到輸出的「黑色區域」變多 — 那些就是「死掉」的 neuron。
怎麼選 activation
新模型多半從 SiLU 或 GELU 開始(YOLOv5+, Transformer 都用)。 老模型用 ReLU 沒問題,簡單快速。
碰到 dying ReLU 改 Leaky ReLU
Sigmoid / Tanh 現在只用在輸出層(分類機率、座標限制),很少當隱藏層 activation 了。
小測驗

為什麼現代模型不再用 Sigmoid 當隱藏層的 activation?

因為計算太慢
因為會飽和,導致梯度消失
因為輸出不是 0~1
因為不能微分

Batch Normalization

Batch Normalization 是現代 CNN 標配,但很多人講不清楚它在做什麼。 簡單說:把每一層的輸出分布「拉回來」,讓訓練更穩定。 下面實際看一看資料分布怎麼被改變。

公式
y = γ × (x − μ) / σ + β,把分布壓成 mean=0、std=1,再用 γ、β 去學適合的分布。
為什麼有用
讓每層收到的 input 分布穩定,可以用更大的 learning rate,訓練快很多。
Train vs Eval
新手雷:Train 用當前 batch 的 μ、σ,Eval 用訓練時的移動平均。忘記切換會慘。
放在哪
通常順序:Conv → BN → ReLU。有 BN 之後幾乎不用 Dropout 了。
原始分布的偏移 (shift)0.0
模擬某層 conv 輸出的 mean 偏離 0
原始分布的尺度 (scale)1.0
模擬某層 conv 輸出的 std 偏離 1
學到的 γ(scale)1.0
BN 之後再 scale 多少
學到的 β(shift)0.0
BN 之後再 shift 多少
原始分布(BN 之前)
Mean
0.0
Std
1.0
BN 後分布
Mean
0.0
Std
1.0
為什麼要有 γ 和 β
只把分布壓到 mean=0, std=1 太死板,可能反而削弱模型表達力。所以 BN 多加兩個可學參數 γ, β, 讓模型自己決定「這層的 activation 適合哪種分布」 — 等於最佳化標準化的程度。

FC vs Conv

你可能會問:既然全連接層 (FC)能擬合任何函數,為什麼還要 Conv? 答案是參數量。下面動態算給你看,有圖有真相。

FC 是什麼
每個輸入都連到每個輸出,參數量 = input × output。彈性最大,但...也最貴。
Conv 為什麼省
同一個 kernel 在整張圖共用權重。一個 3×3 kernel = 9 個參數,不管圖多大。
Inductive Bias
Conv 內建「相鄰像素相關、特徵位置不變」的假設。FC 完全沒有,要從頭學起。
現代用法
Conv 提取特徵 → GAP 壓縮 → FC 做最後分類。FC 通常只剩最後一層。
輸入圖片尺寸224 × 224
越大張參數爆炸越誇張
輸出 channel(下一層神經元)1024
FC 是 neuron 數;Conv 是 filter 數
參數量大比拼
FC 全連接
Conv 3×3
Conv 1×1
FC 參數量
Conv 3×3 參數量
Conv 1×1 參數量
💀 FC 比 Conv 多 倍參數!所以根本不用考慮直接接 FC。

為什麼 Conv 可以共用權重

FC 的世界觀

把整張圖攤平成一條線。左上角的像素跟右下角的像素「平等」 — 這完全違背圖片的本質 (相鄰像素其實相關性很高)。所以 FC 要從零學起「什麼是空間關係」。

Conv 的世界觀

「貓的眼睛」這種 pattern 不管出現在圖的哪個位置,都應該被同一個偵測器抓到。 所以一個 kernel 滑遍整張圖 = 共用權重 = 大幅減少參數。

參數量的概念
FC 隨輸入尺寸線性增長 — 224×224 比 32×32 多 49 倍參數。 Conv 跟輸入尺寸無關 — 一個 3×3 kernel 永遠 9 個參數,不管圖多大。 這就是為什麼現代視覺模型幾乎全是 Conv,FC 只剩最後一層當分類器。

梯度消失現場

拉動下面的 slider 改變網路深度,看左右兩種架構的梯度怎麼變化。 亮 = 梯度大(學得到), 暗 = 梯度接近 0(學不到)。 還可以按 ⏵ 看反向傳播的動畫。

梯度是什麼
告訴模型「往哪個方向調參數能讓 loss 變小」。沒有梯度 = 不知道怎麼學。
為什麼會消失
梯度從輸出層往輸入層傳時,每經過一層就乘一次偏導數。如果都小於 1,深層網路前幾層的梯度會指數衰減到 0。
ResNet 怎麼解
skip connection 讓梯度有捷徑可走,backward 時不會被中間層稀釋掉。
影響有多大
原本 30 層就訓練不起來,有 ResNet 後可以堆到 152 層、甚至 1000 層還能訓練。
網路深度20 layers
越深問題越明顯
每層的梯度因子0.50
每層的偏導數平均大小;越小衰減越快
反向傳播動畫
看梯度從輸出層怎麼一路衰減
Plain CNN(沒有 skip connection)
← input 前面 output 後面 →
第一層梯度
有效層數
※ 有效層 = 梯度 > 0.01 的層
ResNet(有 skip connection)
← input 前面 output 後面 →
第一層梯度
有效層數
※ 每個 residual block 都有 +1 的 identity path
數學直覺
梯度 ≈ factor^depth。當 factor < 1 且 depth 大,結果指數衰減到 0。
ResNet 的 skip connection 讓反向傳播多一條 +1 的路徑,等於 (factor + 1)^depth。 這就是為什麼可以蓋到 152 層還訓練得起來。

殘差學習現場

殘差學習是 ResNet 的核心發明。 下面動態看 F(x)、x、F(x)+x 三個訊號的關係,以及為什麼這條 skip connection 能解決梯度消失。

原本的學法
直接學 H(x) — 從 input 學到 output 該長什麼樣。難。
殘差的學法
學 F(x) = H(x) − x — 只學「需要在 x 上加什麼東西」。簡單多了。
退化解
如果這層沒用,模型可以把 F(x) 學成 0,等於 identity。不會比原本差
梯度高速公路
∂(F+x)/∂x = ∂F/∂x + 1。那個 +1 保證梯度永遠有東西傳回去。
F(x) 學到的「殘差」幅度0.5
模擬 conv block 學到的調整量
F(x) 的相位偏移0
調整 F(x) 的形狀
三條訊號的疊加
input x F(x) F(x) + x
Residual Block 結構
x (input) Conv 3×3 BN + ReLU Conv 3×3 BN skip connection + ReLU F(x)

梯度視覺化:傳統 vs Residual

想像 backward 時梯度從輸出層往前傳。傳統 CNN 每經過一層就衰減,ResNet 因為有 +1 的 identity path,梯度可以直接「跳過」中間層。

為什麼這個發明這麼重要
ResNet 之前,網路深度被卡在 30 層左右(再深 train loss 反而變高)。 ResNet 之後,深度不再是問題。Transformer、Diffusion、所有 LLM 也都用這個技巧。 可以說 deep learning 從 2015 年之後的發展都是建立在這條看似簡單的 +x 路徑上。

CNN 演進史

從 1998 年的 LeNet 到現代的 ConvNeXt,CNN 的發展像一場接力賽。每一代解決了上一代的痛點。

1998
LeNet-5 (Yann LeCun)
第一個成功的 CNN,用來辨識手寫數字。結構簡單:2 個 conv + 2 個 pool + 3 個 FC。證明了 CNN 可以學到有用的 feature。
2012
AlexNet (Alex Krizhevsky)
ImageNet 冠軍,error rate 從 26% 降到 16%。第一次用 GPU 訓練大型 CNN。引入 ReLU、Dropout、Data Augmentation。深度學習的「iPhone moment」。
2014
VGGNet (Simonyan & Zisserman)
證明「深度」很重要。統一用 3×3 kernel(兩個 3×3 = 一個 5×5 的感受野,參數更少)。VGG-16 / VGG-19 至今還是常用的 backbone。
2014
GoogLeNet / Inception
引入 Inception module:同時用 1×1、3×3、5×5 conv 平行處理,再 concat。用 1×1 conv 做 channel 壓縮,大幅減少計算量。22 層但參數只有 AlexNet 的 1/12。
2015
ResNet (Kaiming He)
Skip connection 解決梯度消失。152 層!Error rate 3.57%,超越人類(5.1%)。影響至今:所有現代模型都用 residual connection。
2017
DenseNet / SENet / MobileNet
DenseNet:每層連到所有後面的層。SENet:channel attention。MobileNet:depth-wise separable conv 讓 CNN 跑在手機上。
2020
EfficientNet / Vision Transformer
EfficientNet:用 NAS 自動找最佳架構。ViT:把 Transformer 搬到 vision,證明 attention 也能做圖片分類。CNN vs Transformer 大戰開始。
2022
ConvNeXt (Meta)
「如果把 ResNet 用 Transformer 的設計哲學重新設計會怎樣?」結果:純 CNN 也能跟 ViT 打平!證明 CNN 還沒死,只是需要現代化。

重要發明速查

ReLU (2012, AlexNet) — 解決飽和問題
在 AlexNet 之前,大家用 sigmoid / tanh。這些函數在輸入很大或很小時梯度趨近 0(飽和),深層網路訓練不起來。 ReLU: f(x) = max(0, x),正半邊梯度永遠 = 1,計算快 6 倍。從此成為默認 activation。
Batch Normalization (2015) — 加速收斂
Ioffe & Szegedy 發現:每層的 input 分布會隨訓練改變(Internal Covariate Shift)。 BN 把每層輸出標準化到 mean=0, std=1,讓下一層收到穩定的 input。效果:可以用更大 LR,訓練快 14 倍。
Skip Connection (2015, ResNet) — 深度不再是問題
核心: y = F(x) + x。多了一條 identity path,梯度可以直接流到前面的層。 沒有 ResNet 之前,超過 30 層 train loss 反而上升;有了之後可以 152 層、1000 層。 現在幾乎所有深度模型(包括 Transformer)都用這個技巧。
Depthwise Separable Conv (2017, MobileNet) — 手機也能跑
標準 Conv: 每個 output channel 的 kernel 都是 (Cin × K × K)。 Depthwise Separable: 先對每個 channel 獨立做 K×K conv,再用 1×1 conv 做跨 channel 混合。 計算量降低 8~9 倍,品質只掉一點點。MobileNet / EfficientNet / YOLOv5 都大量使用。
Channel Attention (2018, SENet) — 讓模型自己選 channel
不是每個 channel(feature map)都一樣重要。SENet 用 GAP + FC + sigmoid 算出每個 channel 的「重要性權重」,再乘回去。 等於讓模型自己學「這張圖應該多注意哪些特徵」。後來的 ECA、CBAM 都是這個思路的延伸。
為什麼要知道演進?
現代模型不是憑空出現的,每一個設計都是為了解決某個問題。看懂演進,你就能理解為什麼 YOLO 的 backbone 長那樣,也能預測未來的趨勢。
小測驗

ResNet 的 skip connection 主要解決了什麼問題?

過擬合 (Overfitting)
梯度消失,讓深層網路可以訓練
計算量太大
訓練資料不足

YOLO 怎麼看圖

YOLO 怎麼把圖切成 grid,以及 anchor-based vs anchor-free 的差別。 點擊任一格子,可以看到該格子負責預測哪些 box。

Detection 是什麼
不只說「圖裡有貓」,還要說「貓在這個位置,框出來」。比分類難很多。
為什麼用 grid
YOLO 把圖切 SxS 格,每格「負責」預測中心落在它裡面的物件。
什麼是 anchor
預先定義的 box 形狀(瘦高、寬扁、正方形)。用 K-means 算出來。
Anchor-free 的勝利
YOLOv8 改回 anchor-free,簡化 pipeline,不用為每個資料集設計 anchor。
Grid Size (S × S)7 × 7
越細越能偵測小物件,計算量越大
Anchors per cell3
YOLOv3 用 3,YOLOv2 用 5
顯示 NMS 視覺化
NMS 把重複的 box 去掉
Image · Grid · Anchors
橘色框 = ground truth · 綠色高亮 = 負責的格子 · 藍色 = 你選的格子
Output Tensor 結構
Grid: 7 × 7
Anchors per cell: 3
Per-anchor outputs: 5 + 20 = 25
Output shape:
(B, 7, 7, 75)
B = batch size · 75 = 3 anchors × (5 + 20)
5 = (x, y, w, h, confidence) · 20 = 類別數
Selected cell info
點擊左邊任一格子來看 detail

IoU 跟 NMS 是什麼?

IoU 視覺化(拖預測框)
IoU
Status
拖藍色預測框,看跟橘色 ground truth 的 IoU。IoU > 0.5 算 match。
NMS 互動模擬

模型常會對同一物件預測很多個 box(因為附近的格子都會輸出)。NMS (Non-Maximum Suppression) 一步步看:

Kept
Eliminated
Examining
Pending
IoU Threshold0.5
點 Step 或 Auto Play 開始 NMS 流程。
為什麼 anchor-free 變主流
Anchor-based 需要先用 K-means 對訓練集算出最佳 anchor 形狀, 不同任務的最佳 anchor 完全不同(行人偵測 vs 衛星照片偵測 vs 人臉偵測)。 Anchor-free 把這負擔丟給模型自己學,簡化 pipeline。YOLOv8 之後幾乎都改用這個。

YOLO 演進全紀錄

YOLO 系列從 2016 的 v1 到 2023 的 v8,八代演進各有突破。下面用表格 + 手風琴逐一介紹。

版本比較總覽

Version Year Backbone Key Innovation Grid Anchor mAP (COCO)
v12016自定義 24-layerOne-stage 先驅7×763.4 (VOC)
v22017Darknet-19Anchor boxes + BN13×13548.1
v32018Darknet-53Multi-scale (FPN-like)13/26/529 (3×3)55.3
v42020CSPDarknet-53BoF + BoS + CIoU多尺度965.7
v52020CSPDarknet (PyTorch)工業部署、5 sizes多尺度9~68
v62022EfficientRepRepVGG 重參數化多尺度Anchor-free~71
v72022E-ELANPlanned re-param多尺度Anchor-based56.8 AP
v82023C2f backboneDFL + TAL + Multi-task多尺度Anchor-free53.9 AP

各版本詳細解析

YOLOv1 (2016) — One-stage 偵測的開山之作

核心思想:把偵測問題轉成「回歸問題」,一次看完整張圖就預測所有物件。

架構:

  • 輸入: 448 × 448
  • 24 層 conv + 2 層 FC
  • 輸出: 7 × 7 × 30 tensor
  • 每個 cell 預測 2 個 box (x, y, w, h, conf) + 20 class probs

Loss 函數:MSE 形式,含有位置 loss、confidence loss、class loss,用 λ 平衡各項。

限制:

  • 每格只能偵測 1 個物件 → 密集物件漏偵測
  • 沒有 anchor → 對不同比例物件泛化差
  • 小物件偵測差(7×7 grid 太粗)

意義:證明 real-time detection 可行,45 FPS!

YOLOv2 / YOLO9000 (2017) — 七大改進

Backbone: Darknet-19 (19 conv + 5 maxpool)

七大改進表:

改進效果
Batch Normalization+2% mAP,可移除 Dropout
High Resolution Classifier先用 448×448 finetune backbone
Anchor Boxesrecall 從 81% → 88%
Dimension Clusters (K-means)找最佳 anchor 形狀
Direct Location Prediction穩定訓練,用 sigmoid 限制偏移
Fine-Grained Featurespassthrough layer,小物件偵測改善
Multi-Scale Training訓練時隨機切換輸入尺寸 (320~608)

Anchor 機制:用 K-means 在訓練集的 GT box 上跑,找出 5 個最有代表性的 (w, h) 比例。模型預測相對 anchor 的偏移量 (tx, ty, tw, th),而非絕對座標。

YOLOv3 (2018) — 多尺度預測登場

Backbone: Darknet-53(53 層,含 residual connection)

核心改進:

  • Multi-scale prediction: 在 3 個尺度做預測 (13×13, 26×26, 52×52)
  • 類似 FPN 的結構:深層 feature 上採樣後跟淺層 concat
  • 每個尺度 3 個 anchor → 共 9 個 anchor
  • 用 logistic (BCE) 取代 softmax → 支援 multi-label

尺度分配:

  • 13×13: 大物件 (anchor: 116×90, 156×198, 373×326)
  • 26×26: 中物件 (anchor: 30×61, 62×45, 59×119)
  • 52×52: 小物件 (anchor: 10×13, 16×30, 33×23)

效果:mAP-50 = 57.9%,FPS 跟 SSD 相當但精度更高。

YOLOv4 (2020) — Bag of Freebies & Specials

Backbone: CSPDarknet-53(Cross Stage Partial)

Neck: SPP + PANet (Path Aggregation Network)

Bag of Freebies (訓練技巧,不增加推論成本):

  • Mosaic Augmentation: 4 張圖拼成 1 張,大幅增加小物件樣本
  • CutMix: 剪貼不同圖片的區域
  • Label Smoothing: 防止 over-confident
  • DropBlock: 結構化 Dropout
  • CIoU Loss: 比 IoU 更好的 box loss,考慮中心距離和長寬比

Bag of Specials (推論時的額外計算):

  • Mish activation (比 ReLU 平滑)
  • CSP connections
  • SAM (Spatial Attention Module)
  • DIoU-NMS

效果:COCO AP = 43.5% @ 65 FPS (V100)。

YOLOv5 (2020) — PyTorch 生態系的勝利

開發者: Ultralytics (Glenn Jocher)

Backbone: CSPDarknet (PyTorch 重寫)

核心特色:

  • C3 Module: CSP Bottleneck with 3 convolutions
  • SPPF: 比 SPP 更快的空間金字塔池化
  • 5 種大小: n / s / m / l / x (3.2M ~ 86.7M params)
  • Auto Anchor: 自動計算最佳 anchor
  • 極致工程: Mixed precision, EMA, Cosine LR

為什麼紅:

  • PyTorch 原生,pip install 就能用
  • 一行指令訓練自己的資料集
  • TensorRT / ONNX / CoreML 一鍵匯出
  • 工業界大量採用(工廠瑕疵檢測、自駕、無人機)
YOLOv6 (2022, Meituan) — 結構重參數化

開發者: 美團 (Meituan)

Backbone: EfficientRep (基於 RepVGG)

核心創新:

  • Structural Reparameterization: 訓練時用多分支(3×3 + 1×1 + identity),推論時融合成一個 3×3 conv
  • Anchor-free: 直接預測距邊界的距離 (l, t, r, b)
  • Decoupled Head: 分類和回歸用分開的 head
  • SimOTA / TAL: 更好的正樣本分配策略

效果:YOLOv6-S 在 COCO 上 43.1% AP @ 520 FPS (T4)。

YOLOv7 (2022) — E-ELAN + Planned Re-param

開發者: 原 YOLOv4 團隊 (WongKinYiu)

核心創新:

  • E-ELAN: Extended-ELAN,不改 gradient path 但增加 learning capacity
  • Planned Re-parameterization: 根據網路結構選擇性地做重參數化
  • Auxiliary Head: 訓練時加輔助 head 提供更多監督訊號
  • Coarse-to-fine lead guided: 精細的 label assignment

效果:56.8% AP @ 30 FPS (V100),當時最強。

YOLOv8 (2023, Ultralytics) — 統一架構 + Multi-task

開發者: Ultralytics (YOLOv5 同團隊)

Backbone: 改良 CSPDarknet (C2f module)

核心創新:

  • C2f Module: C3 的改良版,更多 gradient flow
  • Anchor-free: 不需要預設 anchor,直接預測
  • DFL (Distribution Focal Loss): 把 box 回歸當作分布來預測
  • TAL (Task-Aligned Learning): 分類和定位的 loss 一起優化正樣本分配
  • Decoupled Head: 分類 / 回歸分開的輸出頭
  • Multi-task: 同一個架構支援 Detection / Segmentation / Classification / Pose

5 種尺寸: n (3.2M) / s (11.2M) / m (25.9M) / l (43.7M) / x (68.2M)

為什麼重要:Anchor-free 讓 pipeline 更簡單;多任務讓一個模型做多件事;Ultralytics 生態系讓使用門檻極低。

演進脈絡:每一代解決了什麼

v1 證明 one-stage 可行 → v2 加 anchor 解決多尺度 → v3 多尺度預測解決小物件 → v4 訓練技巧集大成 → v5 工程化部署 → v6 重參數化加速 → v7 架構設計新高 → v8 Anchor-free + Multi-task 統一框架
作業相關
你的作業是做一個自己的 YOLO 模型。不需要做到 v8 那麼複雜,但要理解基本原理: grid 切割 → anchor/anchor-free 預測 → loss 計算 → NMS 後處理。 建議從 v1 的概念開始,再加入 v3 的多尺度。

PyTorch 模型組裝

拖左邊的元件到中間組成一個 CNN,右邊會即時生成對應的 PyTorch code。 預設 input 是 (B, 3, 224, 224)。可以滑鼠移到 layer 名稱看每種 layer 在做什麼。

nn.Module
PyTorch 所有模型的基礎類別。繼承它就能管理參數、自動 backward
__init__ vs forward
__init__ 定義「有什麼零件」,forward 定義「怎麼組起來」。backward 完全自動
看 shape 變化
每加一層,看 channel 跟空間尺寸怎麼變。經典 pattern 是「channel 翻倍,空間減半」。
參數量 = 模型大小
下面會即時算總參數量,隨時看模型多大。Conv 比 FC 省非常多。
零件庫(可拖拉或點擊)
Conv2d 3×3
+padding=1, stride=1
Conv2d 3×3 ↓
stride=2 下採樣
BatchNorm2d
穩定訓練
ReLU
非線性激活
MaxPool 2×2
空間下採樣
GlobalAvgPool
壓成 1×1
Flatten
攤平成 1D
Linear (FC)
最後分類
Residual Block
含 skip connection
你的模型 · 每層 shape
把左邊的 layer 拖過來,或直接點擊
Total parameters 0
PyTorch Code(自動生成)
# 拖 layer 進來,code 會自動生成
觀察 shape 變化
每加一層,看 channel 數和空間尺寸怎麼變。經典 CNN 的 pattern 是「channel 翻倍,空間減半」 — 這樣每層的計算量大致守恆,而且高階特徵需要更多 channel 來表達。

試試看搭一個 Conv → BN → ReLU 的 block,然後加 MaxPool,觀察 shape 變化。 或直接點「Load ResNet-mini」看一個簡化版的 ResNet 長什麼樣。

Forward Pass 動畫

點 Play 看一筆資料如何從輸入一路經過 Conv → ReLU → Pool → FC 變成分類結果。 觀察每一步的 tensor shape 怎麼變化

動畫速度
Mini CNN: Input → Conv → ReLU → Pool → Conv → ReLU → Pool → FC → Output
點 Play 開始動畫,或用 Step 一步一步走。
Current Layer
Input Shape
Output Shape
Parameters
為什麼要看 shape 變化
新手最常見的 RuntimeError 就是 shape mismatch。理解每一層怎麼改變 tensor 的 shape, 就能自己算出 FC 的 input features 要填多少,不用再靠猜。

PyTorch 實作指南

知道原理後,怎麼用 PyTorch 寫出來?這裡整理最核心的 code pattern,跟新手最常踩的雷。

nn.Module 基本結構

最簡單的 CNN
import torch import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self, num_classes=10): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 64, kernel_size=3, padding=1), nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.MaxPool2d(2, 2), nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.BatchNorm2d(128), nn.ReLU(inplace=True), nn.AdaptiveAvgPool2d(1), ) self.classifier = nn.Linear(128, num_classes) def forward(self, x): x = self.features(x) x = x.flatten(1) x = self.classifier(x) return x

Residual Block 寫法

ResNet 的核心 building block
class ResidualBlock(nn.Module): def __init__(self, channels): super().__init__() self.block = nn.Sequential( nn.Conv2d(channels, channels, 3, padding=1, bias=False), nn.BatchNorm2d(channels), nn.ReLU(inplace=True), nn.Conv2d(channels, channels, 3, padding=1, bias=False), nn.BatchNorm2d(channels), ) self.relu = nn.ReLU(inplace=True) def forward(self, x): return self.relu(self.block(x) + x) # ← skip connection!

完整 Training Loop

PyTorch 訓練五步驟
# 1. 準備資料 train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) # 2. 定義模型、Loss、Optimizer model = SimpleCNN(num_classes=10).cuda() criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) # 3. Training Loop for epoch in range(50): model.train() # ← 切到訓練模式 (BN 用 batch stats) for images, labels in train_loader: images, labels = images.cuda(), labels.cuda() outputs = model(images) # Forward loss = criterion(outputs, labels) # 算 Loss optimizer.zero_grad() # 清梯度 loss.backward() # 反向傳播 optimizer.step() # 更新參數 # 4. Evaluation model.eval() # ← 切到推論模式 (BN 用 running stats) with torch.no_grad(): for images, labels in val_loader: outputs = model(images.cuda()) _, predicted = outputs.max(1) correct += (predicted == labels.cuda()).sum().item()

Training Simulator

調整超參數,看 loss curve 怎麼變化。體會 learning rate 太大太小、模型太深(overfitting)的差別。

Loss Curve 模擬器
Train Loss Val Loss
Learning Rate0.01
太大會震盪,太小會收斂很慢
Batch Size64
小 batch = 更多雜訊,大 batch = 更平滑
Model Depth18 layers
太深 + 少資料 = overfitting
Epoch: 0 / 100 | Train Loss: — | Val Loss: —

新手五大地雷

YOLO vs 分類模型:差在哪?

面向分類模型 (ResNet)YOLO (偵測模型)
輸出單一 class 機率向量 (1000,)每格 × 每 anchor 的 (x,y,w,h,conf,classes)
LossCrossEntropyLossBox loss + Obj loss + Cls loss (複合)
後處理argmax 取最大NMS 去除重複框
標註一張圖一個 label每個物件一個 bbox + label
難點模型設計正負樣本分配 + anchor 設計 + loss 平衡
YOLO Loss 怎麼算?(簡化版)

YOLO 的 loss 有三個部分:

  • Box loss: 預測框跟 GT 框的差異 (CIoU / DFL)
  • Objectness loss: 這格有沒有物件 (BCE)
  • Classification loss: 物件是哪個 class (BCE / CE)

Loss = λ₁ × box_loss + λ₂ × obj_loss + λ₃ × cls_loss

λ 的比例很重要,調不好模型會偏向某一個任務。YOLOv5 預設 box=0.05, obj=1.0, cls=0.5。

DataLoader 怎麼處理偵測資料?

分類:每筆資料 = (image, label_int)

偵測:每筆資料 = (image, [多個 bbox])

因為每張圖的物件數量不同,需要自定義 collate_fn 來 padding。

常見格式 (YOLO txt format):
class_id x_center y_center width height (全部歸一化到 0~1)

Debug 心法
訓練到一半 loss 不動?先確認:
1. 印 shape — 每層 input/output shape 是否符合預期
2. 印 loss 各項 — 看是哪個 loss 卡住了
3. overfit 一張圖 — 確認模型有能力學習(loss 能降到 0)
4. 視覺化預測 — 畫出預測的 bbox 跟 GT 比較
小測驗

PyTorch 的 CrossEntropyLoss 內部已經包含了什麼操作?

ReLU
LogSoftmax
Sigmoid
BatchNorm

作業說明

這次作業的目標:設計一個自己的物件偵測模型,用 PyTorch 實作,並在提供的資料集上訓練。

作業要求

1. 模型設計
設計一個 CNN backbone + detection head。不限架構,但需要解釋設計理由。
2. 訓練腳本
完整的 training loop,包含 data loading, augmentation, loss, optimizer。
3. 實驗紀錄
記錄不同超參數的實驗結果,分析哪些設計有效、哪些沒用。
4. 推論展示
提供幾張測試圖的偵測結果視覺化,跟 mAP 數字。

評分標準

項目比重說明
模型架構設計40%Backbone 選擇、Head 設計是否合理、是否有創意
程式碼品質30%結構清晰、有適當的 config、可重現
訓練策略20%Learning rate schedule、Augmentation、Loss 設計
最終精度10%mAP 越高越好,但不是唯一指標
重要提醒
精度只佔 10%! 重點是你對架構的理解和設計思路,不是跑出最高的數字。 用 pretrained backbone 是允許的(甚至推薦),但你需要說明為什麼選這個 backbone。

繳交內容

建議方向

方向 A: 從零開始 (挑戰型)

自己設計 backbone + detection head,不使用任何 pretrained weights。

建議架構:3~5 個 Conv block (每個: Conv → BN → ReLU → Pool) + YOLO head。

優點:完全理解每個組件。缺點:精度可能不高,需要更多訓練時間。

方向 B: Transfer Learning (推薦型)

使用 pretrained ResNet/EfficientNet 當 backbone,自己加 detection head。

步驟:取 ImageNet pretrained backbone → 接 FPN/PAN neck → 接 YOLO-style head。

優點:精度高、訓練快。需要理解 feature 怎麼從 backbone 取出。

方向 C: 魔改 YOLOv5/v8 (實戰型)

Fork Ultralytics 的程式碼,修改某個模組(如換 backbone、改 loss、加 attention)。

需要:明確說明你改了什麼、為什麼改、改前改後的數據比較。

優點:接近工業實戰。需要理解框架原始碼。

常見問題 FAQ

Q: 可以用 Ultralytics 直接 train 嗎?
可以用來當 baseline 比較,但你自己的模型需要有自己寫的部分。不能只跑一行 yolo train 就交差。
Q: 需要訓練多久?
看資料集大小。如果用提供的小資料集,Google Colab 的免費 GPU 跑幾個小時就夠了。重點不在跑很多 epoch,而是觀察 loss 趨勢。
Q: mAP 多少才算及格?
沒有固定門檻。重點是你的模型有在學(loss 下降、能產生合理的 bbox)。如果設計合理但精度不高,好好分析原因一樣可以拿高分。
Q: 可以幾個人一組?
個人作業。但歡迎互相討論概念,不要直接複製程式碼。