前阵子仔细读完了RT-DETR论文,顺手把官方PaddleDetection版和ultralytics YOLO框架下的版本都完整训练了一遍。说实话,这两个路径差别不小,踩坑点也多,但跑通之后对RT-DETR的网络结构和训练逻辑理解会深很多。这篇笔记就专门拆一下RT-DETR的核心思路,以及官方版和YOLO版训练到底该怎么玩,适合正在上手RT-DETR、或者想把Transformer检测器拉到实时场景里的朋友。
1. RT-DETR论文核心思想解读
1.1 从DETR到RT-DETR:为什么要做实时Transformer检测器
DETR系列把目标检测变成了端到端的集合预测问题,省掉了锚框和NMS,这件事本身非常吸引人。但原始DETR有两个致命痛点:一是收敛速度极慢,动辄几百个epoch才能看到效果;二是计算量大,特征图分辨率稍微拉高一点,自注意力的复杂度就爆炸,根本谈不上实时。
RT-DETR要解决的就是这两点。它不再像DETR那样直接在整个特征金字塔上做全局注意力,而是用混合编码器把多尺度特征高效融合,再用IoU-aware query selection从编码器特征里挑出一批高质量的位置向量作为query,最后送到Transformer decoder里做精细回归。这样既保留了端到端的简洁性,又把推理速度拉到和YOLO一个量级。论文里的数据很直观,RT-DETR-R50在COCO上达到53.1% mAP,速度是108 FPS,比同等精度的YOLOv8快不少。
理解RT-DETR,关键要抓住三个设计:混合编码器、IoU-aware query selection、去噪训练。后面逐条拆。
1.2 混合编码器:内尺度交互与跨尺度融合
混合编码器是整个网络加速的核心。它分成两步:先在每个尺度内部用自注意力捕捉全局上下文,也就是所谓的内尺度交互(intra-scale interaction);再用跨尺度融合模块(cross-scale fusion)把不同分辨率的特征进一步整合。
初看你可能会觉得这不就是FPN那套吗?其实不一样。FPN的融合主要靠简单的加法或concatenate,而RT-DETR的跨尺度融合用的是可变性注意力(deformable attention),让不同尺度的特征之间能够自适应地对齐和聚合。整个过程有点像把YOLO的PANet结构换成了Attention版本,全局感受野保留住了,但计算量却没有像标准自注意力那样随分辨率平方增长。
实操中要注意,混合编码器的输出是多个尺度的增强特征,这些特征会同时用于query selection和classification。所以如果你改了输入分辨率,或者换backbone,混合编码器的参数和训练策略都要重新适应。我自己测试下来,如果直接用RT-DETR的默认权重去跑一个分辨率差距很大的数据集,效果会明显下降,因为跨尺度注意力学到的对齐模式是针对原先的尺度分布的。
1.3 IoU-aware query selection与去噪训练
传统DETR的object query是一组固定学习参数,训练时每个query要自己学会去找目标。RT-DETR换了个思路:不再完全随机初始化query,而是从编码器输出特征里选top-K个点作为query,并且使用IoU-aware分支来预测每个query和对应目标的IoU分数。这样做的好处是,网络一开始就知道该关注哪些位置,收敛速度大幅提升,而且最终输出时还可以直接用IoU分数来做排序,替代NMS。
去噪训练也是从DN-DETR借来的技巧。训练过程中会随机破坏一部分GT框,然后让模型学习从被破坏的query里恢复出原框。这个机制的直观理解是:让模型不只是靠匹配结果学,还要学会“纠错”,从而稳定训练过程。实际训练里,去噪部分对学习率、数据增强扰动都比较敏感,尤其是如果你在自定义数据集上训练,GT框很小或者物体密集,去噪噪声比例调太大会导致模型过度依赖GT,反而影响泛化。
2. 官方版训练:PaddleDetection下的RT-DETR复现
2.1 环境安装:PaddlePaddle的坑与解决
官方RT-DETR是基于PaddlePaddle的PaddleDetection实现的,所以训练第一步就是装PaddlePaddle和PaddleDetection。我用的Python 3.9、CUDA 11.8环境,安装命令很简单:
python -m pip install paddlepaddle-gpu==2.5.2.post118 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html pip install paddlepaddle-gpu git clone https://github.com/PaddlePaddle/PaddleDetection.git cd PaddleDetection pip install -r requirements.txt python setup.py install这里的坑有几个:PaddlePaddle的GPU版本很容易装成CPU版本,需要确认import paddle; paddle.utils.run_check()能通过。另外PaddleDetection和PaddlePaddle的版本有对应关系,用最新的PaddleDetection配合老PaddlePaddle会有算子兼容问题。我一开始装了paddlepaddle 2.6但PaddleDetection还是旧的,直接跑就会报rpn相关算子错误,后来统一到2.5.2才稳定。
如果只是做CPU环境测试,可以直接pip install paddlepaddle,但训练基本别想了,RT-DETR哪怕是最小的模型也很吃算力。建议至少8G显存才能把batch size开到2左右。
2.2 数据准备与配置文件修改
官方训练默认使用COCO数据集。如果你想用自己的数据集,需要把标注转成COCO格式,目录结构大致是这样:
dataset/ custom/ annotations/ instances_train.json instances_val.json train/ (图片) val/ (图片)配置文件在configs/rtdetr/rtdetr_r50vd_6x_coco.yml。打开后要改几个关键地方:
num_classes: 80 dataset: type: COCODataSet dataset_dir: dataset/custom class_names: ['cat', 'dog', ...] train: data_source: ann_file: annotations/instances_train.json eval: data_source: ann_file: annotations/instances_val.jsonnum_classes是你的类数量,注意COCO类别中如果是多类,class_names要保持和标注文件里类别顺序一致。训练轮数和学习率策略在文件底部,可以改epochs,一般自定义数据集从1x(12个epoch)开始调,但RT-DETR的调度器和数据增强量比较大,我建议先用6x的训练策略跑一遍,再根据验证集表现往下减。
另外PaddleDetection的训练配置里有个use_gpu标志,多卡训练会在后面命令行里指定,不需要提前改。warmup_iters、base_lr这些参数在_base_里,如果要调整学习率,需要同时改optimizer里面的内容,否则只改顶层base_lr不一定生效。
2.3 官方训练与评估命令
配置改好后,训练非常直接:
python tools/train.py -c configs/rtdetr/rtdetr_r50vd_6x_coco.yml --eval -o use_gpu=True--eval表示每个epoch结束跑一次验证集,方便观察mAP曲线。-o可以用来覆盖配置文件里的任意参数,比如-o snapshot_epoch=1每1个epoch保存一次权重,防止训练中途断电丢太多进度。
我有一台单卡GPU,RT-DETR-R50配合batch size=2,COCO全量训练差不多要3天。自己小数据集配合预训练权重,通常10个epoch左右就能看到收敛趋势。如果显存不够,可以在命令行加-o batch_size=1,但梯度不稳定,建议配合--fp16半精度训练:
python tools/train.py -c configs/rtdetr/rtdetr_r50vd_6x_coco.yml --amp --evalPaddleDetection的AMP开启之后,显存占用能降低将近一半,但我测试过,某些操作符在AMP下数值不稳定,比如混合编码器里的deformable attention,容易出现NaN loss。解决方法是把losses里某些loss保持float32,不过新手的话还是先别开,等完全能复现基准再折腾。
评估和导出的命令:
python tools/eval.py -c configs/rtdetr/rtdetr_r50vd_6x_coco.yml -o weights=output/rtdetr_r50vd_6x_coco/model_final.pdparams python tools/export_model.py -c configs/rtdetr/rtdetr_r50vd_6x_coco.yml -o weights=output/rtdetr_r50vd_6x_coco/model_final.pdparams导出后会得到output_inference/目录,里面有模型和配置文件,可以直接用PaddleInference推理,也可以转成ONNX再转TensorRT部署。
3. YOLO版训练:用ultralytics训练RT-DETR
3.1 为什么要在YOLO框架里训练RT-DETR
官方版本训练效果自然是最准的,但PaddleDetection的生态相对封闭,很多习惯用YOLO系列做项目的人更熟悉ultralytics的API。另外ultralytics团队早就把RT-DETR集成进了YOLOv8的代码库,可以直接用yolo train命令训练RTDETR模型,数据标注格式还是YOLO格式,没有任何额外转换压力。
这个“YOLO版本训练”最大的优势是:工程化程度高,不需要看复杂的Paddle配置,数据集就是一个images/和labels/文件夹,标注是txt文本,训练过程中自动做缓存、数据增强、学习率调度、早停和best权重保留。适合快速迭代和部署验证。
当然代价是,ultralytics实现和官方PaddleDetection实现有一些细节差异,比如数据增强策略、loss组合系数、训练schedule不完全一致,所以同样训练轮数下,精度表现会有几个点的浮动。如果你最后要上生产,我会建议两个版本都训练一遍,YOLO版跑通流程和做快速AB,官方版作为精度上限的参考。
3.2 数据格式转换:从COCO到YOLO格式
YOLO训练需要的数据格式是每个txt文件对应一张图片,每一行是一个目标:
class x_center y_center width height这里的坐标是归一化到0~1之间的。如果你的原始数据是COCO格式,写个小脚本转一下就行,我直接用一段Python处理:
import os import json from PIL import Image def coco2yolo(coco_json, img_dir, out_labels): with open(coco_json, 'r') as f: data = json.load(f) id_to_name = {img['id']: img['file_name'] for img in data['images']} cat_id_map = {cat['id']: i for i, cat in enumerate(data['categories'])} os.makedirs(out_labels, exist_ok=True) for ann in data['annotations']: img_id = ann['image_id'] file_name = id_to_name[img_id] label_path = os.path.join(out_labels, file_name.rsplit('.', 1)[0] + '.txt') img_path = os.path.join(img_dir, file_name) img_w, img_h = Image.open(img_path).size x, y, w, h = ann['bbox'] x_center = (x + w / 2) / img_w y_center = (y + h / 2) / img_h w /= img_w h /= img_h cat_id = cat_id_map[ann['category_id']] with open(label_path, 'a') as out: out.write(f"{cat_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")注意:COCO的bbox格式是[x, y, width, height],左上角原点,转换时一定记得归一化。标签txt文件必须和图片同名,且放在labels/下。ultralytics训练时的数据集配置是:
# dataset.yaml path: D:/projects/rtdetr_yolo/dataset train: images/train val: images/val names: 0: cat 1: dognames里的类别索引必须和txt里的第一列严格对应。类别数说是无所谓,但超过50个类时,names列表要完整,否则训练报错。
3.3 用yolo命令训练RT-DETR
YOLO版本训练RT-DETR非常简单,先安装ultralytics库:
pip install ultralytics然后直接:
yolo train data=dataset.yaml model=rtdetr-l.pt epochs=100 imgsz=640 batch=8第一次运行会从官方权重库下载rtdetr-l.pt作为预训练权重。model参数也可以指定为rtdetr-l.yaml,表示从零开始训练,不加载预训练权重。实际中除非你的数据集和COCO差异极大,否则强烈建议用预训练权重微调,收敛速度和最终精度都要好很多。
如果你习惯用Python脚本,可以使用ultralytics的API:
from ultralytics import RTDETR model = RTDETR('rtdetr-l.pt') model.train(data='dataset.yaml', epochs=100, imgsz=640, batch=8, project='runs/rtdetr', name='exp1')这里有几个参数很重要:
batch:根据显存调整,8G显存建议batch=4,16G可以batch=8。imgsz:RT-DETR原始输入是640,如果你的目标较小可以调到800或960,但显存和训练时间会成倍增加。lr0:初始学习率默认是0.01,自定义小数据集时经常需要降低到0.002~0.005,否则预热阶段就会有较大震荡。
在训练日志里可以看box_loss、cls_loss、dfl_loss和mAP50等指标,RT-DETR的loss里没有DFL,我记得日志里是GIoU和Class损失,不过ultralytics做了统一接口,输出字段基本都是类似的。
训练结束后,模型会保存在runs/rtdetr/exp1/weights/best.pt,直接用model.predict做推理,或者用导出的ONNX做服务部署。
3.4 两种训练路径的对比
我用同一个自定义数据集分别跑了官方版和YOLO版,数据集规模约8000张图,20个类,训练100个epoch。我的感受是:
| 对比项 | 官方PaddleDetection版 | ultralytics YOLO版 |
|---|---|---|
| 环境配置复杂度 | 较高,依赖Paddle生态 | 低,pip安装即可 |
| 数据标注格式 | COCO格式转来转去 | 直接用YOLO txt格式 |
| 训练速度(同batch) | 略快一点,后端优化好 | 相差不大,但显存占用略高 |
| 默认精度 | 略高,推理时NMS策略更细致 | 精调后也能接近,默认差1~2个点 |
| 部署便利性 | 导出PaddleInference/TensorRT | ONNX/TensorRT/NCNN都支持很顺 |
| 自定义数据友好度 | 需要改yaml+json | 只需要一个dataset.yaml |
如果你的项目原本就基于YOLO数据流,建议直接用ultralytics版本,快速验证效果;如果追求精度上限或需要和Paddle产线集成,再上官方版做一次基准。
4. 训练RT-DETR时的常见坑与疑难排查
4.1 显存溢出:大分辨率、大batch怎么取舍
RT-DETR的Transformer decoder部分显存开销比较大,尤其在推理阶段,和YOLO这种纯卷积网络完全不同。我在8G显存的GPU上,imgsz=640、batch=8训练rtdetr-l直接OOM。解决办法有三个方向:
- 降低batch size到2或4;
- 开启梯度累积,ultralytics直接设置
batch=4配合accumulate=4,效果相当于batch=16,但训练时间会拉长; - 使用
amp=True混合精度训练,RT-DETR在AMP下基本能保持精度,显存可以降低约40%。
如果你用的是官方PaddleDetection版,感觉显存不够还可以考虑换小backbone,比如rtdetr_r18vd_5x_coco.yml,一样能跑,只是精度低一些。YOLO版则可以直接用rtdetr-l.pt切换到rtdetr-x.pt,显存需求会显著增加,不建议小卡用户直接上x。
4.2 损失函数调整:GIoU、L1和分类损失的权重平衡
RT-DETR的损失函数包含三部分:分类损失的cls_loss、回归的L1 loss和GIoU loss。官方权重默认是cls_loss=1、L1_loss=5、GIoU_loss=2。这个比例对大多数通用目标检测是合适的,但有几种例外情况需要手动调整:
- 小目标很多:L1 loss对小目标的位置误差比较敏感,权重不变的话会过度关注小目标的边界,导致大目标定位偏差。我写过官方配置文件,把L1权重从5降到3,GIoU权重升到2.5,小目标的Recall提升比较明显。
- 密集场景、物体互相遮挡:GIoU权重可以提升到3以上,让模型更重视框之间的重叠关系。
- 类别不均衡:如果某些类样本很少,分类损失权重可以适当调高,但不要直接调节点权重,优先考虑用focal loss或者调整采样策略。
在ultralytics里改权重要小心,它把RT-DETR的loss挂在model.loss里,想改只能改源码或者用回调函数。实际项目中我一般不动默认权重,先看日志里的各类loss走势,再决定要不要介入。
4.3 训练不收敛:学习率、数据增强与BN层的那些事
我碰到最多的问题是无脑训练导致前50个epoch完全学不动,或者loss直接发散。原因基本都是学习率或数据增强不匹配。
官方版RT-DETR默认是在COCO上预训练过的,使用6xschedule,base_lr=0.0001,warmup 1000步。如果你换到自定义数据集,数据量只有几千张,这个学习率会偏大,导致一开始loss就飞。建议先用官方相同的学习率训练5个epoch看看,如果loss上下跳动严重,就调低到0.00002再试。
YOLO版默认初始学习率是0.01,配合cos_lr=True衰减。但RT-DETR的Transformer模块对学习率比CNN敏感,我试过几次,降到0.005收敛更稳定。如果你用的是rtdetr-x这种大模型,建议初始学习率直接乘以0.5。
另外,无论哪个版本,都要确保输入图片做了归一化到0~1的操作。RT-DETR的backbone对输入均值方差有要求,PaddleDetection内部会做normalize,ultralytics库里也自动处理了。但如果你用ONNX导出后自己写预处理代码,务必保持和训练一致,很多部署生效不佳的案例都出在这个环节。
4.4 推理部署:从PyTorch到ONNX再到TensorRT
YOLO版训练完部署相对简单:
yolo export model=runs/rtdetr/exp1/weights/best.pt format=onnx simplify=True导出的ONNX可以直接用ONNXRuntime跑,也可以转成TensorRT engine加速。RT-DETR的ONNX导出整体比较顺利,只有一点要注意:dynamic批量维度默认关闭,如果你的服务需要动态batch,导出时加上dynamic=True,但TensorRT要先做优化。
官方版导出PaddleInference再转ONNX也支持,命令我前面提过。转好ONNX后,TensorRT用trtexec --onnx=model.onnx --saveEngine=model.engine --fp16编译即可。实测RT-DETR转TensorRT之后,在3080上单张图片推理时间能压到5ms以内,比PyTorch原始模型快3倍以上,和YOLOv8x的推理速度已经很接近。
部署时还有个细节:RT-DETR默认输出是[num_queries, 6],其中6个值是[x1, y1, x2, y2, score, class],已经不需要NMS。有些推理框架不了解这一点,还硬是用NMS后处理,反而把速度拖慢了。YOLO版本导出的模型会有(1, 300, 6)的输出,这里的300就是query数量,直接取置信度大于阈值的框就行。
最后再分享一个小技巧:当你想在自定义数据集上快速看效果时,先用YOLO版训练100个epoch,再把训练好的权重转成ONNX,在官方PaddleDetection的config里加载不了。反过来,如果你想用官方版做最终模型,可以把YOLO版训练好的模型权重作为预训练?不行,结构有差异。最靠谱的方式是:用官方COCO预训练权重在官方框架里按你的数据微调,或者用YOLO版预训练权重在YOLO框架里微调,两者互不干扰。我个人现在习惯是先用YOLO版快速跑通流程和验证指标,确认思路没问题后,再花时间用官方版做一次终版训练,这样时间和算力都能最大化利用。