跳到主要內容

CV Day 16 DETR 與 Transformer 偵測器

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 相同。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...