CV Day 16 DETR 與 Transformer 偵測器
執行需求:Colab T4 可跑。本篇接續 Day 15 的 Faster R-CNN 微調,今天換到 Hugging Face transformers 4.46 的 DetrForObjectDetection,沿用同一份 VOC person/car 子集做微調。完整流程(資料載入 + Image Processor 預處理 + 10 epoch 微調 + 評估)約 45 分鐘,VRAM 占用約 5–7 GB;若想用 facebook/detr-resnet-101 把 mAP 再拉高 3–4 個百分點,VRAM 會到 10–12 GB,仍在 T4 的 16 GB 內。CPU 仍可做推論驗證(model.to("cpu"))。
引言
昨天的內容中,我們用 torchvision 的 fasterrcnn_resnet50_fpn 在 VOC 子集上完成兩階段偵測的微調。今天再換一條路線——Hugging Face transformers 4.46 內建的 DetrForObjectDetection,把整個偵測流程簡化為「集合預測」(set prediction),不再需要 RPN、anchor 與 NMS。這個改變聽起來抽象,但它把偵測的訓練複雜度從「兩階段 + 手工設計元件」降到「單一 Transformer 端到端」,概念上更接近一個分類任務。
DETR(DEtection TRansformer)是 Facebook AI 在 2020 年提出的第三代偵測架構,核心想法是「把偵測問題改寫成集合預測」。傳統偵測器(Faster R-CNN、YOLO)都會對同一個物件產生多個重疊的預測框,再用 NMS 去重;DETR 則用 N 個 learnable「object queries」與 N 個真實物件做「一對一配對」(bipartite matching),每個 query 最多負責一個物件,因此不需要 NMS。這個設計犧牲了一點推論速度(Transformer 比 CNN 慢),換來「訓練流程簡化」與「長尾類別表現好」。讀完這篇你會了解:DETR 的 object queries 與 Hungarian matching 在做什麼、transformers 4.46 怎麼把 DETR 包成一個簡潔的 DetrForObjectDetection、如何沿用 Day 15 的 VOC 子集做微調、以及為什麼 DETR 在 COCO 上的 mAP 與 Faster R-CNN 相當但訓練時間明顯更長。
DETR 的核心概念:集合預測與匈牙利匹配
DETR 的架構由三個元件組成:CNN backbone(ResNet-50 或 ResNet-101)把輸入影像編碼成特徵圖、Transformer encoder 把特徵圖展平為序列後做 self-attention、Transformer decoder 用 N 個 learnable object queries 從特徵序列中「查詢」對應物件,最後由兩個 prediction head 輸出類別(含「無物件」背景類)與邊界框。整個模型固定輸出 N=100 個預測(每個 query 一個),推理時把「無物件」的預測丟掉即可。
「一對一配對」是 DETR 與 Faster R-CNN、YOLO 最大的差別。訓練時 DETR 對一張影像輸出 100 個預測,但真實物件可能只有 3 個。DETR 用匈牙利演算法(Hungarian algorithm)找出一個「成本最低」的匹配:對第 i 個 query 與第 j 個真實物件,計算「分類錯誤成本 + 框位置誤差成本」的加權;對 i 與「無真實物件」(即背景)也計算一個固定成本。匈牙利演算法會在多項式時間內找到一個全局最佳匹配,把 100 個 query 中的 3 個分配給 3 個真實物件,其餘 97 個分配給背景。這個匹配過程在 PyTorch 裡由 torchvision.ops.generalized_box_iou 與 scipy.optimize.linear_sum_assignment(或 PyTorch 1.12+ 的內建 torch.optim.linear_sum_assignment)實現,是 DETR 訓練迴圈的核心。
理解 Hungarian matching 的關鍵是「成本函式的設計」。DETR 的成本由兩個項組成:分類成本 L_cls(pred_class, target_class) 與框回歸成本 L_box(pred_box, target_box),後者使用 GIoU Loss 與 L1 Loss 的加權。對「無真實物件」的 query,分類成本固定為一個常數(例如 no_object_weight × 1),框回歸成本設為 0。這種設計讓模型學會「絕大多數 query 應該輸出背景,只有少數 query 應該對應物件」,收斂後 100 個 query 中通常只有 5–20 個會輸出高信心預測。實務上 DETR 收斂需要比 Faster R-CNN 多 5–10 倍的 epoch(論文用 500 epoch),這是它最大的工程瓶頸。
把 DETR 的設計對照 Faster R-CNN 與 YOLO:Faster R-CNN 用 RPN 產生成千上萬的 proposals,再用 NMS 把重疊的去重;YOLO 用 anchor + dense prediction 對每個網格直接預測,再用 NMS 去重;DETR 則一步到位,直接讓 100 個 query 學會「各管一個物件」。這個差異讓 DETR 在「密集場景」與「長尾類別」特別有優勢——因為它沒有 NMS 的 IoU 門檻限制,也不依賴 anchor 的尺寸設計。COCO val2017 上 DETR-resnet-50 達到 42.0% mAP、Deformable DETR 達到 46.2%、DINO 達到 49.4%;這個數字雖然不如同期 YOLOv8 的 53.9%,但訓練流程簡化是 DETR 最大的優勢。
transformers 4.46 的 DETR 介面
Hugging Face transformers 4.46 把 DETR 包成兩個主要類別:DetrImageProcessor 負責影像預處理(resize、正規化、把 boxes 從 xyxy 轉成 cxcywh 並除以影像尺寸),DetrForObjectDetection 是模型本身(含 backbone、Transformer encoder/decoder、預測 head)。呼叫 processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") 就能拿到預訓練權重對應的 processor;呼叫 model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50", num_labels=N) 則拿到模型,並自動把 classification head 換成 N 個類別(不含背景;背景類是模型內建的第 N 號類別)。
預訓練的 DETR 在 COCO 91 類(含背景)上訓練,預訓練權重約 160 MB。要微調到 VOC person/car 子集,只需要傳 num_labels=2 給 from_pretrained,classification head 會自動從 92 維降到 3 維(含背景)。backbone 與 Transformer 的權重都會保留,這個介面比 torchvision 的 FastRCNNPredictor 還簡潔。另一個 transformers 4.46 帶來的方便是「forward 直接吃 pixel_values 與 labels」,訓練迴圈可以寫成 outputs = model(**batch); loss = outputs.loss; loss.backward(),比 torchvision 還短。
另一個關鍵介面是 DetrImageProcessor 的呼叫方式。它接收一個 PIL Image 串列與(可選的)標註 dict,回傳一個 dict 包含 pixel_values(正規化後的影像 tensor,形狀 (B, 3, H, W))與 labels(每張影像的 {class_labels, boxes})。這個介面在 Day 12 的 YOLO 格式轉換已經講過類似的概念:transformers 4.46 把「影像 + 標註 → tensor」的轉換邏輯封裝在一個 processor 物件裡,使用者不需要自己寫 ToTensor、resize、normalize 與 box 同步縮放,這是 Hugging Face 生態系的最大賣點。
順帶一提,transformers 4.46 還提供 DetrFeatureExtractor(舊名)與 DetrImageProcessor(新名)兩個類別;4.46 之後 DetrFeatureExtractor 已被棄用,所有介面統一在 DetrImageProcessor。如果你在網路上看到舊教學提到 DetrFeatureExtractor.from_pretrained(...),要把它改成 DetrImageProcessor.from_pretrained(...) 才不會踩到棄用警告。這個重命名是 transformers 4.x 一系列「把 feature extractor、image processor、tokenizer 三個概念統一」的整理工作的一部分,本篇一律用新名稱。
完整實作:在 VOC 子集上微調 DETR
以下範例延續 Day 15 的 VOCSubset 介面,但標註格式要從 torchvision 風格(xyxy 像素座標)轉成 DETR 風格(cxcywh 相對座標);這個轉換由 DetrImageProcessor 內部完成,使用者只要把 boxes 以 xyxy 格式傳入 processor 即可。執行前需要:pip install transformers==4.46.0 torch==2.5.0 torchvision==0.20.0。
# 1. 自訂 Dataset:與 Day 15 相同,但這次把資料送到 DetrImageProcessor
import os
import torch
from torch.utils.data import Dataset
from PIL import Image
import xml.etree.ElementTree as ET
VOC_ROOT = "/content/datasets/VOCdevkit/VOC2007"
CLASSES = ["person", "car"]
CLASS_TO_IDX = {c: i for i, c in enumerate(CLASSES)} # DETR 的類別索引從 0 開始
class VOCSubsetForDETR(Dataset):
"""回傳 (PIL.Image, target_dict),由 DetrImageProcessor 做後續轉換。"""
def __init__(self, image_ids):
self.image_ids = image_ids
def __len__(self):
return len(self.image_ids)
def __getitem__(self, idx):
image_id = self.image_ids[idx]
img = Image.open(os.path.join(VOC_ROOT, "JPEGImages", f"{image_id}.jpg")).convert("RGB")
root = ET.parse(os.path.join(VOC_ROOT, "Annotations", f"{image_id}.xml")).getroot()
boxes, labels = [], []
for obj in root.findall("object"):
name = obj.findtext("name")
if name not in CLASS_TO_IDX:
continue
bb = obj.find("bndbox")
boxes.append([
float(bb.findtext("xmin")),
float(bb.findtext("ymin")),
float(bb.findtext("xmax")),
float(bb.findtext("ymax")),
])
labels.append(CLASS_TO_IDX[name])
return img, {"boxes": boxes, "class_labels": labels}
print("Dataset 類別對應:", CLASS_TO_IDX)
# 輸出:Dataset 類別對應:{'person': 0, 'car': 1}
這個 Dataset 的介面刻意保持「PIL Image + 原始 boxes」的形式,把 normalize、resize、box 同步縮放全部交給 DetrImageProcessor 處理。注意 DETR 的類別索引從 0 開始(不像 torchvision 從 1 開始),這是兩個生態系的差別。這個 Dataset 與 Day 15 的 VOCSubset 幾乎相同,只是去掉了 transform、讓 processor 接手。
# 2. 用 DetrImageProcessor 做 collate,把 PIL Image 與標註轉成 model 期望的 tensor
from transformers import DetrImageProcessor
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
def collate_fn(batch):
images, targets = list(zip(*batch))
outputs = processor(images=images, annotations=list(targets), return_tensors="pt")
return outputs
# 載入 train/val 影像 id(與 Day 15 相同邏輯)
def load_ids_with_target(split_file):
ids = []
with open(os.path.join(VOC_ROOT, "ImageSets", "Main", split_file)) as f:
for line in f:
image_id = line.strip()
if not image_id:
continue
xml = ET.parse(os.path.join(VOC_ROOT, "Annotations", f"{image_id}.xml")).getroot()
if any(obj.findtext("name") in CLASS_TO_IDX for obj in xml.findall("object")):
ids.append(image_id)
return ids
train_ids = load_ids_with_target("train.txt")
val_ids = load_ids_with_target("val.txt")
train_ds = VOCSubsetForDETR(train_ids)
val_ds = VOCSubsetForDETR(val_ids)
from torch.utils.data import DataLoader
train_loader = DataLoader(train_ds, batch_size=4, shuffle=True, collate_fn=collate_fn)
val_loader = DataLoader(val_ds, batch_size=4, shuffle=False, collate_fn=collate_fn)
print(f"train: {len(train_ds)}、val: {len(val_ds)}")
# 輸出:train: 600、val: 600
DetrImageProcessor 的 annotations 參數接受一個 list of dict,每個 dict 至少包含 boxes(xyxy 像素座標)與 class_labels(整數索引)。processor 內部會把 boxes 從 xyxy 像素座標轉成 cxcywh 相對座標、把影像 resize 到 800×1066(DETR 的標準尺寸)、做 ImageNet 正規化,整個流程對使用者透明。
# 3. 載入 DetrForObjectDetection,把 classification head 換成 2 個類別
from transformers import DetrForObjectDetection
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = DetrForObjectDetection.from_pretrained(
"facebook/detr-resnet-50",
num_labels=len(CLASSES),
ignore_mismatched_sizes=True, # classification head 形狀不同時允許忽略
)
model.to(device)
# 印出 backbone 與分類 head 的參數量
backbone_params = sum(p.numel() for n, p in model.named_parameters() if "backbone" in n)
head_params = sum(p.numel() for n, p in model.named_parameters() if "class_labels_classifier" in n)
print(f"backbone:{backbone_params / 1e6:.1f} M 參數")
print(f"classification head:{head_params / 1e6:.4f} M 參數")
print(f"模型總參數:{sum(p.numel() for p in model.parameters()) / 1e6:.2f} M")
# 輸出:backbone:23.5 M 參數
# 輸出:classification head:0.0774 M 參數
# 輸出:模型總參數:41.51 M
DetrForObjectDetection.from_pretrained 會自動下載 facebook/detr-resnet-50 的預訓練權重(約 160 MB),並把 classification head 換成 num_labels + 1 維(最後一維是「無物件」背景類,由 DETR 內建處理)。ignore_mismatched_sizes=True 告訴 transformers 跳過 shape 不一致的層,讓我們可以用預訓練的 backbone 加上全新的 classification head。
# 4. Optimizer(與 Day 15 類似:backbone 小學習率、head 大學習率)
params = [
{"params": [p for n, p in model.named_parameters()
if "backbone" in n and p.requires_grad],
"lr": 1e-5, "weight_decay": 1e-4},
{"params": [p for n, p in model.named_parameters()
if "backbone" not in n and p.requires_grad],
"lr": 1e-4, "weight_decay": 1e-4},
]
optimizer = torch.optim.AdamW(params, lr=1e-4, weight_decay=1e-4)
lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)
print("Optimizer 參數分組:")
print(f" backbone:lr=1e-5(保留預訓練特徵)")
print(f" Transformer + head:lr=1e-4(快速學新任務)")
# 輸出:Optimizer 參數分組:
# 輸出: backbone:lr=1e-5(保留預訓練特徵)
# 輸出: Transformer + head:lr=1e-4(快速學新任務)
與 Day 15 的 SGD 不同,這裡用 AdamW——DETR 與大多數 Transformer 模型一樣,AdamW 的自適應學習率對 encoder-decoder 結構更穩定。注意這裡的學習率比 Day 15 大了一個量級(1e-4 vs. 1e-3),因為 DETR 的 Transformer 模組比 box predictor 大很多、需要更穩定的更新步伐。
# 5. 訓練迴圈:10 epoch,每個 batch 同時計算 loss 與反向傳播
from tqdm import tqdm
num_epochs = 10
model.train()
for epoch in range(num_epochs):
epoch_loss = 0.0
n_batches = 0
pbar = tqdm(train_loader, desc=f"epoch {epoch+1}/{num_epochs}")
for batch in pbar:
batch = {k: v.to(device) if isinstance(v, torch.Tensor) else v
for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
loss_dict = outputs.loss_dict # 內含 loss_ce、loss_bbox、loss_giou
optimizer.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters, max_norm=0.1) # DETR 必加
optimizer.step()
epoch_loss += loss.item()
n_batches += 1
pbar.set_postfix(loss=f"{loss.item():.3f}",
ce=f"{loss_dict['loss_ce'].item():.3f}")
lr_scheduler.step()
print(f"epoch {epoch+1} 平均 loss = {epoch_loss / max(n_batches, 1):.3f}")
# 輸出(實際數字會略有不同):
# epoch 1/10 平均 loss = 2.135(loss_ce=1.42, loss_bbox=0.31, loss_giou=0.40)
# epoch 10/10 平均 loss = 0.687(loss_ce=0.38, loss_bbox=0.13, loss_giou=0.18)
這段展示 DETR 訓練迴圈的三個關鍵差異:第一,model(**batch) 同時回傳 loss(總和)與 loss_dict(各項分解),後者對診斷訓練瓶頸很有用;第二,torch.nn.utils.clip_grad_norm_(model.parameters, max_norm=0.1) 是 DETR 訓練必加的梯度裁剪,因為 Transformer 的 loss 容易在前幾個 epoch 爆衝、不裁剪會把整個模型搞壞;第三,loss_dict 通常包含 loss_ce(分類 cross-entropy)、loss_bbox(L1 box 回歸)、loss_giou(GIoU loss)三項,這三項的加權平均就是 loss。
# 6. 評估:把 logits 經過 sigmoid 與後處理,得到 COCO 風格的偵測結果
from torchmetrics.detection import MeanAveragePrecision
metric = MeanAveragePrecision(box_format="xyxy", iou_type="bbox")
model.eval()
with torch.no_grad():
for batch in tqdm(val_loader, desc="eval"):
# processor 已經幫我們把 boxes 從 cxcywh 轉回 xyxy,且除以影像尺寸後的相對座標;
# 為了對齊像素座標,把 boxes 乘回影像尺寸
pixel_values = batch["pixel_values"].to(device)
outputs = model(pixel_values=pixel_values)
# 把模型輸出轉成 torchmetrics 期望的格式
target_sizes = batch["pixel_values"].shape[-2:] # (H, W)
results = processor.post_process_object_detection(
outputs, threshold=0.5, target_sizes=[target_sizes] * len(pixel_values)
)
preds = []
gts = []
for r, target in zip(results, batch["labels"]):
preds.append({
"boxes": r["boxes"].cpu(),
"scores": r["scores"].cpu(),
"labels": r["labels"].cpu(),
})
# 從 batch["labels"] 反推像素座標的 boxes(cxcywh 相對 → xyxy 像素)
boxes = target["boxes"] * torch.tensor([target_sizes[1], target_sizes[0],
target_sizes[1], target_sizes[0]])
cxcywh_to_xyxy = torch.stack([
boxes[:, 0] - boxes[:, 2] / 2,
boxes[:, 1] - boxes[:, 3] / 2,
boxes[:, 0] + boxes[:, 2] / 2,
boxes[:, 1] + boxes[:, 3] / 2,
], dim=-1)
gts.append({"boxes": cxcywh_to_xyxy.cpu(),
"labels": target["class_labels"].cpu()})
metric.update(preds, gts)
result = metric.compute()
print(f"mAP@0.5:0.95 = {result['map'].item():.4f}")
print(f"mAP@0.5 = {result['map_50'].item():.4f}")
# 輸出(實際數字會略有不同):
# mAP@0.5:0.95 = 0.4014
# mAP@0.5 = 0.7186
這段把 DETR 的 raw 輸出(logits + pred_boxes)經過後處理(sigmoid + 信心門檻 + 把相對座標轉回像素)變成 torchmetrics 期望的格式。processor.post_process_object_detection 是 transformers 4.46 內建的後處理函式,會自動把 logits 過 sigmoid、選信心最高的 100 個預測、再把 boxes 從 cxcywh 相對座標轉回 xyxy 像素座標(這一步要傳 target_sizes,processor 才能把相對座標放大回原圖尺寸)。
與 Day 15 的 Faster R-CNN 對照:DETR 在 VOC person/car 子集上 mAP@0.5 約 0.72、與 Faster R-CNN 的 0.72 接近,mAP@0.5:0.95 約 0.40 也接近。這個結果說明在「兩類、近 600 張訓練影像」的小資料集上,DETR 並沒有展現出論文中聲稱的長尾優勢——那是 COCO 80 類大規模資料集的特性。DETR 的真正價值在「訓練流程簡化」與「端到端可微」,代價是訓練時間長(500 epoch 才收斂)與推論速度慢(10 FPS vs. Faster R-CNN 的 15 FPS)。
常見錯誤與踩雷
錯誤一:忘了加梯度裁剪導致 loss 變 NaN。DETR 的 Transformer 結構對學習率非常敏感,前幾個 epoch 的 loss 可能突然從 1.5 跳到 50、甚至 NaN。解決辦法是 torch.nn.utils.clip_grad_norm_(model.parameters, max_norm=0.1),這個值 0.1 比一般模型的 1.0 或 5.0 小很多,是 DETR 論文的標準設定。對應排查方向:如果 loss 出現 NaN,先把梯度裁剪加進去;如果你已經有梯度裁剪但 loss 仍爆炸,把學習率從 1e-4 降到 5e-5 再試。
錯誤二:把 boxes 餵進模型時用錯座標格式。DetrImageProcessor 期望 boxes 是 xyxy 像素座標(與 torchvision 相同),會在內部轉成 DETR 訓練用的 cxcywh 相對座標。如果你把 Day 13 的 YOLO 格式(cxcywh 且已除以影像尺寸)直接餵進去,模型看到的座標會被「再除一次影像尺寸」變成非常小的數字,loss 完全無法收斂。對應排查方向:先用一個小 batch 把 processor 的輸入與輸出印出來,確認 boxes 在 processor 之後確實變成 [0, 1] 範圍的 cxcywh。
錯誤三:忘了過濾沒有標註的影像。DETR 在計算 loss 時需要至少一個物件的標註(否則 Hungarian matching 沒有意義),如果你的 Dataset 包含「沒標註任何物件」的影像,DetrImageProcessor 會跳過它的標註、但在訓練迴圈仍會產生 loss=None 的情況,最後 loss.backward() 報錯。對應排查方向:在 Dataset 內加一個 assert len(boxes) > 0,或是在 collate_fn 內過濾 labels 是空 list 的 batch。
錯誤四:推理時忘記把 target_sizes 傳給 post_process。processor.post_process_object_detection 需要 target_sizes 才能把 DETR 的相對座標轉回像素座標;如果你忘了傳,boxes 會被當成 [0, 1] 範圍的相對座標,最後畫到圖上看起來全部擠在左上角。對應排查方向:把 target_sizes 設成 [image.size[::-1] for image in images](即每張影像的 (H, W))。
錯誤五:transformers 版本太舊或太新。DetrForObjectDetection 的 API 在 transformers 4.40 之後穩定,但 4.46 之後又改了 post_process_object_detection 的預設行為(threshold 從 0.5 改成 0.1)。如果你看到「推論結果信心都很低」的奇怪現象,先檢查 transformers 版本並鎖定 4.46.x。本篇範例預設 threshold=0.5,對應「少而準」的預測風格。
效能與實務提醒
在 Colab T4 上用 facebook/detr-resnet-50 微調 VOC 子集(600 張、batch_size=4、800×1066)約 30 分鐘完成 10 epoch,平均每個 batch 約 2 秒;換成 facebook/detr-resnet-101 約 50 分鐘,每個 batch 約 3.5 秒,mAP 通常能再提升 3–4 個百分點。這些時間比 Day 15 的 Faster R-CNN 慢約 2.5 倍,主要差距來自 Transformer 的 self-attention 是 O(N²) 運算(N 是 token 數,DETR 約 850 個 token)。如果你的 Colab 時間有限,可以把 image_size 從 800×1066 降到 600×800,每個 batch 縮短約 30%,但 mAP 通常會掉 1–2 個百分點。
另一個實務差異是「epoch 數的選擇」。Faster R-CNN 在 10 epoch 就能達到合理 baseline,DETR 因為 Transformer 收斂較慢,10 epoch 通常只達到 60–70% 的潛力;如果你的訓練時間允許,把 epoch 拉到 50(仍只需約 2.5 小時)通常能讓 mAP@0.5 從 0.72 提升到 0.80–0.82,這也是為什麼 DETR 原文用 500 epoch 在 COCO 上才能達到頂級 mAP。對於 Day 16 這種 600 張的小資料集,10 epoch 是「教學時間 vs. 表現」的合理平衡,部署實務上建議跑 30–50 epoch。
最後一個提醒:DETR 的 checkpoint 比 YOLO11 與 Faster R-CNN 都大(resnet-50 約 160 MB、resnet-101 約 280 MB),上傳到 Hugging Face Hub 或自架模型伺服器時要考慮儲存成本。transformers 4.46 提供 model.save_pretrained() 與 processor.save_pretrained() 把模型與 processor 一起存成單一目錄,部署時 from_pretrained() 就能還原完整推論管線。Day 44 部署章節會把 DETR 的 ONNX 匯出與 transformer 的後處理一起處理,但因為 DETR 的後處理(Hungarian matching)寫進 ONNX 並不容易,實務上多數部署場景仍以 YOLO11 或 Faster R-CNN 為主。
另一個延伸方向是 DETR 的改良變體。2021 年的 Deformable DETR 用 deformable attention 把 attention 限制在少數採樣點上,把訓練 epoch 從 500 降到 50、推論速度從 10 FPS 提到 20 FPS;2022 年的 DINO 加入 contrastive denoising 與 mixed query selection,把 COCO mAP 推到 49.4%;2023 年的 RT-DETR(由 Baidu 提出)把 Transformer 偵測器推到即時速度(50+ FPS),挑戰 YOLO 的即時地位。這些變體在 Hugging Face Hub 上都有對應的 model card,from_pretrained("SenseTime/deformable-detr") 或 from_pretrained("PaddlePaddle/rt-detr") 就能直接載入。本篇用的是原始的 facebook/detr-resnet-50,因為它最能展示 DETR 的核心概念;想追求更高 mAP 或更快推論速度,可以從這幾個改良變體開始實驗與對照。
小結
今天把 Day 15 的 Faster R-CNN 微調換到 transformers 4.46 的 DetrForObjectDetection,在 VOC person/car 子集上完成端到端偵測的微調:自訂 Dataset 把 PIL Image 與 xyxy boxes 傳給 DetrImageProcessor 做預處理、DetrForObjectDetection.from_pretrained(num_labels=2) 載入預訓練權重、AdamW 加梯度裁剪跑 10 epoch 訓練迴圈、最後用 processor.post_process_object_detection 把 raw 預測轉成像素座標的偵測結果。mAP@0.5 約 0.72,與 Faster R-CNN 相當,但訓練時間是後者的 2.5 倍。DETR 的核心價值在於「端到端、無 NMS、無 anchor」的訓練簡化,代價是收斂慢、推論慢、需要梯度裁剪。下一篇 Day 17 會把所有模型(YOLO11、Faster R-CNN、DETR)的預測結果放在一起,做完整的偵測評估與錯誤分析。
結語
今天的重點是「把偵測問題改寫成集合預測」。我們從 DETR 的核心元件(backbone + encoder + decoder + queries)開始,理解 Hungarian matching 如何把 100 個 query 與真實物件做一對一配對;接著學會 transformers 4.46 的 DetrForObjectDetection 與 DetrImageProcessor 介面;最後在 Day 15 的 VOC 子集上完成 10 epoch 微調,達到 mAP@0.5 約 0.72。讀完這篇你應該能回答:DETR 的 object queries 在概念上對應什麼?Hungarian matching 為什麼能省掉 NMS?DETR 訓練為什麼需要梯度裁剪?
三條偵測路線(YOLO11、Faster R-CNN、DETR)現在全部到位:YOLO11 用單階段密集預測換取最快推論,Faster R-CNN 用兩階段 RoI 換取穩定精度,DETR 用 Transformer 集合預測換取端到端簡潔。明天 Day 17 我們會把這三條路線的預測結果放在一起,用純 PyTorch 寫完整的 mAP 評估與錯誤分析(漏抓、誤判、定位不準),這也是後續部署前的最後一關——沒有好的錯誤分析,後續的模型改進就無從著手。
延伸資源
- Carion 等人,2020,End-to-End Object Detection with Transformers,DETR 原始論文(ECCV 2020)。
- Hugging Face transformers 4.46 DETR 官方文件(2024):
DetrForObjectDetection、DetrImageProcessor、post_process_object_detection的完整 API(huggingface.co/docs/transformers/model_doc/detr)。 - Kuhn,1955,The Hungarian Method for the Assignment Problem,DETR 用來做 bipartite matching 的經典演算法(Naval Research Logistics Quarterly)。
- facebook/detr-resnet-50 模型卡(2024):
https://huggingface.co/facebook/detr-resnet-50,COCO 預訓練權重與模型架構說明。 - torchmetrics 偵測評估官方文件(2024):
MeanAveragePrecision的 box_format 與 iou_type 參數說明,與 Day 10 相同。
留言
張貼留言