尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

DETR目标检测:Transformer端到端集合预测原理与实战

DETR目标检测:Transformer端到端集合预测原理与实战 如果你在2020年之前接触过目标检测那么你一定对“两阶段”和“一阶段”这两个词印象深刻。从R-CNN系列到YOLO系列整个领域似乎都在这两条技术路径上做“选择题”是先找候选框再分类还是直接回归出框和类别无论怎么选都绕不开一个核心组件——非极大值抑制NMS。这个后处理步骤像一道无法绕开的“人工关卡”负责剔除冗余的检测框但它带来的计算开销、调参敏感性和端到端训练的障碍一直是工程师们心中的一根刺。直到2020年Facebook AI ResearchFAIR扔出了一颗“炸弹”DETRDetection Transformer。它用一句话宣告了自己的不同“我们不需要NMS也不需要手工设计的锚框Anchor我们直接用Transformer做端到端的目标检测。”这个想法在当时听起来有些“离经叛道”因为Transformer那时还是NLP领域的霸主在视觉任务中大规模应用才刚刚开始。如今DETR及其衍生模型如Deformable DETR已经成为目标检测领域不可忽视的一股力量。但很多开发者初次接触时往往会被其“端到端”的宣传所吸引却在实际部署和调优时感到困惑为什么训练这么慢为什么小目标检测效果不好Transformer的注意力机制到底是怎么用在目标检测上的本文将从实战和源码的角度彻底拆解DETR。我们不只讲“是什么”更要讲清楚“为什么”要这样设计以及在实际项目中“怎么用”和“怎么调”。你将看到DETR如何用一套简洁的编码器-解码器架构将目标检测任务转化为一个集合预测问题并理解其背后的设计哲学与工程权衡。1. DETR解决了什么根本问题—— 告别手工设计组件在深入代码之前我们必须先理解DETR要革谁的命。传统目标检测模型无论是Faster R-CNN还是YOLO其 pipeline 中都包含大量手工设计hand-crafted的归纳偏置inductive bias锚框Anchor Boxes需要预先定义一系列不同尺度、长宽比的候选框。这些超参数对数据集非常敏感换一个场景可能就需要重新调整。非极大值抑制NMS后处理必备用于去除高度重叠的冗余框。NMS本身有一个阈值需要调节这个阈值会影响模型的召回率和精度且其不可微的性质阻碍了真正的端到端训练。区域提议网络RPN或特征金字塔网络FPN这些结构的设计初衷是为了处理多尺度目标但其本身也是复杂的模块。DETR的核心主张是将这些手工设计的组件全部拿掉用一套统一、可学习、端到端的架构来替代。它把目标检测视为一个**集合预测Set Prediction**问题。给定一张图片模型直接输出一个固定长度的无序集合集合中的每个元素就是一个预测目标包含类别和边界框。这种设计带来了几个直观的好处架构极度简洁没有NMS没有Anchor pipeline 清晰。真正的端到端从图像输入到预测结果所有部分都可微可以联合优化。全局推理能力得益于Transformer的自注意力机制模型在做预测时能够“看到”图像的所有位置理论上能更好地处理被遮挡物体或需要上下文理解的目标。然而这种“简洁”并非没有代价最突出的就是训练收敛慢和小目标检测性能相对较弱。这也是后续Deformable DETR等改进模型重点优化的方向。理解原始DETR是理解这一切改进的基石。2. 核心思想如何用Transformer做目标检测DETR的 pipeline 可以概括为四个步骤下图清晰地展示了其数据流向flowchart TD A[输入图像] -- B[CNN Backbonebr如ResNet-50] B -- C[特征图br 位置编码] C -- D[Transformer 编码器br自注意力] subgraph E [Transformer 解码器] direction LR F[对象查询br可学习参数] -- G[交叉注意力br查询与特征交互] end D -- G G -- H[前馈网络 FFN] H -- I[预测头br类别 框坐标] I -- J[输出: 无序预测集合brSet of Predictions] J -- K[二分图匹配br匈牙利算法] K -- L[计算损失br监督训练]下面我们来逐一拆解图中的每个关键模块。2.1 Backbone从图像到特征序列和大多数视觉模型一样DETR需要一个CNN主干网络如ResNet-50来提取图像的视觉特征。输入一张3xH0xW0的图像Backbone会输出一个更低分辨率的特征图例如2048xHxW其中HH0/32,WW0/32。关键的一步是“展平”DETR将这张2D特征图在空间维度上展平变成一个1D的序列。特征图的每个“像素点”实际上是特征图上的一个位置对应序列中的一个元素。因此我们得到一个长度为H*W的序列每个元素是一个2048维的特征向量。为了保留位置信息DETR为这个序列的每一个位置即原特征图上的每个坐标(x, y)计算了一个位置编码Positional Encoding并将其加到对应的特征向量上。这样Transformer才能知道特征来自图像中的哪个位置。# 伪代码示意特征提取与位置编码 import torch import torch.nn as nn # 假设 backbone 输出特征图 feat_map: [batch_size, d_model2048, H, W] batch_size, d_model, H, W feat_map.shape # 1. 将特征图展平为序列 feat_sequence feat_map.flatten(2).permute(0, 2, 1) # 形状: [batch_size, H*W, d_model] # 2. 生成对应的2D位置编码 (sin/cos形式) pos_encoding generate_2d_sincos_pos_encoding(H, W, d_model) # 形状: [H*W, d_model] pos_encoding pos_encoding.unsqueeze(0).expand(batch_size, -1, -1) # 扩展到batch维度 # 3. 将位置编码加到特征序列上 feat_sequence_with_pos feat_sequence pos_encoding2.2 Transformer 编码器-解码器加上位置编码的特征序列被送入一个标准的Transformer编码器。编码器由多层自注意力Self-Attention和前馈网络FFN组成。自注意力机制让序列中的每个特征都能与所有其他特征进行交互从而捕获图像的全局上下文信息。这对于理解物体之间的关系、解决遮挡问题至关重要。解码器的输入有两部分来自编码器的内存Memory即编码器输出的特征序列。对象查询Object Queries这是一组可学习的参数可以理解为模型预先定义好的“提问”。DETR默认有100个对象查询num_queries100这意味着模型最多预测100个物体。每个查询都是一个d_model维的向量。在解码器的每一层对象查询会通过交叉注意力Cross-Attention机制与编码器输出的内存进行交互。这个过程可以理解为每个对象查询“提问”在整个编码后的特征图“知识库”中寻找与自己最相关的信息从而“解码”出一个具体的物体。2.3 预测头与集合预测解码器输出的每个对象查询现在已经是富含信息的向量会分别通过两个前馈网络FFN一个FFN用于分类输出(num_classes 1)个分数其中1代表“无物体”no object类。一个FFN用于边界框回归输出一个4维向量(cx, cy, w, h)表示边界框的中心坐标和宽高通常归一化到[0, 1]区间。因此DETR的最终输出是一个固定大小的集合100个预测结果每个结果包含一个类别概率分布和一个边界框。2.4 二分图匹配与损失函数这是DETR训练中最精妙也最关键的一环。如何为这100个无序的预测分配监督信号即图像中真实存在的物体标签DETR使用了匈牙利算法Hungarian Algorithm来解决这个二分图匹配问题。它寻找一个最优的“配对”方式将预测集合与真实物体集合进行一一对应使得总体的配对代价最小。这个代价由两部分组成分类代价配对的预测类别与真实类别之间的负对数概率。边界框代价配对的预测框与真实框之间的L1损失和广义IoUGIoU损失。匹配完成后只有成功配对的预测才计算分类和框回归损失未配对的预测则被鼓励预测为“无物体”类。# 伪代码示意匈牙利匹配的核心思想 from scipy.optimize import linear_sum_assignment import torch def hungarian_matching(pred_logits, pred_boxes, gt_labels, gt_boxes): pred_logits: [batch_size, num_queries, num_classes1] pred_boxes: [batch_size, num_queries, 4] gt_labels: list of tensor (每个图像的真实标签) gt_boxes: list of tensor (每个图像的真实框) batch_size pred_logits.shape[0] indices [] # 存储每个样本的匹配结果 for i in range(batch_size): num_gt gt_boxes[i].shape[0] # 1. 计算代价矩阵 cost_matrix: [num_queries, num_gt] cost_class -pred_logits[i, :, gt_labels[i]] # 分类代价 cost_bbox torch.cdist(pred_boxes[i], gt_boxes[i], p1) # L1距离 cost_giou 1 - generalized_box_iou(pred_boxes[i], gt_boxes[i]) # GIoU代价 cost_matrix cost_class cost_bbox cost_giou # 加权和 # 2. 填充如果num_queries num_gt用零填充代价矩阵 if num_gt pred_logits.shape[1]: padding torch.zeros(pred_logits.shape[1], pred_logits.shape[1] - num_gt) cost_matrix torch.cat([cost_matrix, padding], dim1) # 3. 执行匈牙利算法找到最优匹配 row_ind, col_ind linear_sum_assignment(cost_matrix.cpu().numpy()) indices.append((row_ind, col_ind)) return indices通过这种匹配方式DETR在训练中动态地学习如何将对象查询“分配”给不同的物体甚至学习到一些空间先验例如某个查询倾向于检测图像左下角的物体。3. 环境搭建与源码获取理解了原理我们开始动手实践。DETR的官方实现基于PyTorch代码清晰是学习的绝佳材料。3.1 环境准备建议使用Python 3.8和PyTorch 1.9。以下命令创建了一个基础的conda环境# 创建并激活环境 conda create -n detr python3.8 -y conda activate detr # 安装PyTorch (请根据你的CUDA版本到PyTorch官网选择对应命令) # 例如对于CUDA 11.3: conda install pytorch torchvision torchaudio cudatoolkit11.3 -c pytorch # 安装DETR所需的其他依赖 pip install cython scipy pip install -U githttps://github.com/cocodataset/cocoapi.git#subdirectoryPythonAPI pip install opencv-python3.2 获取官方源码直接从Facebook Research的GitHub仓库克隆git clone https://github.com/facebookresearch/detr.git cd detr项目结构非常清晰models/: 包含DETR模型的核心定义transformer.py,detr.py。hubconf.py: 用于TorchHub的模型加载配置。main.py: 训练脚本。datasets/: 数据准备相关代码COCO和Panoptic。util/: 工具函数包括框操作、分布式训练等。4. 模型构建核心代码拆解我们深入到models/detr.py和models/transformer.py看看关键模块是如何实现的。4.1 DETR主类 (Detr)Detr类集成了所有组件。其forward函数清晰地展示了数据流# 文件models/detr.py (简化版) class Detr(nn.Module): def __init__(self, backbone, transformer, num_classes, num_queries, aux_lossFalse): super().__init__() self.num_queries num_queries self.transformer transformer hidden_dim transformer.d_model # 分类头输出 num_classes 1 (背景类) self.class_embed nn.Linear(hidden_dim, num_classes 1) # 边界框回归头输出4个坐标 (cx, cy, w, h) self.bbox_embed MLP(hidden_dim, hidden_dim, 4, 3) # 对象查询可学习的参数 self.query_embed nn.Embedding(num_queries, hidden_dim) # 将backbone输出的通道数映射到transformer的隐藏维度 self.input_proj nn.Conv2d(backbone.num_channels, hidden_dim, kernel_size1) self.backbone backbone self.aux_loss aux_loss def forward(self, samples: NestedTensor): # 1. 通过Backbone提取特征 features, pos self.backbone(samples) # 2. 将特征投影到隐藏维度并展平 src, mask features[-1].decompose() assert mask is not None hs self.transformer(self.input_proj(src), mask, self.query_embed.weight, pos[-1])[0] # 3. 通过预测头得到输出 outputs_class self.class_embed(hs) outputs_coord self.bbox_embed(hs).sigmoid() # 使用sigmoid将坐标限制在[0,1] out {pred_logits: outputs_class[-1], pred_boxes: outputs_coord[-1]} if self.aux_loss: out[aux_outputs] self._set_aux_loss(outputs_class, outputs_coord) return out4.2 Transformer编码器 (TransformerEncoder)编码器层就是标准的Transformer编码器层包含多头自注意力和前馈网络。# 文件models/transformer.py (编码器层) class TransformerEncoderLayer(nn.Module): def __init__(self, d_model, nhead, dim_feedforward2048, dropout0.1, activationrelu, normalize_beforeFalse): super().__init__() self.self_attn nn.MultiheadAttention(d_model, nhead, dropoutdropout) # 前馈网络 self.linear1 nn.Linear(d_model, dim_feedforward) self.dropout nn.Dropout(dropout) self.linear2 nn.Linear(dim_feedforward, d_model) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.dropout1 nn.Dropout(dropout) self.dropout2 nn.Dropout(dropout) self.activation _get_activation_fn(activation) self.normalize_before normalize_before def forward(self, src, src_maskNone, src_key_padding_maskNone, posNone): # 添加位置编码 if pos is not None: src2 src pos q k src2 # 自注意力 src2 self.self_attn(q, k, valuesrc2, attn_masksrc_mask, key_padding_masksrc_key_padding_mask)[0] src src self.dropout1(src2) src self.norm1(src) # 前馈网络 src2 self.linear2(self.dropout(self.activation(self.linear1(src)))) src src self.dropout2(src2) src self.norm2(src) return src4.3 Transformer解码器与交叉注意力 (TransformerDecoder)解码器层是DETR的灵魂其核心是交叉注意力机制。# 文件models/transformer.py (解码器层关键部分) class TransformerDecoderLayer(nn.Module): def __init__(self, d_model, nhead, dim_feedforward2048, dropout0.1, activationrelu, normalize_beforeFalse): super().__init__() # 自注意力对象查询之间的交互 self.self_attn nn.MultiheadAttention(d_model, nhead, dropoutdropout) # 交叉注意力对象查询与编码器输出的交互 self.multihead_attn nn.MultiheadAttention(d_model, nhead, dropoutdropout) # 前馈网络 self.linear1 nn.Linear(d_model, dim_feedforward) self.linear2 nn.Linear(dim_feedforward, d_model) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.norm3 nn.LayerNorm(d_model) self.dropout1 nn.Dropout(dropout) self.dropout2 nn.Dropout(dropout) self.dropout3 nn.Dropout(dropout) self.activation _get_activation_fn(activation) def forward(self, tgt, memory, tgt_maskNone, memory_maskNone, tgt_key_padding_maskNone, memory_key_padding_maskNone, posNone, query_posNone): # tgt: 对象查询 [num_queries, batch_size, d_model] # memory: 编码器输出 [H*W, batch_size, d_model] # query_pos: 对象查询的位置编码就是可学习的query_embed # pos: 编码器特征的位置编码 # 第一步对象查询的自注意力 (带查询位置编码) q k tgt query_pos tgt2 self.self_attn(q, k, valuetgt, attn_masktgt_mask, key_padding_masktgt_key_padding_mask)[0] tgt tgt self.dropout1(tgt2) tgt self.norm1(tgt) # 第二步交叉注意力 (查询与内存交互) # 查询侧加入query_pos键侧加入pos tgt2 self.multihead_attn(querytgt query_pos, keymemory pos, valuememory, attn_maskmemory_mask, key_padding_maskmemory_key_padding_mask)[0] tgt tgt self.dropout2(tgt2) tgt self.norm2(tgt) # 第三步前馈网络 tgt2 self.linear2(self.dropout(self.activation(self.linear1(tgt)))) tgt tgt self.dropout3(tgt2) tgt self.norm3(tgt) return tgt关键理解在交叉注意力中query是来自解码器的对象查询加上其位置编码query_pos而key和value都来自编码器的输出memory加上其空间位置编码pos。这使得每个对象查询能够从全局特征中聚合与自身最相关的信息。5. 训练与推理实战5.1 使用官方脚本训练DETR官方提供了在COCO数据集上训练的脚本。这是一个多GPU训练示例# 在detr目录下 python -m torch.distributed.launch --nproc_per_node8 --use_env main.py \ --coco_path /path/to/coco \ --output_dir /path/to/save/checkpoints \ --batch_size 2 \ --epochs 300 \ --lr_drop 200 \ --num_queries 100重要参数解析--coco_path: COCO数据集的根目录。--batch_size: 这是每个GPU的batch size。如果你有8张GPUbatch_size2则总batch size为16。--epochs: DETR需要长时间训练论文中在COCO上训练了300个epoch。--lr_drop: 学习率在第200个epoch时下降10倍。--num_queries: 默认100决定了模型最多预测的物体数量。对于一般场景足够如果图像中物体非常密集可以考虑增加。5.2 加载预训练模型进行推理DETR提供了在COCO上预训练的模型可以通过TorchHub轻松加载import torch import matplotlib.pyplot as plt import torchvision.transforms as T from PIL import Image import requests from io import BytesIO # 加载预训练模型 model torch.hub.load(facebookresearch/detr, detr_resnet50, pretrainedTrue) model.eval() # 定义图像预处理变换 transform T.Compose([ T.Resize(800), # 将短边缩放到800像素 T.ToTensor(), T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) # 加载并预处理图像 url http://images.cocodataset.org/val2017/000000039769.jpg im Image.open(requests.get(url, streamTrue).raw) img_tensor transform(im).unsqueeze(0) # 增加batch维度 # 模型推理 with torch.no_grad(): outputs model(img_tensor) # 后处理过滤低置信度预测 probas outputs[pred_logits].softmax(-1)[0, :, :-1] # 去掉背景类 keep probas.max(-1).values 0.7 # 置信度阈值 # 绘制结果 def plot_results(pil_img, prob, boxes): plt.figure(figsize(16,10)) plt.imshow(pil_img) ax plt.gca() for p, (xmin, ymin, xmax, ymax) in zip(prob, boxes.tolist()): ax.add_patch(plt.Rectangle((xmin, ymin), xmax-xmin, ymax-ymin, fillFalse, colorred, linewidth3)) cl p.argmax() text f{model.config.id2label[cl.item()]}: {p[cl]:0.2f} ax.text(xmin, ymin, text, fontsize15, bboxdict(facecoloryellow, alpha0.5)) plt.axis(off) plt.show() # 转换框坐标 (cx, cy, w, h) - (xmin, ymin, xmax, ymax) bboxes_scaled outputs[pred_boxes][0, keep] bboxes_scaled[:, [0, 2]] bboxes_scaled[:, [0, 2]] * im.size[0] bboxes_scaled[:, [1, 3]] bboxes_scaled[:, [1, 3]] * im.size[1] plot_results(im, probas[keep], bboxes_scaled)运行这段代码你将看到模型对图像中物体的检测结果完全不需要NMS后处理输出已经是过滤后的最终结果。6. 深入分析DETR的优势与挑战6.1 优势总结设计简洁优雅移除了Anchor和NMS pipeline 清晰减少了大量超参数调优工作。全局推理能力Transformer的自注意力机制使模型能够利用图像中所有位置的上下文信息对于处理遮挡、理解物体关系有潜在优势。易于扩展DETR的框架很容易扩展到其他视觉任务如全景分割Panoptic Segmentation只需在解码器后添加一个掩码头即可。6.2 面临的挑战与改进方向DETR并非完美其原始版本存在明显短板这也催生了一系列改进工作训练收敛慢这是最被诟病的一点。DETR在COCO上需要训练500个epoch才能达到最佳效果后续工作优化到108个epoch。原因在于Transformer解码器中的对象查询是随机初始化的它们需要很长时间才能学会关注图像中不同的、有意义的区域。小目标检测性能弱DETR使用CNN backbone下采样32倍后的特征图对于小目标来说其特征信息已经非常微弱。同时Transformer在处理高分辨率特征图时自注意力的计算复杂度是序列长度的平方O(N²)直接使用高分辨率特征会导致计算量爆炸。计算资源需求大Transformer编码器对长序列H*W做自注意力当输入图像较大时内存和计算消耗很高。针对这些挑战社区提出了许多改进方案Deformable DETR引入了可变形注意力Deformable Attention让每个查询只关注特征图上的一小部分关键采样点而不是全部位置。这大幅降低了计算量并加速了训练收敛约10倍同时通过多尺度特征融合显著提升了对小目标的检测能力。Conditional DETR改进了解码器的交叉注意力机制让对象查询能够更早、更准确地聚焦到目标区域加速训练。DAB-DETR将对象查询显式地解码为动态锚框Dynamic Anchor Boxes提供了更好的空间先验。7. 常见问题与排查指南在实际使用DETR时你可能会遇到以下问题问题现象可能原因排查方式解决方案训练Loss不下降或NaN学习率设置过高梯度爆炸数据中存在异常标注如框坐标超出图像。1. 监控训练初期的几个batch的loss值。2. 使用torch.nn.utils.clip_grad_norm_裁剪梯度。3. 检查数据预处理确保框坐标被正确归一化到[0,1]。1. 降低初始学习率如从1e-4开始。2. 添加梯度裁剪max_norm0.1。3. 在数据加载器中添加对非法标注的过滤。显存不足OOM输入图像尺寸过大Batch size过大Transformer层数或隐藏维度太大。1. 使用torch.cuda.empty_cache()清理缓存。2. 使用nvidia-smi监控显存使用。3. 尝试减小--resize参数如从800调到600。1. 减小输入图像尺寸。2. 使用梯度累积--accumulate_grad_batches来模拟大batch。3. 考虑使用混合精度训练AMP。推理速度慢未使用model.eval()和torch.no_grad()图像预处理和后处理耗时。1. 使用Python的cProfile或PyTorch的torch.utils.bottleneck分析代码瓶颈。2. 对比开启/关闭eval模式的速度。1. 确保推理时调用model.eval()并置于torch.no_grad()上下文。2. 对预处理和后处理进行优化或向量化。3. 考虑使用TensorRT或ONNX进行模型加速。小目标漏检严重Backbone下采样率过高小目标特征丢失原始DETR架构缺陷。1. 可视化特征图看小目标区域是否有响应。2. 在验证集上统计不同尺度目标的AP。1. 使用特征金字塔网络FPN或类似结构的多尺度特征。2.强烈建议使用改进模型如Deformable DETR它原生支持多尺度特征。预测框数量不足100个这是正常现象。模型输出的100个查询中大部分会预测为“背景”。只有置信度高于阈值的才会被保留。检查outputs[pred_logits].softmax(-1)[:, :, :-1].max(-1).values看有多少查询的置信度高于你的阈值如0.7。调整置信度阈值。如果希望召回更多目标可以降低阈值如0.5但可能会引入更多误检。8. 最佳实践与工程建议从预训练模型开始除非有海量数据和计算资源否则不要从头开始训练DETR。优先使用在COCO上预训练的模型进行微调Fine-tuning。针对自己的数据集微调python main.py \ --coco_path /path/to/your/coco_format_dataset \ --output_dir /path/to/output \ --resume /path/to/pretrained/detr-r50.pth \ --epochs 50 \ --lr 1e-5 \ --batch_size 4 \ --num_queries 100将你的数据集转换为COCO格式并修改datasets/coco.py中的类别映射。使用改进版本对于新项目优先考虑 Deformable DETR。它在收敛速度和小目标检测上都有巨大提升是更实用的选择。其GitHub仓库同样来自FAIR。注意对象查询数量num_queries决定了模型最大检测数量。对于交通监控等密集场景可以适当增加如150或200。但增加查询数会线性增加解码器的计算量。理解训练时间即使使用Deformable DETR在中等规模数据集上微调也需要数十个epoch。请预留足够的训练时间和GPU资源。部署优化Transformer的解码器在推理时是串行执行的不利于并行化。可以考虑将解码器层进行融合或使用更高效的注意力实现来提升推理速度。DETR的出现标志着目标检测领域从大量手工设计时代向更简洁、更统一的端到端学习时代迈出了坚实的一步。它可能不是所有场景下的最优解但其思想深刻影响了后续的许多工作。通过本文对原理和源码的拆解希望你能不仅学会如何使用DETR更能理解其设计精髓从而在面对新的视觉任务时能够思考是否可以用“集合预测”和“Transformer”的视角来优雅地解决问题。真正的创新往往始于对固有范式的重新审视。
返回列表