Lesson 01 · Computer Vision Foundations
視覺模型
基礎
電腦怎麼看圖? — 從 CNN, ResNet, 到 YOLO
給學弟的
~ 90 mins
interactive
00
節奏總覽
課程節奏
這 90 分鐘要走完的路
1 · CNN 複習
Convolution / Pooling / FC
+ 輔助組件(BN, ReLU, Dropout)
demo · Conv Playground
2 · ResNet 全變體
梯度消失 / Skip Connection
Basic vs Bottleneck / Pre-activation
demo · Gradient Vanishing
3 · YOLO 演進
v1 → v8,每代解決什麼問題
Backbone + Neck + Head
demo · Anchor Visualizer
4 · PyTorch 實作
nn.Module / forward / 訓練流程
從零刻一個你的 model
demo · Model Builder
5 · Q&A + 作業
作業要求 / 評分重點
卡關了什麼時候來找我
00
開場
先想一個問題
電腦看到的圖,長這樣 ↓
那要怎麼從這堆數字判斷「有沒有貓」?
00
開場
用「規則」寫看看?
「找毛?」
那「毛」要怎麼定義?
多長算毛?多細算毛?
草跟毛長很像怎麼辦?
「找耳朵?」
貓耳朵的形狀有幾種?
正面 / 側面看完全不一樣
摺耳跟立耳要分開寫嗎?
「比對範本?」
貓的姿勢千變萬化
躺著 / 站著 / 跳著
每種姿勢都要一張範本?
★ 結論
用規則寫不出來,所以才需要
讓模型自己學特徵 ——
這就是 CNN 在做的事:把 feature engineering 交給模型自己。
§ 1
CNN 完整複習
Convolution · Pooling · FC,以及它們為什麼長這樣
Local Connectivity
Weight Sharing
Hierarchical
01·1
FC 的問題
先問一個問題
為什麼是 CNN,不是普通的 FC?
一張 224×224 的彩色圖
= 150,528 個數字
如果直接接 FC 到 1024 個 neuron...
★ 三個更糟的問題
① 空間關係被打散 — 攤平後左上角跟右下角變「平等」
② 位置敏感 — 貓在左上跟貓在右下會被學成兩件事
③ 沒有 inductive bias — 模型對「什麼是圖片」一無所知
01·2
三大特性
CNN 的三大設計特性 ★ 重點
給模型三個內建假設
① Local Connectivity
局部連接
每個 neuron 只看前一層的小區域(receptive field)
→ 模仿人類視覺皮質
符合「相鄰像素相關」的事實
② Weight Sharing
參數共享
同一個 filter 滑過整張圖,參數共用
→ 大幅減少參數
同一種特徵不管在哪都認得
③ Hierarchical
層次特徵
淺層學邊緣紋理,深層學部件語意
→ 跟人類視覺一樣
由低層特徵組合出高層概念
★ 記住這三個
這就是 CNN 「比 FC 強這麼多」的根本原因。
後面所有架構(包含 Transformer 的 ViT)都在跟這三個假設對話 —— 要嘛延續它,要嘛挑戰它。
01·2
Receptive Field
補充:Receptive Field(感受野)
一個 neuron 「能看到」原始輸入的多大範圍
第一層 3×3 conv
→ RF = 3×3
第二層 3×3 conv
→ RF = 5×5
因為它看的是 layer1 的 output,而 layer1 每個位置已經看了 3×3
★ VGG 的核心洞見
兩層 3×3 (RF = 5×5) 的參數量 =
2×9 = 18,
一層 5×5 (RF 也是 5×5) =
25。
兩層 3×3 參數更少,還多一層非線性。
01·3
Convolution
核心層 (A) · Convolutional Layer
拿一個小窗戶在圖上滑,每滑一次做點積
範例:垂直邊緣檢測器
Kernel: 計算左上 3×3:
[ 1 0 -1] 1·1 + 2·0 + 3·(-1)
[ 1 0 -1] + 4·1 + 5·0 + 6·(-1)
[ 1 0 -1] + 7·1 + 8·0 + 9·(-1)
= -6
本質就是「左邊像素 - 右邊像素」
→ 絕對值大代表這裡有垂直邊緣
01·3
重點觀念
關於 Kernel,要記住四件事
① Kernel 的值是學出來的
不是寫死的!模型自己 backprop 學出最有用的 filter。
② 同一個 kernel 全圖共用
這就是 parameter sharing —— 大幅減少參數,左上角學到的特徵右下角也認得。
③ 一個 kernel 抓一種特徵
邊緣 / 紋理 / 顏色變化 / 角點...每個 filter 各司其職。
④ 一層常有 32 ~ 512 個 kernel
越深越多,各自抓不同抽象層次的特徵。
01·3
層次特徵
CNN 各層學到的特徵
從邊緣到完整物件 — Hierarchical Features
Layer 1-2
╱ ╲ ╱
邊緣、角點
線條方向
Layer 3-5
▦ ⊙ ◈
紋理、圖案
簡單形狀
Layer 6-10
👁 🦷 👂
物件部件
眼睛、輪子、耳朵
直覺類比:就像給模型一堆放大鏡,每個放大鏡專看一種東西。
早期看「邊」「角」,深層看「眼睛」「車輪」這種高階概念。
01·3
超參數
Convolution 重要超參數
記住:3×3 是工業界共識
| 參數 | 在做什麼 | 影響 |
| Kernel size | 窗戶多大 | 1×1, 3×3, 5×5, 7×7 都常見;3×3 是工業界共識 |
| Stride | 一次滑幾格 | stride=2 會讓輸出尺寸減半 |
| Padding | 邊緣補幾圈 0 | padding=1 + kernel=3 + stride=1 → 尺寸不變(same) |
| Channel | 用幾個 kernel | 越多抓越多特徵,但參數越多 |
01·3
1×1 Conv
為什麼 1×1 Conv 很重要?
看起來沒做事,其實是現代架構的關鍵
① 改變 channel 數
256 channels 壓到 64 channels 再做 3×3 conv,計算量大幅降低。
② 跨 channel 線性組合
不同特徵之間做混合,等於「特徵的 FC 層」。
③ 加非線性 (+ ReLU)
增加模型表達能力,但幾乎不增加參數。
ResNet Bottleneck、Inception、所有後續架構都用到這個技巧。記住了它,後面看 ResNet / YOLO 都會輕鬆很多。
01·3
Pooling
核心層 (B) · Pooling Layer
取區域代表值,降尺寸、增 RF
Input 4×4 MaxPool 2×2
[1 3 | 2 4]
[2 1 | 0 5] → [3 5]
[----- | -----] [4 9]
[4 0 | 7 9]
[1 2 | 8 3]
每個 2×2 區塊取最大值
→ 4×4 變 2×2,留住最強特徵
Max Pooling
取最大值,最常用(保留最強特徵)
Average Pooling
取平均,較少用於分類
Global Average Pooling
整張 feature map → 一個數字,取代 FC
01·3
Pooling 功能
為什麼要 Pooling?
② 增加 Receptive Field
同樣 3×3 kernel,在 pooling 後等於看更大範圍。
③ 平移不變性
物體稍微移動一點,輸出不會差太多。
現代很多架構(ResNet 後期、YOLOv3+、Transformer)會用 stride=2 conv 取代 pooling,效果差不多但更可學習。Darknet-53 就是純全卷積,完全沒 pooling。
01·3
FC vs GAP
核心層 (C) · 分類頭的演進
為什麼現代架構不再用 FC?
經典 (LeNet / AlexNet)
Input
→
Conv×N
→
Flatten + FC
→
Class
⚠ FC 參數爆炸,容易 overfit
現代 (ResNet+)
Feature
→
GAP
→
1×1 Conv
→
Class
✓ 參數從幾百萬降到幾千
01·4
BN
輔助組件 · Batch Normalization
把每層的輸入標準化
y = γ · (x - μ) / σ + β
γ, β 是可學參數
μ, σ 從當前 batch 算出
① 加速收斂
可以用更大 learning rate
② 穩定訓練
減少 internal covariate shift
③ 輕微 regularization
用了 BN 通常可以不用 Dropout
⚠ 新手最常踩的雷
BN 在 train / eval 模式行為不同。
忘記切換 model.train() / model.eval() 是新手最常見的雷之一。
01·4
Activations
輔助組件 · 激活函數
YOLO 各代用過的非線性
| 名字 | 公式 | 用在哪 |
| ReLU | max(0, x) | 最經典,YOLOv1~v3 主要用 |
| Leaky ReLU | max(0.01x, x) | 解決 dying ReLU,Darknet 系列愛用 |
| Mish | x · tanh(softplus(x)) | YOLOv4 用,平滑且非單調 |
| SiLU / Swish | x · sigmoid(x) | YOLOv5+ 主要用,效果好 |
★ Dying ReLU 問題
ReLU 在 x<0 時輸出和梯度都恆為 0。某個 neuron 不幸一直收到負輸入,
就「
死了」—— 永遠輸出 0,永遠沒梯度。
Leaky ReLU 給負半邊一個小斜率解決這個。
01·5
演進史
經典 CNN 演進史(快速帶過)
從 LeNet 到 ConvNeXt · 14 年精度進化史
1998
LeNet-5
CNN 開山始祖,5 層,手寫數字辨識
2012
AlexNet
8 層,GPU 訓練,首次贏 ImageNet (top-5 error 16.4%)
2014
VGG-16/19
全用 3×3 conv,證明深度的價值
2014
GoogLeNet
22 層,Inception module(多分支)
2015 ★
ResNet
152 層,Skip Connection,3.57% top-5 error
2017+
DenseNet · ConvNeXt
後續架構皆建立在 Skip Connection 之上
01·D
Demo
★ 互動 demo · Convolution Playground
親手調 kernel,看不同 filter 的效果
Output Feature Map
試試 Sobel → 邊緣
Gaussian → 模糊
Sharpen → 銳化
CNN 的第一層其實就在做這種事 —— 只是 kernel 是學出來的。
人類花幾十年研究的 SIFT、HOG、Sobel,CNN 訓練幾個 epoch 自己長出來。
§ 2
ResNet 全變體
梯度消失 → Skip Connection → 改變了 deep learning
02·1
背景
為什麼需要 ResNet
2015 · 何愷明 · 改變 deep learning 的論文
微軟研究院 · Kaiming He 等人 2015 年提出
論文:Deep Residual Learning for
Image Recognition
152 層
3.57% top-5 error
比 VGG 深 8 倍
後續所有現代架構幾乎都用了 skip connection
Transformer
YOLO
Diffusion
ViT
02·2
退化問題
2014 年發現的怪現象
把 CNN 疊得很深,train loss 居然變高
20 層 CNN → train error: 5%
56 層 CNN → train error: 11% (蛤?)
理論上深層至少應該跟淺層一樣好
(多出來的層學成 identity 就行)
→ 但實務上做不到
★ Degradation Problem
不是過擬合(train error 也變高),
是
優化問題 —— 我們無法把那麼深的網路訓練起來。
02·3
梯度消失
兇手是誰?
梯度消失 (Gradient Vanishing)
backprop 從輸出傳回輸入,每經過一層就乘一次該層的偏導數
L5
∇=1.0
×0.5
L4
∇=0.5
×0.5
L3
∇=0.25
×0.5
L2
∇=0.125
×0.5
L1
∇=0.063
如果每層 |梯度|<1...
10 層後:0.510 ≈ 0.001
50 層後:0.550 ≈ 10-15 → ≈ 0
02·3
類比
從一樓傳話到 50 樓,
每傳一層走音 50%,
到頂樓已經完全聽不懂。
ResNet 的 skip connection 就像給每一樓裝一支直通電話
訊息可以直接跳過中間樓層
02·4
解法
何愷明的解法 · Residual Learning
不學 H(x),改學 F(x) = H(x) - x
模型只要學「我要在 x 上加什麼」
而不是「我要產出什麼」
① 梯度直接通路
∂(F+x)/∂x = ∂F/∂x + 1
那個 +1 保證梯度永遠有東西
② 學殘差比學整體容易
起點已經很近,只要學差量就行
③ 退化解
沒用就把 F(x) 學成 0,等於 identity,不會劣化
02·4
架構
兩種長相對比
普通 CNN
x
→
Conv
→
BN
→
ReLU
→
Conv
→
y
ResNet Block
x
→
Conv
→
BN
→
ReLU
→
Conv
→
⊕
→
y
skip connection
02·5
兩種 Block
兩種殘差區塊
看模型大小決定用哪個
Basic Block — ResNet-18 / 34
兩層 3×3 conv
速度快、計算便宜,適合小模型
Bottleneck — ResNet-50 / 101 / 152
三層:1×1 降維 → 3×3 計算 → 1×1 升維
省參數,更多非線性
x
→
1×1↓
→
3×3
→
1×1↑
→
⊕
★ Bottleneck 省了多少?(以 256-channel 為例)
兩層 3×3 =
1,179,648 參數 vs Bottleneck =
69,632 參數
→
省了約 17 倍!
精神:讓貴的運算(3×3 conv)在比較少的 channel 上做。
02·6
架構
ResNet 五階段架構
★ 資訊守恆原則
每個 stage
空間尺寸減半(÷2),
channel 翻倍(×2)
56×56×64 ≈ 28×28×128 ≈ 14×14×256 ≈ 200K
—— 總資訊量守恆,後面所有 backbone 都沿用這 pattern。
02·8
v2
Pre-activation ResNet (ResNet v2, 2016)
把 BN, ReLU 從 conv 之後移到之前
原版 v1 (post-activation)
x
→
Conv
→
BN
→
ReLU
→
Conv
→
⊕
→
ReLU
⚠ shortcut 加完還要過 ReLU,梯度路徑被破壞
v2 (pre-activation)
x
→
BN
→
ReLU
→
Conv
→
BN
→
ReLU
→
Conv
→
⊕
✓ shortcut 是純 identity,梯度直接無損傳遞
結果:可以訓練到 1000+ 層(原版到 200 層就開始劣化)
02·D
Demo
★ 互動 demo · Gradient Vanishing
親眼看普通 CNN vs ResNet 的梯度差異
Plain CNN(沒 skip)
梯度從右(輸出)往左(輸入)傳遞
ResNet(有 skip)
skip connection 讓梯度有 +1 加法項
Plain 在 30+ 層就梯度全黑;ResNet 不管多深都還是亮的。這就是 Skip Connection 的魔力。
§ 3
YOLO 系列演進
v1 → v8,每一代都在解決前一代的某個具體問題
03·1
背景
先區分清楚兩件事
物件偵測 vs 分類
分類 (Classification)
輸入:一張圖
輸出:這張圖是什麼類別
→ 一個答案
🖼️ → CNN → "貓 (95%)"
偵測 (Detection)
輸入:一張圖
輸出:有哪些物件 + 各自在哪 + 各自是什麼
→ N 個答案
🖼️ → Detector → 貓 (x1,y1,x2,y2)
狗 (x3,y3,x4,y4)
...
偵測比分類難很多 —— 要同時回答「在哪」和「是什麼」,
而且物體數量不固定(0 個或 100 個)。
03·2
背景
YOLO 之前的世界
Two-stage vs One-stage Detector
Two-Stage (Faster R-CNN)
① 找候選區域 → ② 對每個區域分類
→ 慢:5~7 FPS,沒辦法即時
One-Stage (YOLO)
一次到底,所有 box 一次輸出
→ 快:YOLOv1 達 45 FPS
FPS(Frames Per Second):即時偵測通常要求 ≥ 30 FPS,模型必須在 33ms 內完成推論。
03·3
v1
YOLOv1 (2016) · One-stage 開山之作
把偵測當 regression 問題,一次到底
輸出設計:S × S × (B×5 + C)
| S = 7 | 網格大小 7×7 |
| B = 2 | 每 cell 預測 2 個 bbox |
| C = 20 | PASCAL VOC 20 類 |
7 × 7 × (2×5 + 20) = 7×7×30
怎麼分配 GT?
① 物體中心點落在哪個 cell
② 那個 cell 就「負責」預測它
③ x, y 是 cell 內偏移 (0~1)
④ w, h 是整圖比例 (0~1)
⑤ confidence = P(物體) × IoU
限制:每 cell 只能預測一個類別。
兩個不同類別中心同 cell → 只能偵到一個。
03·3
v1 Loss
YOLOv1 Loss 函數
Multi-part Sum-Squared Error
λ_coord = 5 · 定位誤差
x, y, √w, √h(強化定位重要性)
λ_noobj = 0.5 · 不含物件 Conf
弱化大量背景的影響(7×7 大部分都沒物體)
為什麼用 √w, √h?
GT w=100, pred=95 → 差 5%(可接受)
GT w=10, pred=5 → 差 50%(很嚴重)
平方根後:
√100=10 vs √95≈9.75(差 0.25)
√10≈3.16 vs √5≈2.24(差 0.92)
小物體誤差被放大,大物體被壓縮,更符合直覺。
03·4
v2
YOLOv2 / YOLO9000 (2016/17)
Better, Faster, Stronger · 七大改進
| # | 改進 | 內容 | 效果 |
| 1 | Batch Normalization | 所有 conv 後加 BN,移除 dropout | mAP +2% |
| 2 | High Res Classifier | 用 448×448 fine-tune 分類器 10 epoch | mAP +4% |
| 3 | Anchor Boxes ★ | 移除 FC,改用 anchor。輸入改 416×416 | recall ↑ |
| 4 | Dimension Clusters | k-means(IoU 距離)在訓練集找 anchor | 比手選好 |
| 5 | Direct Location | sigmoid 限制中心在 cell 內 | 訓練穩定 |
| 6 | Fine-Grained Features | passthrough layer 接 26×26 → 13×13 | 小物件 ↑ |
| 7 | Multi-Scale Training | 每 10 batch 換尺寸 {320,...,608} | 多解析度 |
03·4
Anchor
Anchor 是什麼?(這個一定要懂)
預先定義常見 box 形狀,模型只預測「微調」
想像:給你一張空白問卷 vs
給你一張填好 80% 的草稿
→ 後者只要修錯的就好
Anchor 就是那個 80% 正確的草稿
bx = σ(tx) + cx # 中心 x
by = σ(ty) + cy # 中心 y
bw = pw · e^(tw) # anchor 寬 × 縮放
bh = ph · e^(th) # anchor 高 × 縮放
sigmoid:確保中心在 cell 內
e^x:確保寬高為正,e^0=1 不調整
03·5
v3
YOLOv3 (2018) · Darknet-53 + 三尺度預測
類 FPN 架構,影響後面所有 YOLO
Backbone · Darknet-53
53 層 conv + 加入 ResNet skip connection
完全沒 pooling,用 stride=2 取代
top-1/top-5 與 ResNet-152 相當,速度快 2 倍
分類改 sigmoid + BCE
一個物體可能屬多類別(Woman + Person)
COCO label 不互斥,BCE 訓練更穩定
三尺度預測
13×13 (stride 32)
→ 大物體(車、公車)
26×26 (stride 16)
→ 中物體(人、狗)
52×52 (stride 8)
→ 小物體(杯子、遙控器)
每尺度 3 個 anchor,共 9 個
03·5
FPN
FPN · Feature Pyramid Network 為什麼重要
高解析度 ⊕ 高語意 = 兩邊都要
CNN 的天然多尺度
淺層 · 解析度高,知道「在哪」(位置精確)
但不知道「是什麼」
深層 · 解析度低,知道「是什麼」(語意豐富)
但位置粗糙
FPN 的做法
把深層的語意 top-down 傳到淺層,讓淺層同時擁有:
① 高解析度 ② 高語意
→ 大物體用小 feature map 預測
→ 小物體用大 feature map 預測
03·6
v4
YOLOv4 (2020) · 工程集大成
三段式架構 · Backbone-Neck-Head
03·6
CSP
YOLOv4 Backbone · CSPDarknet-53
把 feature map 切成兩半
Input Feature
↓ Split
Part 1 (50%)
↓
Dense Block
↓ Concat
Output
為什麼切一半?
DenseNet 每層都跟前面所有層 concat,
梯度資訊大量重複計算。
CSP 把 feature 切一半,只讓一半做 dense 運算,
另一半直接跳過
→ 計算減少 ~20%
→ 保留梯度流動優勢
03·6
Neck
YOLOv4 Neck · SPP + PANet
擴大 RF · 雙向特徵融合
SPP · Spatial Pyramid Pooling
多尺度 max pool(5×5, 9×9, 13×13)concat,擴大 RF 但不增加計算。
輸入 Feature (13×13×512)
├─→ MaxPool 5×5 → 13×13×512
├─→ MaxPool 9×9 → 13×13×512
├─→ MaxPool 13×13 → 13×13×512
└─→ Identity → 13×13×512
→ Concat → 13×13×2048
PANet · Path Aggregation
FPN 只 top-down,深層語意傳到淺層路徑太長。
PANet 加上 bottom-up,淺層精確定位資訊也快速傳到深層。
03·6
v4 訓練
v4 訓練技巧 · Bag of Freebies / Specials
不增加推論成本 vs 略增成本
Bag of Freebies (BoF) · 只訓練時用
Mosaic · 4 張圖拼一張(小物體增強)
CutMix · 切貼,label 按面積混合
Label Smoothing · one-hot → [0.9, 0.1]
DropBlock · 整塊 dropout(普通 dropout 在 CNN 沒用)
CIoU Loss · 取代 MSE
Bag of Specials (BoS) · 略增成本
Mish activation
SPP · 多尺度 pooling
PAN · 雙向特徵融合
DIoU-NMS · NMS 時考慮中心距離
03·6
IoU Loss
IoU 家族 Loss(看不懂的話這頁要記住)
從 IoU → GIoU → DIoU → CIoU
IoU · 衡量重疊
IoU = 交集面積 / 聯集面積
問題:無交集時 IoU=0,梯度為 0
DIoU · 加中心距離
DIoU = IoU - dist²/diag²
CIoU · 再加長寬比 ★
同時考慮 overlap、距離、長寬比 —— v4/v5/v8 都用這個
03·7
v5
YOLOv5 (2020, Ultralytics) · 工業界主流
不是學術論文,但因為好用紅了
主要貢獻(都是工程上的)
✓ PyTorch 實作(前面都是 Darknet C)
✓ 訓練流程超簡單(yolo train 一行)
✓ 5 種尺寸:n / s / m / l / x
✓ 部署友善(ONNX, TensorRT, CoreML)
✓ 配置檔 .cfg → .yaml
關鍵組件
C3 Module · CSP 簡化版
SPPF · 串聯 5×5 取代並聯 SPP,等價但更快
Auto-anchor · 訓練前自動 k-means
Letterbox · 自適應縮放保持 aspect ratio
| 模型 | depth | width | 參數量 | 用途 |
| YOLOv5n | 0.33 | 0.25 | ~1.9M | nano,邊緣裝置 |
| YOLOv5s | 0.33 | 0.50 | ~7.2M | small |
| YOLOv5m | 0.67 | 0.75 | ~21.2M | medium |
| YOLOv5l | 1.00 | 1.00 | ~46.5M | large |
| YOLOv5x | 1.33 | 1.25 | ~86.7M | x-large |
03·8
v6
YOLOv6 (2022, 美團) · 主打工業部署
RepVGG · 結構重參數化
推論時 · 單分支
Input
單一 3×3 Conv
(已合併所有分支)
Output
→ 極快
★ 為什麼可以合併?
1×1 conv 可 zero-pad 成 3×3,identity 看成中心為 1 的 3×3 conv,BN 是線性可融合進 conv ——
三條分支數學上等價合併成一個 3×3 conv。訓練多分支、推論單分支!
03·8
Decoupled
v6 / v8 共同特性 · Decoupled Head + Anchor-Free
分類跟回歸分開預測
舊做法 · Coupled
Feat
→
共用 Conv
→
cls + box
⚠ 兩個任務互相拉扯
新做法 · Decoupled
✓ 各專注一個任務,YOLOX 實驗 +1.1% AP
分類需要語意資訊(這是什麼),定位需要空間資訊(在哪)—— 共用 head 會互相干擾。
03·9
v7
YOLOv7 (2022)
E-ELAN · Lead + Aux Head · COCO 56.8% AP
E-ELAN Backbone
透過 expand → shuffle → merge 的 cardinality 操作,控制最短/最長 gradient path。
Planned Re-param
避免 RepConv 的 identity 連接破壞 ResNet/DenseNet 的 gradient。
Lead + Aux Head
Lead head 產生最終預測,Aux head 訓練時輔助;用 lead 指導兩個 head 的標籤分配。
不在 ImageNet 預訓練,只用 COCO 從零訓練。COCO 達 56.8% AP,5–160 FPS 範圍 SOTA。
03·10
v8
YOLOv8 (2023, Ultralytics)
整合多任務 · C2f · Anchor-Free + DFL
C2f 取代 C3 · Backbone 改變
C3 只取最後一個 bottleneck 輸出。
C2f 把所有 bottleneck 輸出都 concat,有效感受野更大、上下文更豐富(類 DenseNet)。
Anchor-Free + DFL
直接預測物件中心 + 寬高,不再用 anchor。
DFL:把 bbox 邊界當分布預測,而不是單一值 —— 表達邊界的不確定性。
| 模型 | 參數量 | mAP COCO | 用途 |
| YOLOv8n | 3.2M | 37.3 | nano |
| YOLOv8s | 11.2M | 44.9 | small |
| YOLOv8m | 25.9M | 50.2 | medium |
| YOLOv8l | 43.7M | 52.9 | large |
| YOLOv8x | 68.2M | 53.9 | x-large |
03·11
對照表
YOLO 各版本對照總表
5 個維度橫向對照 · hover 看高亮
03·11
對照表
YOLO 各版本對照總表
| 版本 | 年 | Backbone | Neck | Head | Anchor | Loss |
| v1 | 2016 | GoogLeNet-like | — | FC, 7×7×30 | 無 | SSE multi-part |
| v2 | 2016 | Darknet-19 | passthrough | conv | 5 anchors | SSE+sigmoid |
| v3 | 2018 | Darknet-53 | FPN-like | multi-scale | 9 anchors | BCE+SSE |
| v4 | 2020 | CSPDarknet+Mish | SPP+PANet | v3 head | 9 anchors | CIoU+BCE |
| v5 | 2020 | CSPDarknet (C3) | SPPF+CSP-PAN | v3 head | 9 (auto) | CIoU+BCE |
| v6 | 2022 | EfficientRep | Rep-PAN | Decoupled | 無 | VFL+SIoU+DFL |
| v7 | 2022 | E-ELAN | SPPCSPC+PAN | Lead+Aux | 有 | CIoU+BCE |
| v8 | 2023 | CSPDarknet (C2f) | SPPF+PAN | Decoupled | 無 | CIoU+DFL+BCE |
03·12
演進邏輯
★ 重點頁:每代解決什麼問題
理解演進邏輯,才知道未來會往哪走
v1
證明 one-stage 可行
把偵測當 regression,一次到底,45 FPS
v2
anchor 提升定位精度
k-means 找 anchor + sigmoid 限制中心 + BN
v3
多尺度解決小物體
Darknet-53 + 三尺度 FPN-like
v4
工程技巧集大成
Backbone-Neck-Head + BoF/BoS
v5
工業部署友善化
PyTorch + 5 種尺寸 + 部署一條龍
v6
結構重參數化加速
RepVGG · 訓練多分支推論單分支
v7
訓練技巧 SOTA
E-ELAN + Lead/Aux head
v8
anchor-free + 多任務
Det / Seg / Pose / Cls 一個架構搞定
03·D
Demo
★ 互動 demo · YOLO Anchor Visualizer
看 grid 跟 anchor 怎麼覆蓋整張圖
橘色框 ground truth ·
藍色框 預測 ·
黃格 responsible cell
玩玩看:
→ 拖拉 grid:從稀疏 5×5 拉到密集 19×19
→ 切 anchor-based / free 模式
→ 點圖片任何位置畫一個 GT box
§ 4
PyTorch 組模型
nn.Module · forward · 訓練流程
04·1
nn.Module
nn.Module 是什麼
PyTorch 所有模型的爸爸
它幫你做四件事
① 管理所有 parameter(自動追蹤,給 optimizer 用)
② 支援 GPU 移動(.cuda() 一行)
③ 支援 train/eval 模式切換(BN, Dropout 行為)
④ 自動 backward(forward 寫對就好,不用自己寫)
兩個重點方法
class SimpleCNN(nn.Module):
def __init__(self):
# 定義「有什麼」
super().__init__()
...
def forward(self, x):
# 定義「怎麼用」
...
return x
04·2
SimpleCNN
最簡單的 CNN
完整一個分類模型
import torch
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
# ── 在 __init__ 定義零件 ──────────────────────
self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.relu = nn.ReLU()
self.fc = nn.Linear(64 * 8 * 8, num_classes)
def forward(self, x):
# ── 在 forward 定義組裝方式 ──────────────────
x = self.relu(self.conv1(x)) # (B, 32, 32, 32)
x = self.pool(x) # (B, 32, 16, 16)
x = self.relu(self.conv2(x)) # (B, 64, 16, 16)
x = self.pool(x) # (B, 64, 8, 8)
x = x.view(x.size(0), -1) # 攤平 (B, 4096)
x = self.fc(x) # (B, 10)
return x
__init__ 定義「有什麼」 · forward 定義「怎麼用」 · backward 完全自動,不用自己寫
04·3
ResNet Block
寫一個 ResNet Block
這幾行就是 ResNet 的精髓
class ResidualBlock(nn.Module):
def __init__(self, in_ch, out_ch, stride=1):
super().__init__()
self.conv1 = nn.Conv2d(in_ch, out_ch, 3, stride, padding=1)
self.bn1 = nn.BatchNorm2d(out_ch)
self.conv2 = nn.Conv2d(out_ch, out_ch, 3, 1, padding=1)
self.bn2 = nn.BatchNorm2d(out_ch)
self.relu = nn.ReLU(inplace=True)
# ── shape 不一致時用 1×1 conv 對齊 (projection shortcut)
if stride != 1 or in_ch != out_ch:
self.shortcut = nn.Sequential(
nn.Conv2d(in_ch, out_ch, 1, stride),
nn.BatchNorm2d(out_ch))
else:
self.shortcut = nn.Identity()
def forward(self, x):
identity = self.shortcut(x)
out = self.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out = out + identity # ←── 這就是 skip connection
return self.relu(out)
04·4
訓練流程
訓練流程怎麼跑
記住這個 5 步驟 pattern
# 1. 建立模型、loss、optimizer
model = SimpleCNN().cuda()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)
# 2. 訓練 loop
for epoch in range(num_epochs):
model.train() # 切到訓練
for images, labels in train_loader:
images, labels = images.cuda(), labels.cuda()
outputs = model(images) # forward
loss = criterion(outputs, labels)
optimizer.zero_grad() # 清梯度!忘了會爆
loss.backward() # 自動算梯度
optimizer.step() # 更新參數
model.eval() # 切到 eval
with torch.no_grad():
...
⚠ 新手最常踩的雷
① 忘記 zero_grad() → 梯度累加,訓練爆炸
② 忘記 train()/eval() 切換 → BN 行為不對
④ tensor 沒搬 GPU → CPU/GPU mismatch
04·5
YOLO 差別
YOLO 跟分類模型差在哪
作業要寫 YOLO,先記住四個改點
① Output shape 變了
(B, S, S, anchors × (5 + classes))
5 = (x, y, w, h, conf)
② Loss 是組合 loss
Localization (CIoU/MSE) +
Confidence (BCE) +
Classification (BCE/CE)
③ 資料前處理要產 GT tensor
把 annotation 轉成跟 output 同 shape 的 tensor;
決定哪個 cell、哪個 anchor 負責哪個 GT。
→ 這是 YOLO 最複雜的部分
④ Inference 後處理
decode → confidence filter → NMS(去除重疊 box)
04·5
NMS
NMS · Non-Maximum Suppression
同一物體可能被多 cell 偵測,要去重
演算法
① 依 confidence 排序所有 box
② 取最高分的 box 保留
③ 把跟它 IoU > threshold 的 box 刪掉
④ 重複 2-3 直到沒 box 剩下
Raw Output
↓
Decode 座標
↓
Conf Filter (>0.25)
↓
NMS (IoU>0.45)
↓
Final Boxes
04·D
Demo
★ 互動 demo · PyTorch Model Builder
點 layer 組模型,即時看 shape 跟 code
Palette
PyTorch Code · Shape
# 點左邊加 layer
§ 5
作業 說明
實作並訓練一個自己設計的 YOLO
05·1
作業
作業 1 詳細說明
實作並訓練一個自己設計的 YOLO
① 自己設計模型架構
參考 v1~v8,但不能完全照抄。
要有自己的 backbone 設計、anchor / anchor-free 選擇,
要有自己的 design choice。
② PyTorch 從頭刻 nn.Module
❌ 不能用 ultralytics 現成 YOLO
❌ 不能用 torch.hub.load
✓ backbone 可用 torchvision 預訓練(說明怎麼接)
③ 寫設計說明 (Markdown)
架構圖、每層 shape、為什麼這樣設計、
trade-off、anchor 選擇、loss 設計
④ 自選資料集訓練到收斂
COCO subset / PASCAL VOC / 自製(100張也行)
要附 loss 曲線
05·2
評分
評分重點(權重)
重點不是準確率多高
★ 再強調一次
重點不是準確率多高,是
有沒有想清楚自己在做什麼。
寫「我這層用 64 channels 因為...」我要看到「
因為」後面的東西,不是「因為大家都這樣」。
05·3
繳交
繳交方式 · 規則 · 截止
繳交內容(GitHub repo)
.
├── model.py # 模型定義
├── train.py # 訓練 script
├── README.md # 設計說明
├── loss_curve.png # 訓練曲線
└── inference demo # 吃一張圖,輸出畫框
下週上課前截止
✓ 卡關了來問我
loss 設計、資料前處理、設計合理性、找不到資料集 —— 全部都可以問。
✗ 不可以做的事
直接複製 GitHub 上的 YOLO 實作 ·
用 ChatGPT/Claude 全部生成完跑一跑
05·4
資源
課後資源
論文必讀
→ ResNet arxiv.org/abs/1512.03385
→ ResNet v2 /1603.05027
→ YOLOv1 /1506.02640
→ YOLOv3 /1804.02767
→ YOLOv4 /2004.10934
→ YOLOv7 /2207.02696
推薦影片
→ 李宏毅 · CNN
→ Aladdin Persson · YOLOv1 from scratch
→ Yannic Kilcher · ResNet 解讀
官方文件
→ pytorch.org/tutorials/
→ docs.ultralytics.com
→ paperswithcode.com / object-detection
Q & A · 然後...
Just Build It.
看了這麼多論文,不如自己刻一遍
看到 loss 真的下降的那刻,你就懂了
下週見
記得寫作業
有問題隨時找我