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...
150,528 × 1024
≈ 1.5 億
參數,而且這還只是第一層
★ 三個更糟的問題
① 空間關係被打散 — 攤平後左上角跟右下角變「平等」
② 位置敏感 — 貓在左上跟貓在右下會被學成兩件事
③ 沒有 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
👁 🦷 👂
物件部件
眼睛、輪子、耳朵
Layer 11+
🐱
完整物件
人臉、汽車、貓
直覺類比:就像給模型一堆放大鏡,每個放大鏡專看一種東西。 早期看「邊」「角」,深層看「眼睛」「車輪」這種高階概念。
01·3
超參數
Convolution 重要超參數

記住:3×3 是工業界共識

參數在做什麼影響
Kernel size窗戶多大1×1, 3×3, 5×5, 7×7 都常見;3×3 是工業界共識
Stride一次滑幾格stride=2 會讓輸出尺寸減半
Padding邊緣補幾圈 0padding=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?

① 降低計算量
尺寸減半,後面層計算少 4 倍
② 增加 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 各代用過的非線性

名字公式用在哪
ReLUmax(0, x)最經典,YOLOv1~v3 主要用
Leaky ReLUmax(0.01x, x)解決 dying ReLU,Darknet 系列愛用
Mishx · tanh(softplus(x))YOLOv4 用,平滑且非單調
SiLU / Swishx · 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 的效果

Input · Kernel
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

y = F(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
速度快、計算便宜,適合小模型
x
3×3
3×3
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 五階段架構

Input
224×224×3
conv1
7×7, s=2
112×112
MaxPool
56×56
conv2_x
56×56
conv3_x
28×28
conv4_x
14×14
conv5_x
7×7
GAP
FC
1000
★ 資訊守恆原則
每個 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)
Img
CNN
RPN
Cls
① 找候選區域 → ② 對每個區域分類
→ 慢:5~7 FPS,沒辦法即時
One-Stage (YOLO)
Img
CNN
Head
Boxes
一次到底,所有 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 = 20PASCAL 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(強化定位重要性)
含物件 · Confidence
權重 1
λ_noobj = 0.5 · 不含物件 Conf
弱化大量背景的影響(7×7 大部分都沒物體)
含物件 cell · 分類誤差
權重 1
為什麼用 √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 · 七大改進

#改進內容效果
1Batch Normalization所有 conv 後加 BN,移除 dropoutmAP +2%
2High Res Classifier用 448×448 fine-tune 分類器 10 epochmAP +4%
3Anchor Boxes移除 FC,改用 anchor。輸入改 416×416recall ↑
4Dimension Clustersk-means(IoU 距離)在訓練集找 anchor比手選好
5Direct Locationsigmoid 限制中心在 cell 內訓練穩定
6Fine-Grained Featurespassthrough layer 接 26×26 → 13×13小物件 ↑
7Multi-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
Part 2 (50%)
Shortcut
↓ 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,淺層精確定位資訊也快速傳到深層。
P5
↓ FPN
P4
P3
P5'
↑ PAN
P4'
P3'
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
GIoU · 加包圍框懲罰
考慮「離交集有多遠」
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
模型depthwidth參數量用途
YOLOv5n0.330.25~1.9Mnano,邊緣裝置
YOLOv5s0.330.50~7.2Msmall
YOLOv5m0.670.75~21.2Mmedium
YOLOv5l1.001.00~46.5Mlarge
YOLOv5x1.331.25~86.7Mx-large
03·8
v6
YOLOv6 (2022, 美團) · 主打工業部署

RepVGG · 結構重參數化

訓練時 · 多分支
Input
3×3
1×1
Identity
Output
→ 好訓練
推論時 · 單分支
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
Feat
Cls Branch
類別
Reg Branch
Bbox
✓ 各專注一個任務,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用途
YOLOv8n3.2M37.3nano
YOLOv8s11.2M44.9small
YOLOv8m25.9M50.2medium
YOLOv8l43.7M52.9large
YOLOv8x68.2M53.9x-large
03·11
對照表
YOLO 各版本對照總表

5 個維度橫向對照 · hover 看高亮

03·11
對照表

YOLO 各版本對照總表

版本BackboneNeckHeadAnchorLoss
v12016GoogLeNet-likeFC, 7×7×30SSE multi-part
v22016Darknet-19passthroughconv5 anchorsSSE+sigmoid
v32018Darknet-53FPN-likemulti-scale9 anchorsBCE+SSE
v42020CSPDarknet+MishSPP+PANetv3 head9 anchorsCIoU+BCE
v52020CSPDarknet (C3)SPPF+CSP-PANv3 head9 (auto)CIoU+BCE
v62022EfficientRepRep-PANDecoupledVFL+SIoU+DFL
v72022E-ELANSPPCSPC+PANLead+AuxCIoU+BCE
v82023CSPDarknet (C2f)SPPF+PANDecoupledCIoU+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: 7
Anchors: 3
玩玩看:
→ 拖拉 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 行為不對
③ 驗證沒包 no_grad() → 記憶體爆炸
④ 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
My Model
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
評分
評分重點(權重)

重點不是準確率多高

40%
設計思路說明
你有沒有想清楚為什麼這樣做
30%
架構合理性
設計有沒有明顯錯誤、合不合理
20%
訓練完成度
有沒有真的訓練到收斂
10%
準確率
不重要,有就好
★ 再強調一次
重點不是準確率多高,是有沒有想清楚自己在做什麼。 寫「我這層用 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 真的下降的那刻,你就懂了
下週見 記得寫作業 有問題隨時找我