【项目手册 | YOLO26-Dual】从0到1复现、融合实验与维护全流程
[TOC]
YOLO26-Dual 项目手册
写在前面
YOLO26-Dual此项目名字就是临时起意取的,YOLO项目推出了最新的26版本(模仿苹果这一块\.)本项目基于YOLO26仓库修改而来,理论上支持后续YOLO版本(只要结构不变)。Dual意为“双重”,所以拼接一下就“临时起意”了一个YOLO26-Dual
本仓库目前仅供内部学习使用,设为Private状态。
本项目通过Vibe Coding实现。
本说明文档将使你学会:
- 刚接触 YOLO 也能独立跑通;
- 做实验能稳定复现并横向对比融合模块;
- 后续接手维护有明确的排障与同步路线。
1. 项目定位与边界
1.1 项目功能
一句话:
YOLO26-Dual = 双骨干(RGB/IR)+ 多层融合(P3/P4/P5)+ YOLO 检测头。
与单流基线 YOLO 相比,变化点是:
- 输入从单张图变成
rgb + ir双流输入; - Backbone 分成两条独立分支(权重不共享);
- 在多尺度位置做融合,融合模块可替换;
- 训练器/验证器/推理器都走双流逻辑。
1.2 Can / Can’t
Can
- Detect 任务下的 RGB+IR 双流训练、验证、推理;
- 多种融合模块配置的可行性验证与实验;
- 支持 IR 缺失时灰度回退,保证流程可跑;
- 支持 DDP,让训练更迅速。
Can’t
-
不建议开启
compile=True(动态图与自定义路由的兼容性可能会出现问题); -
不建议开启
multi_scale>0(我们需要让图像素级对齐,对rgb变换了不能保证对ir也变换);多尺度训练(Multi-scale)的原理:在训练过程中,每隔几个 batch,训练器会随机改变输入图像的尺寸(例如从 640 改为 512 或 768),以增强模型对不同尺度目标的鲁棒性。
-
不建议在双流训练时使用非同步几何增强(原因基本同上);
几何增强:包括随机旋转(Rotation)、平移(Translation)、剪切(Shear)、翻转(Flip)、以及 Mosaic(马赛克拼图)和 MixUp 等改变像素位置的操作。
-
以 detect 为主,其他任务暂不作为本项目目标(实则是其他任务的代码没改,不保证能用)。
1.3 设计原则
- opt-in 设计:只有命中双流条件(如
dual_stream: True/rgbir配置)才进入双流路径; - 向后兼容:不影响单流 YOLO 的正常训练与推理;
- 可替换融合层:融合模块通过在注册表中注册管理,便于实验与扩展;
- 最小侵入改造:尽量复用原生解析器与头部结构,降低上游同步成本,便于上游仓库Ultralytics更新时同步更新。
2. 快速开始
2.1 环境
cd YOLO26-Dualconda create -n yolo26dual python=3.10 -yconda activate yolo26dualpip install -U pippip install -e .如需测试依赖:
pip install -e ".[dev]"2.2 数据准备(以 LLVIP 数据集为示例,可替换为任意 RGB+IR 数据集)
python scripts/prepare_llvip.pyprepare_llvip.py 此脚本可将 LLVIP 数据集处理为本项目可用的路径结构。
每个数据集对数据存放的格式不同,对此强烈建议让AI帮你处理。详见本文AI提示词部分。
2.3 健康检查
python scripts/verify_dataset.py --data ultralytics/cfg/datasets/LLVIP.yamlpython scripts/verify_all_modules.pypython scripts/audit_tfblock_gates.pypytest tests/test_dual_stream.py -vverify_dataset.py 此脚本用于快速检查数据集配置文件是否符合双流YOLO训练需要。
verify_all_modules.py 生产环境中无用,
- 作用:全模块普查。
- 详情:它会自动遍历项目中定义的所有融合模块,挨个加载并跑一次前向传播和反向传播。
- 目的:确保每个模块都没语法错误,且梯度能回传,防止某个冷门模块坏了没人发现。但是生产环境会有人每次运行前检查所有融合模块吗?
2.4 跑一个 10 epoch baseline
这里假定你本地安装好了修改后的Ultralytics库,已经切换了环境
yolo detect train \ model=ultralytics/cfg/models/26/yolo26-rgbir.yaml \ data=ultralytics/cfg/datasets/LLVIP.yaml \ epochs=10 imgsz=640 batch=16 device=0 \ optimizer=AdamW compile=False multi_scale=0 augment=False \ project=runs/detect name=baseline_10e注:这里用
LLVIP.yaml只是示例。若你使用 KAIST / FLIR / M3FD 或自建数据集,只需要把data=...换成对应 YAML,并确保配置文件中包含ir_train/ir_val字段。
3. 双流架构全景图
解读这张图:
- 双骨干独立提特征;
- 融合发生在 P3/P4/P5;
- Head 部位仍是 YOLO 家族思路,只是输入换成了融合特征;
- 模块替换主要发生在 Fusion 层,而不需要将整个 head 重写。
4. 代码落点总览
4.1 路由入口
ultralytics/models/yolo/model.py_is_dual_stream():判断是否双流task_map:双流 detect 路由到专用 model/trainer/validator/predictor
4.2 模型解析与前向核心
ultralytics/nn/tasks.py_FUSION_REGISTRY:融合模块映射表parse_dual_stream_model():构建双骨干+融合+头parse_dual_stream_head():融合输出接入 headDualStreamDetectionModel:_predict_once()和loss()- 关键实现:把所有层拼成 flat Sequential 的
self.model,确保 stride / EMA / state_dict 与 Ultralytics 主流程兼容
4.3 数据与训练验证
ultralytics/data/dataset.pyYOLODualStreamDataset
ultralytics/models/yolo/detect/train.pyDualStreamDetectionTrainer
ultralytics/models/yolo/detect/val.pyDualStreamDetectionValidator
ultralytics/models/yolo/detect/dual_stream_predict.pyDualStreamPredictor
4.4 融合模块实现
ultralytics/nn/modules/block.py- 基础:
ChannelFusion/AddFusion/TransformerFusion - 注意力块:
TransformerFusionBlock - 扩展方法:
TFusion、CombinedFusionBlocks、SFEG、CAFF、ICFusion、MaxFusion、ConcatFusion、Add、DFSC、CSSA
- 基础:
4.5 双流判定与解析流程
判定逻辑(_is_dual_stream())
if self._is_dual_stream(): task_map["detect"] = { "model": DualStreamDetectionModel, "trainer": DualStreamDetectionTrainer, "validator": DualStreamDetectionValidator, "predictor": DetectionPredictor # MVP 回退 }模型会从这几类信息判断是否走双流路由:
- 配置文件名是否包含
rgbir; - YAML 里是否有
dual_stream: True; - 已加载模型是否是
DualStreamDetectionModel。
解析流程(parse_dual_stream_model)
这块是上游同步后最容易被覆写的逻辑之一,在同步合并冲突时一定优先核对。
4.6 修改文件清单
| 文件路径 | 修改类型 | 说明 |
|---|---|---|
nn/modules/block.py | 新增 | TFBlock, TFusion, Combined, SFEG, CAFF, ICFusion, MAxFusion, Concat, Add, DFSC, CSSA 等 11 个融合模块 |
nn/modules/__init__.py | 修改 | 导出新增的融合模块 |
nn/tasks.py | 新增 | parse_dual_stream_model(), parse_dual_stream_head(), DualStreamDetectionModel |
cfg/models/26/yolo26-rgbir-*.yaml | 新建 | 11 个实验性双流模型配置 (tfblock-all, tfusion, sfeg 等) |
cfg/datasets/LLVIP.yaml | 新建 | LLVIP 双流数据集配置 |
data/dataset.py | 新增 | YOLODualStreamDataset 类 |
models/yolo/detect/train.py | 新增 | DualStreamDetectionTrainer 类 |
models/yolo/detect/val.py | 新增 | DualStreamDetectionValidator 类 |
models/yolo/model.py | 修改 | _is_dual_stream() 方法 + 双流 task_map 路由 |
tests/test_dual_stream.py | 新建 | 双流专用测试套件 |
5. 数据链路全生命周期
如果你只会“跑命令”,但不理解数据是怎么走的,后面遇到 bug 会非常痛苦。
双流数据在训练中的生命周期如下:
关键张量形状(默认)可按下面理解:
| 阶段 | 张量 | 形状示例 |
|---|---|---|
| Dataset 输出 | img | (3, H, W) |
| Dataset 输出 | ir_img | (3, H, W) |
| Collate 后 | batch["img"] | (B, 3, H, W) |
| Collate 后 | batch["ir_img"] | (B, 3, H, W) |
| 进入模型 | {"rgb": img, "ir": ir_img} | 双输入 dict |
额外说明(验证阶段):
- 验证器基类默认是
model(batch["img"])单输入; - 双流验证器会在 forward 前注入
model._ir_cache; - 模型从
_ir_cache读取 IR,完成双流验证路径。
这就是为什么 val.py 里有 IR 注入逻辑。
6. 数据准备与配置(以 LLVIP 为例)
6.1 示例:LLVIP 结构
datasets/LLVIP/├── rgb/│ ├── train/│ │ └── images/│ │ ├── 000001.jpg│ │ └── ...│ └── val/│ └── images/│ └── ...├── ir/│ ├── train/│ │ └── images/│ │ ├── 000001.jpg│ │ └── ...│ └── val/│ └── images/│ └── ...└── rgb/ ├── train/ │ └── labels/ │ ├── 000001.txt │ └── ... └── val/ └── labels/ └── ...硬约束:
- RGB 与 IR 必须同名配对;
- 标注在 RGB 路径下(YOLO 规则);
- IR 路径仅存图像,不存 labels。
6.2 数据 YAML
ultralytics/cfg/datasets/LLVIP.yaml:
path: ../datasets/LLVIPtrain: rgb/train/imagesval: rgb/val/imagesir_train: ir/train/imagesir_val: ir/val/imagesnames: 0: person⚠️ 注意:如果不写
ir_train/ir_val,你跑出来的是“伪双流”(RGB 转灰度作为 IR)。
如果找不到对应的 IR 图像,系统自动使用 *灰度回退*(将 RGB 转为灰度图并复制到 3 通道作为合成 IR)。适用于管线验证,但实际效果需要真实 IR 数据。
6.3 自定义数据集YAML模板
path: ../datasets/YourDatasettrain: rgb/train/imagesval: rgb/val/imagesir_train: ir/train/imagesir_val: ir/val/imagesnames: 0: person 1: car如果你的原始数据不是这个结构,先写一个预处理脚本把目录整理成这个格式再训练。(可让 AI 帮忙完成)
6.4 避免隐式失败:数据集校验工具
由于 YOLODualStreamDataset 在找不到 IR 图像时会静默回退到灰度图,这可能导致你误以为自己在跑双流,实际跑的是“假双流”。
强烈建议在训练前运行校验脚本:
python scripts/verify_dataset.py --data ultralytics/cfg/datasets/LLVIP.yaml- 绿色 ✅ Success:说明所有 RGB 都有对应的 IR 图片。
- 红色 ❌ Failed:会列出缺失 IR 的文件名。
7. 训练前的核心规则
这四条不要省,省了大概率踩坑:
规则1:optimizer != "Muon"
原因:TransformerFusionBlock 包含 1D 可学习参数 (LearnableCoefficient, LearnableWeights),Muon 优化器要求参数为 2D。
可选的优化器类型:SGD, Adam, RMSprop.
不要用 auto。因为 auto 策略有时会根据模型结构自动切到 Muon或其他实验性优化器,为了避免不可控的报错,还是建议显式指定。
规则2:compile=False
原因:双流路径包含自定义路由,当前阶段不建议 compile。
规则3:multi_scale=0
原因:RGB/IR 需要同步缩放,异步会破坏配准。
规则4:augment=False
原因:几何增强若不双流同步,错配会直接污染训练。当前实现里也不建议把 Mosaic/MixUp 直接用于 IR 分支。
8. 训练
8.1 路线总览
8.2 第一阶段:健康检查
python scripts/verify_dataset.py --data ultralytics/cfg/datasets/LLVIP.yamlpytest tests/test_dual_stream.py -v8.3 第二阶段:基线短训
yolo detect train \ model=ultralytics/cfg/models/26/yolo26-rgbir.yaml \ data=ultralytics/cfg/datasets/LLVIP.yaml \ epochs=10 imgsz=640 batch=16 device=0 \ optimizer=AdamW compile=False multi_scale=0 augment=False \ project=runs/detect name=baseline_10e或使用Python脚本类型:
model.train( data="LLVIP.yaml", epochs=100, imgsz=640, batch=64, # 2×RTX 3090 (24GB) 推荐 device="0,1", # 多卡 DDP 训练 optimizer="AdamW", # 必须 workers=8, compile=False, # 必须关闭 multi_scale=0, # 必须关闭 augment=False, # 推荐关闭 close_mosaic=10,)8.4 第三阶段:正式训练 (以 TransformerFusionBlock为例)
python scripts/train_fusion_tfblock.py该脚本脚本里有 batch 回退(64→32→16),比手动盲试更稳。
备注:当前脚本默认
device="0,1"(双卡)。如果你只有一张卡,建议先改成device="0"再跑。
8.5 理解训练日志
训练过程中你会常见到这些字段:
| 字段 | 说明 |
|---|---|
Epoch | 当前/总 epoch |
GPU_mem | 每卡 GPU 显存使用 |
box_loss | 边框回归损失 (越小越好) |
cls_loss | 分类损失 (越小越好) |
dfl_loss | 分布焦点损失 (越小越好) |
Instances | 当前批次中的目标实例数 |
Size | 输入图像尺寸 |
如果 loss 明显下降但 mAP 长时间不动,优先检查:
- 数据标注与配对是否正确;
- 是否出现了“伪双流”(IR 没真正参与);
- 学习率和 batch 是否过激导致震荡。
| 指标 | 说明 |
|---|---|
Box(P) | 精确率 (Precision) |
R | 召回率 (Recall) |
mAP50 | IoU=0.5 时的均值平均精度 |
mAP50-95 | IoU=0.5:0.95 的均值平均精度 (主要指标) |
8.6 资源与规模参考(经验之谈)
推荐 batch(640 输入)
| GPU 配置 | 建议 Batch Size | 预估显存 |
|---|---|---|
| 1×RTX 3090 (24GB) | 32 | ~16 GB |
| 2×RTX 3090 (24GB) | 64 | ~16 GB/卡 |
| 1×RTX 4090 (24GB) | 32 | ~16 GB |
| 1×A100 (80GB) | 128 | ~50 GB |
参数量参考(示例)
根据当前 artifacts/tfblock_all/final_report.md:
- TFBlock-All(n-scale)总参数量约 24.7M;
- 结构中
TransformerFusionBlock数量为 3。
9. 融合模块配置
9.1 常用的索引对照
| 骨干层索引 | 对应层级 | 常见用途 |
|---|---|---|
4 | P3 | 高分辨率融合点 |
6 | P4 | 中分辨率融合点 |
10 | P5 | 低分辨率融合点 |
9.2 配置文件位置
ultralytics/cfg/models/26/├── yolo26-rgbir.yaml # 基线: ChannelFusion (P3/P4) + TransformerFusion (P5)└── yolo26-rgbir-tfblock-all.yaml # 实验: TransformerFusionBlock (全阶段)9.3 基线配置: yolo26-rgbir.yaml
| 特征层 | 骨干层索引 | 分辨率 (640px) | 通道数 | 融合模块 | 说明 |
|---|---|---|---|---|---|
| P3 | 4 | 80×80 (1/8) | 128 (n-scale) | ChannelFusion | Concat + 1×1 Conv,轻量高效 |
| P4 | 6 | 40×40 (1/16) | 128 (n-scale) | ChannelFusion | 同上 |
| P5 | 10 | 20×20 (1/32) | 256 (n-scale) | TransformerFusion | 多头注意力 + FFN |
9.4 TFBlock-All 配置: yolo26-rgbir-tfblock-all.yaml
| 特征层 | 骨干层索引 | 分辨率 (640px) | 通道数 | 融合模块 | 说明 |
|---|---|---|---|---|---|
| P3 | 4 | 80×80 (1/8) | 128 (n-scale) | TransformerFusionBlock | 交叉注意力融合,16×16 网格 |
| P4 | 6 | 40×40 (1/16) | 128 (n-scale) | TransformerFusionBlock | 同上 |
| P5 | 10 | 20×20 (1/32) | 256 (n-scale) | TransformerFusionBlock | 同上 |
9.5 融合配置段语法
fusion: - {rgb_idx: 4, ir_idx: 4, module: <模块名>, args: [参数列表]} - {rgb_idx: 6, ir_idx: 6, module: <模块名>, args: [参数列表]} - {rgb_idx: 10, ir_idx: 10, module: <模块名>, args: [参数列表]}rgb_idx/ir_idx: 骨干网络输出层索引 (两个骨干结构相同)module: 融合模块类名 (必须在_FUSION_REGISTRY中注册)args: 传递给模块构造函数的参数
9.6 可用融合模块
| 模块 | 位置 | 参数 | 原理 |
|---|---|---|---|
ChannelFusion | nn.modules.block | [c_out] | 拼接 + 1×1 Conv |
AddFusion | nn.modules.block | [c_out] | 投影 + 逐元素加 |
TransformerFusion | nn.modules.block | [c_out, num_heads] | 简单 MHA + FFN |
TransformerFusionBlock | nn.modules.block | [vert, horz, h] | 交叉注意力 + 空间锚点 |
TFusion | nn.modules.block | [c_out] | 3D 卷积 + 变形卷积 |
CombinedFusionBlocks | nn.modules.block | [c_out] | 多模块集成融合 (Ensemble) |
SFEG | nn.modules.block | [c_out] | 空间特征提取与门控 |
CAFF | nn.modules.block | [c_out] | 通道注意力特征融合 |
ICFusion | nn.modules.block | [c_out] | 交互式通道融合 |
MaxFusion | nn.modules.block | [] | 逐元素取最大值 |
ConcatFusion | nn.modules.block | [c_out] | 拼接 + 降维 (同 ChannelFusion) |
Add | nn.modules.block | [] | 简单逐元素加 (无投影) |
DFSC | nn.modules.block | [c_out] | 密集特征选择卷积 |
CSSA | nn.modules.block | [] | 通道-空间自注意力 |
当前仓库可直接使用的实验配置:
yolo26n-rgbir-add.yaml->Addyolo26n-rgbir-caff.yaml->CAFFyolo26n-rgbir-combined.yaml->CombinedFusionBlocksyolo26n-rgbir-concatfusion.yaml->ConcatFusionyolo26n-rgbir-cssa.yaml->CSSAyolo26n-rgbir-dfsc.yaml->DFSCyolo26n-rgbir-icfusion.yaml->ICFusionyolo26n-rgbir-maxfusion.yaml->MaxFusionyolo26n-rgbir-sfeg.yaml->SFEGyolo26n-rgbir-tfblock-all.yaml->TransformerFusionBlockyolo26n-rgbir-tfusion.yaml->TFusion
9.7 自定义融合配置示例
# 混合使用不同融合模块fusion: - {rgb_idx: 4, ir_idx: 4, module: ChannelFusion, args: [512]} - {rgb_idx: 6, ir_idx: 6, module: AddFusion, args: [512]} - {rgb_idx: 10, ir_idx: 10, module: TransformerFusionBlock, args: [16, 16, 4]}9.8 模型规模选择
模型支持多种规模 (scale),通过 scales 字段定义:
scales: n: [0.50, 0.25, 1024] # nano: 深度 0.50, 宽度 0.25, 最大通道 1024 s: [0.50, 0.50, 1024] # small: 深度 0.50, 宽度 0.50默认使用
n(nano) 规模。如未在文件名中指定 scale (如yolo26n-rgbir.yaml),系统会自动选择n。
9.9 建议实验策略
- 先进行模块可用性验证;
- 再做 1~3 epoch 快速筛选;
- 挑 Top 候选做 50~100 epoch 长训;
- 只比较同条件实验(相同 seed、batch、imgsz、数据划分)。
10. 审计门(G1~G5)与证据
10.1 审计门定义
- G1:前向传播与形状检查
- G2:IR 敏感性(改 IR 后输出应变化)
- G3:IR 梯度回流(IR 分支应有梯度)
- G4:模型结构检查(例如 TFBlock 数量)
- G5:解析器语义审计(AST 级检查)
10.2 当前关键审计指标
| 项目 | 结果 |
|---|---|
| IR sensitivity delta | 159772.03125 |
| IR grad max | 6.3955717 |
| parse_model AST Hash | 6971f247988db3686ae172b00eedeac49bc61544cfdd65127b1a494871156845 |
| 10e 验证 best mAP50 | 0.0495 |
| 10e 验证 best mAP50-95 | 0.0161 |
对应文件:
artifacts/tfblock_all/ir_sensitivity.jsonartifacts/tfblock_all/ir_grad_flow.jsonartifacts/tfblock_all/parse_model_semantic_proof.txtartifacts/tfblock_all/final_report.md
11. 推理、验证与“假双流”排查
11.1 验证
from ultralytics import YOLO
model = YOLO("runs/detect/baseline_10e/weights/best.pt")metrics = model.val(data="ultralytics/cfg/datasets/LLVIP.yaml")print("mAP50:", metrics.box.map50)print("mAP50-95:", metrics.box.map)11.2 推理(自动配对,隐式IR)
results = model.predict(source="datasets/LLVIP/rgb/val/images/010001.jpg")11.3 推理(显式 IR)
results = model.predict( source="datasets/LLVIP/rgb/val/images/010001.jpg", ir_source="datasets/LLVIP/ir/val/images")11.4 AutoBackend 注意事项
autobackend.py 对 dict 输入({"rgb":..., "ir":...})的支持主要面向 PyTorch 后端。
简而言之:
- 训练/验证/本地 PyTorch 推理没问题;
- 非 PyTorch 推理后端要额外验证输入路径是否兼容。
12. 常见错误速查
12.1 AssertionError: len(G.shape) == 2
- 原因:优化器路径不兼容 1D 参数;
- 处理:显式
optimizer="AdamW"。
12.2 Input type Float and weight type Half
- 原因:AMP 下 dtype 不一致;
- 处理:
- 融合模块输出 cast 回输入 dtype;
- 检查双流
loss()是否在 autocast 上下文中重算 preds。
12.3 KeyError: 'ir_img'
- 原因:数据配置或加载链路断了;
- 处理:
- 检查
ir_train/ir_val是否存在; - 检查 IR 路径与同名配对是否成立;
- 检查是否走到了
YOLODualStreamDataset。
- 检查
12.4 输出对 IR 不敏感
python scripts/audit_tfblock_gates.py重点看 Gate2 与 Gate3。
12.5 git pull 后冲突或异常
先保护本地改动:
git add .git commit -m "wip" # 或 git stash再进行同步,不要直接硬拉。具体可询问 AI 如何解决。
13. 如何新增融合模块
建议严格按这 6 步执行:
- 在
ultralytics/nn/modules/block.py新增模块类; - 在
ultralytics/nn/modules/__init__.py导出; - 在
tasks.py的_FUSION_REGISTRY注册; - 如参数签名特殊,在
parse_dual_stream_model()增加分支; - 新建配置
ultralytics/cfg/models/26/yolo26n-rgbir-xxx.yaml; - 跑验证脚本与单测。
最小接口模板:
class YourFusion(nn.Module): def __init__(self, c_rgb, c_ir, c_out, **kwargs): super().__init__() ...
def forward(self, x_rgb, x_ir): output = ... return output.to(x_rgb.dtype) # AMP 兼容建议解析器里常见的通道处理逻辑(建议保持一致):
c_out = f_args[0] if f_args else c_rgbc_out = make_divisible(min(c_out, max_channels) * width, 8)如果你想要完整示例(比如 SEFusion 从实现到注册到 YAML),可以直接对照:
docs/zh/融合模块开发指南.md的完整示例节;- 现有实现
ChannelFusion(最简)与TransformerFusionBlock(复杂)。
14. 上游同步与长期维护
14.1 同步流程图
14.2 常规命令
git remote add upstream https://github.com/ultralytics/ultralytics.git # 首次git fetch upstreamgit log --oneline main..upstream/main | head -20git checkout maingit merge upstream/main14.3 冲突处理最小流程
当你在合并输出里看到 CONFLICT 或冲突标记时:
<<<<<<< HEAD...你的内容...=======...upstream内容...>>>>>>> upstream/main处理原则:
- 上游 bugfix/性能修复优先保留;
- 双流核心能力必须补回;
- 处理后确认冲突标记已清理干净。
14.4 冲突高发文件
ultralytics/nn/modules/block.pyultralytics/nn/tasks.pyultralytics/models/yolo/model.pyultralytics/data/dataset.pyultralytics/models/yolo/detect/train.pyultralytics/models/yolo/detect/val.py
14.5 合并后的最低验证
python -c "from ultralytics import YOLO; YOLO('ultralytics/cfg/models/26/yolo26-rgbir.yaml'); print('dual build ok')"python -c "from ultralytics import YOLO; YOLO('yolo26n.yaml'); print('single build ok')"python scripts/audit_tfblock_gates.py14.6 同步频率建议
| 场景 | 建议频率 |
|---|---|
| 日常开发 | 1~2 周一次 |
| 准备发版前 | 发版前至少一次 |
| 上游重大版本更新 | 及时同步 |
| 上游安全修复发布 | 尽快同步 |
15. 脚本与产物说明
15.1 常用脚本
/scripts 下当前常用脚本:
verify_dataset.py:校验 RGB/IR 数据集配对完整性prepare_llvip.py:LLVIP 解压+结构检查+配对检查verify_all_modules.py:11 模块前向+梯度可用性检查audit_tfblock_gates.py:G1~G4 审计audit_parse_semantic.py:G5 解析器语义审计train_fusion_tfblock.py:TFBlock 正式训练(含 batch 回退)train_tfblock.py:smoke/full 入口训练compare_fusion_feasibility.py:融合可行性对比generate_fusion_configs.py:批量生成融合配置audit_fusion_migration.py:迁移模块审计verify_tfblock_roundtrip.py:权重回环验证
15.2 artifacts 产物
artifacts/tfblock_all/ 下你会看到这类文件:
| 文件 | 含义 |
|---|---|
final_report.md | 审计与训练阶段总结 |
ir_sensitivity.json | Gate2 指标 |
ir_grad_flow.json | Gate3 指标 |
model_summary.txt | 模型结构摘要 |
parse_model_semantic_proof.txt | AST 哈希证明 |
train_10ep_log.txt | 10 epoch 训练日志 |
建议:跑长训之前至少执行一次 verify_all_modules.py + audit_tfblock_gates.py,并保存对应 artifacts。
新增文件清单与说明
下面列出项目中所有新增的(不属于原版 YOLOv26 的)文件及其用途。
一、scripts/ — 脚本目录
数据准备
| 文件 | 用途 |
|---|---|
prepare_llvip.py | LLVIP 数据集准备脚本。将下载的 LLVIP 压缩包解压并整理为框架要求的标准目录结构(rgb/train/images、ir/train/images、labels/ 等)。 |
审计与测试
| 文件 | 用途 |
|---|---|
audit_tfblock_gates.py | 预训练审计门 (G1-G4)。自动执行 4 项必检项:前向传播形状检查 (G1)、IR 敏感性检查 (G2)、IR 梯度回流检查 (G3)、模型摘要验证 (G4)。输出结果到 artifacts/tfblock_all/。 |
audit_parse_semantic.py | 解析器安全审计 (G5)。对 parse_model 函数进行 AST 哈希,生成 parse_model_semantic_proof.txt,用于追踪解析器是否被意外修改。 |
test_amp_fix.py | AMP 兼容性测试。对 TransformerFusionBlock 进行 5 种混合精度场景的测试(FP32、AMP 训练、Half+Eval、Half+Autocast、FP32+Autocast),验证 dtype 修复是否生效。 |
test_2ep_train.py | 2-epoch 快速管线测试。用 DDP 双卡跑 2 个 epoch(含训练 + 验证),验证完整训练管线是否能跑通,不关注精度。 |
verify_tfblock_roundtrip.py | 权重往返验证。加载训练保存的 best.pt,重新前向传播并与原始推理对比,确保模型保存/加载过程无损。 |
训练
| 文件 | 用途 |
|---|---|
train_tfblock_smoke.py | 烟雾测试 (1 epoch)。最小化训练运行,仅验证从构建模型到完成 1 个 epoch 的整个流程无报错。 |
train_tfblock_10ep.py | 10-epoch 效果验证。快速验证训练,用于判断融合模块是否在学习有效特征(loss 是否下降、mAP 是否上升)。 |
train_tfblock_full.py | 100-epoch 完整训练。正式训练脚本,含 OOM 自动降 batch 回退机制(64→32→16)。输出日志到 artifacts/tfblock_all/train_100e_log.txt。 |
二、artifacts/tfblock_all/ — 审计产出物
| 文件 | 来源脚本 | 内容说明 |
|---|---|---|
model_summary.txt | audit_tfblock_gates.py | 模型完整结构摘要(层数、参数量、GFLOPs) |
ir_sensitivity.json | audit_tfblock_gates.py | IR 敏感性检查结果:修改 IR 输入后输出变化量 (Delta) |
ir_grad_flow.json | audit_tfblock_gates.py | 梯度回流检查结果:IR 骨干参数的最大梯度值 |
parse_model_semantic_proof.txt | audit_parse_semantic.py | parse_model 函数的 AST 哈希值,用于追踪代码变更 |
git_diff.patch | 手动生成 | 所有代码改动的 Git diff 补丁文件 |
test_2ep_log.txt | test_2ep_train.py | 2-epoch DDP 测试的完整日志 |
train_10ep_log.txt | train_tfblock_10ep.py | 10-epoch 验证训练的完整日志 |
final_report.md | 手动编写 | TFBlock-All 审计总报告(汇总所有门、训练结果、Bug 修复记录) |
三、ultralytics/cfg/ — 配置文件
模型配置 (cfg/models/26/)
| 文件 | 说明 |
|---|---|
yolo26-rgbir.yaml | 基线双流模型。P3/P4 使用 ChannelFusion,P5 使用 TransformerFusion。 |
yolo26-rgbir-tfblock-all.yaml | TFBlock-All 实验模型。P3/P4/P5 全部使用 TransformerFusionBlock(16×16 网格、4 头注意力)。 |
数据集配置 (cfg/datasets/)
| 文件 | 说明 |
|---|---|
LLVIP.yaml | LLVIP RGB+IR 行人检测数据集配置。定义了 RGB/IR 图像路径和类别(1 类: person)。 |
验证脚本 (scripts/)
| 文件 | 用途 |
|---|---|
verify_all_modules.py | 全模块验证脚本。自动遍历所有 11 个融合模块的配置文件,验证加载、前向传播形状和反向传播梯度流,确保模块可用性。 |
新增融合模块配置 (cfg/models/26/)
| 文件 | 说明 |
|---|---|
yolo26n-rgbir-tfblock-all.yaml | TransformerFusionBlock (交叉注意力) |
yolo26n-rgbir-tfusion.yaml | TFusion (3D 卷积 + DeformConv) |
yolo26n-rgbir-combined.yaml | CombinedFusionBlocks (集成融合) |
yolo26n-rgbir-sfeg.yaml | SFEG (空间特征门控) |
yolo26n-rgbir-caff.yaml | CAFF (通道注意力融合) |
yolo26n-rgbir-icfusion.yaml | ICFusion (交互式通道融合) |
yolo26n-rgbir-maxfusion.yaml | MaxFusion (最大值特征融合) |
yolo26n-rgbir-concatfusion.yaml | ConcatFusion (拼接融合) |
yolo26n-rgbir-add.yaml | Add (直接相加) |
yolo26n-rgbir-dfsc.yaml | DFSC (密集特征选择) |
yolo26n-rgbir-cssa.yaml | CSSA (通道空间注意力) |
四、docs/zh/ — 中文文档
| 文件 | 说明 |
|---|---|
rgb_ir_使用说明文档.md | 用户使用手册。面向使用者,涵盖快速开始、数据集准备、模型配置、训练参数、常见问题。 |
rgb_ir_修改文档.md | 代码修改记录。记录对 Ultralytics 源码的所有改动点(block.py、tasks.py、dataset.py、train.py、val.py 等)。 |
融合模块开发指南.md | 开发者指南。面向研究者,讲解如何添加自定义融合模块、编写配置文件、准备数据集。含完整 SE-Fusion 示例。 |
五、ultralytics/ — 框架源码改动
以下文件虽属于原版 YOLOv26,但包含了双流功能的新增代码:
| 文件 | 新增内容 |
|---|---|
nn/modules/block.py | 新增 8 个类:ChannelFusion、AddFusion、TransformerFusion、TransformerFusionBlock、CrossTransformerBlock、CrossAttention、AdaptivePool2d、LearnableWeights、LearnableCoefficient |
nn/modules/__init__.py | 导出上述新增模块 |
nn/tasks.py | 新增 DualStreamDetectionModel 类、parse_dual_stream_model 函数、_FUSION_REGISTRY 注册表、loss() AMP 兼容修复 |
data/dataset.py | 新增 YOLODualStreamDataset 类,支持 RGB+IR 图像配对加载和灰度回退 |
models/yolo/detect/train.py | 新增 DualStreamDetectionTrainer 类,处理双流训练逻辑 |
models/yolo/detect/val.py | 新增 DualStreamDetectionValidator 类,处理双流验证逻辑 |
models/yolo/detect/dual_stream_predict.py | 新建文件。实现 DualStreamPredictor 类,处理双流推理时的自动配对与对齐逻辑 |
engine/model.py | 修改模型加载逻辑,识别 dual_stream: True 配置并路由到双流模型 |
六、runs/detect/ — 训练输出(不跟踪)
此目录已在 .gitignore 中排除,当前保留的结果:
上游同步指南
本文档说明如何将 Ultralytics 官方仓库的更新同步到本项目。
仓库关系
ultralytics/ultralytics (upstream) ← 官方上游仓库 │ ├── fork ──→ majianyu2007/YOLO26-Dual (origin) ← 你的远程仓库 │ │ │ └── clone ──→ 本地 /home/mjy/20260215 │ └── 我们的改动:双流 RGB-IR 检测功能Remote 配置
origin git@github.com:majianyu2007/YOLO26-Dual.git ← 你的仓库 (push/fetch)upstream https://github.com/ultralytics/ultralytics.git ← 官方上游 (fetch only)同步步骤
1. 拉取上游最新代码
git fetch upstream2. 查看上游有哪些新提交
# 查看上游比你多了多少提交git log --oneline main..upstream/main | head -203. 合并上游更新
# 确保在 main 分支git checkout main
# 合并上游 (推荐用 merge,保留完整历史)git merge upstream/main如果不想产生 merge commit,也可以用
git rebase upstream/main,但 rebase 风险更高,不推荐新手使用。
4. 解决冲突
合并时可能会出现冲突(CONFLICT)。以下是最可能冲突的文件和对应的处理策略:
高概率冲突
| 文件 | 我们的改动 | 处理策略 |
|---|---|---|
ultralytics/nn/modules/block.py | 末尾新增了融合模块 (ChannelFusion, TransformerFusionBlock 等) | 保留双方:上游修改的部分用上游的,我们新增在末尾的融合模块代码保留 |
ultralytics/nn/tasks.py | 新增了 DualStreamDetectionModel、_FUSION_REGISTRY、parse_dual_stream_model | 保留双方:上游改动照收,我们在末尾新增的双流代码保留 |
ultralytics/nn/modules/__init__.py | 导出列表新增了融合模块 | 合并导出列表:上游新增的导出 + 我们新增的融合模块导出 |
中概率冲突
| 文件 | 我们的改动 | 处理策略 |
|---|---|---|
ultralytics/data/dataset.py | 末尾新增 YOLODualStreamDataset | 保留双方 |
ultralytics/models/yolo/detect/train.py | 末尾新增 DualStreamDetectionTrainer | 保留双方 |
ultralytics/models/yolo/detect/val.py | 末尾新增 DualStreamDetectionValidator | 保留双方 |
ultralytics/models/yolo/detect/dual_stream_predict.py | 末尾新增 DualStreamPredictor | 保留双方 |
ultralytics/engine/model.py | 修改了模型加载路由逻辑 | 仔细对比,手动合并 |
绝不会冲突
以下文件是我们新增的,上游没有,绝不会冲突:
ultralytics/cfg/models/26/yolo26-rgbir*.yamlultralytics/cfg/datasets/LLVIP.yamlscripts/*artifacts/*ultralytics/models/yolo/detect/dual_stream_predict.pydocs/zh/*
5. 冲突解决实操
当 Git 提示冲突时,打开冲突文件会看到类似:
<<<<<<< HEAD我们的代码=======上游的代码>>>>>>> upstream/main处理原则:
- 上游的原有代码 → 用上游的版本(他们修 bug、优化性能等)
- 我们新增的代码 → 保留(双流功能)
- 同一行的不同修改 → 需逐行判断,通常以上游为准再手动加回我们的改动
# 解决所有冲突后git add .git commit -m "merge: sync upstream ultralytics (描述上游更新内容)"git push6. 验证合并结果
合并后务必运行以下验证:
# 1. 模型能正常构建python -c "from ultralytics import YOLO; m = YOLO('ultralytics/cfg/models/26/yolo26-rgbir.yaml'); print('✅ 基线模型构建成功')"
# 2. TFBlock 模型能构建python -c "from ultralytics import YOLO; m = YOLO('ultralytics/cfg/models/26/yolo26-rgbir-tfblock-all.yaml'); print('✅ TFBlock 模型构建成功')"
# 3. 前向传播测试python -c "import torchfrom ultralytics import YOLOm = YOLO('ultralytics/cfg/models/26/yolo26-rgbir.yaml')m.model.cuda().eval()rgb = torch.randn(1,3,640,640).cuda()ir = torch.randn(1,3,640,640).cuda()with torch.no_grad(): out = m.model({'rgb': rgb, 'ir': ir})print('✅ 前向传播成功')"
# 4. 标准 YOLO 单流模型不受影响python -c "from ultralytics import YOLO; m = YOLO('yolo11n.pt'); print('✅ 标准模型正常')"建议的同步频率
| 场景 | 建议频率 |
|---|---|
| 日常开发 | 每 1-2 周同步一次 |
| 准备发版 | 发版前同步一次 |
| 上游发布重大版本 | 及时同步 |
| 上游修复了安全漏洞 | 立即同步 |
常见问题
Q: 合并后原有功能不正常了怎么办?
A: 用 git log --oneline -10 查看最近提交,用 git revert <commit> 回退合并提交。或者用 git reset --hard HEAD~1 完全撤销合并(慎用,会丢失未推送的改动)。
Q: 可以只同步特定文件吗?
A: 可以用 git checkout upstream/main -- path/to/file 只拉取特定文件,但不推荐,容易造成代码不一致。
Q: upstream 的 main 分支改名了怎么办?
A: 用 git remote show upstream 查看默认分支名,将命令中的 upstream/main 替换为实际分支名。
RGB+IR 双流融合 — 开发者指南
适用于: Ultralytics YOLOv26 双流检测框架
本文档面向研究者和开发者,详细讲解如何在本框架中:
- 添加自己的(或论文中的)融合模块
- 编写模型配置文件
- 准备和适配数据集
目录
一、添加自定义融合模块
1.1 融合模块接口规范
所有融合模块必须遵循以下接口:
class YourFusion(nn.Module): def __init__(self, c_rgb, c_ir, c_out, **kwargs): """ Args: c_rgb (int): RGB 特征通道数 (由解析器自动传入) c_ir (int): IR 特征通道数 (由解析器自动传入) c_out (int): 输出通道数 **kwargs: 其他自定义参数 """ super().__init__() ...
def forward(self, x_rgb, x_ir): """ Args: x_rgb (Tensor): RGB 特征, shape (B, C_rgb, H, W) x_ir (Tensor): IR 特征, shape (B, C_ir, H, W)
Returns: Tensor: 融合后的特征, shape (B, C_out, H, W) """ ...关键约束:
__init__的前两个参数必须是c_rgb和c_ir,由解析器自动从骨干网络的通道数推断并传入forward必须接受两个位置参数(x_rgb, x_ir)- 返回值必须是单个张量,尺寸与输入的空间分辨率一致 (H, W 不变)
1.2 编写模块代码
在 ultralytics/nn/modules/block.py 文件末尾添加你的模块。以下是现有模块的参考实现:
最简实现:通道融合
class ChannelFusion(nn.Module): """Concat + 1×1 Conv 融合"""
def __init__(self, c_rgb, c_ir, c_out): super().__init__() self.conv = Conv(c_rgb + c_ir, c_out, 1)
def forward(self, x_rgb, x_ir): return self.conv(torch.cat([x_rgb, x_ir], dim=1))中等复杂度:加法融合
class AddFusion(nn.Module): """投影 + 逐元素相加"""
def __init__(self, c_rgb, c_ir, c_out): super().__init__() self.proj_rgb = Conv(c_rgb, c_out, 1) if c_rgb != c_out else nn.Identity() self.proj_ir = Conv(c_ir, c_out, 1) if c_ir != c_out else nn.Identity()
def forward(self, x_rgb, x_ir): return self.proj_rgb(x_rgb) + self.proj_ir(x_ir)1.3 注册到框架
完成模块编写后,需要在三个位置注册:
步骤 1: 在 block.py 的 __all__ 中导出(如有)
# ultralytics/nn/modules/block.py 顶部# 确保你的类名包含在模块的导出列表中步骤 2: 在 __init__.py 中导出
from .block import ( ..., YourFusion, # ← 添加这一行)步骤 3: 在 tasks.py 的 _FUSION_REGISTRY 中注册
# ultralytics/nn/tasks.py (约第 1844 行)from ultralytics.nn.modules.block import (..., YourFusion)
_FUSION_REGISTRY = { "ChannelFusion": ChannelFusion, "AddFusion": AddFusion, "TransformerFusion": TransformerFusion, "TransformerFusionBlock": TransformerFusionBlock, "TFBlock": TransformerFusionBlock, # Alias "TFusion": TFusion, "Combined": Combined, "SFEG": SFEG, "CAFF": CAFF, "ICFusion": ICFusion, "MaxFusion": MaxFusion, "Concat": ConcatFusion, # Alias "ConcatFusion": ConcatFusion, "Add": AddFusion, # Alias "DFSC": DFSC, "CSSA": CSSA, "YourFusion": YourFusion, # ← 添加这一行}1.4 在解析器中添加实例化逻辑
打开 ultralytics/nn/tasks.py,找到 parse_dual_stream_model 函数中 Step 3 的融合层构建循环(约第 2032 行)。在 for fi, f_spec in enumerate(fusion_specs) 循环中,已有 4 个分支处理不同模块类型:
# ultralytics/nn/tasks.py — parse_dual_stream_model 函数内部
for fi, f_spec in enumerate(fusion_specs): c_rgb = bb_ch_map[f_spec["rgb_idx"]] # 自动获取 RGB 通道数 c_ir = bb_ch_map[f_spec["ir_idx"]] # 自动获取 IR 通道数 mod_name = f_spec["module"] mod_cls = _FUSION_REGISTRY[mod_name] f_args = f_spec.get("args", [])
# ---- 以下是各模块的实例化分支 ----
if mod_cls is TransformerFusion: # ... 已有逻辑 elif mod_cls is TransformerFusionBlock: # ... 已有逻辑 elif mod_cls is ChannelFusion: c_out = f_args[0] if f_args else c_rgb c_out = make_divisible(min(c_out, max_channels) * width, 8) fusion_layers.append(mod_cls(c_rgb, c_ir, c_out))
# ---- 添加你的模块分支 ---- elif mod_cls is YourFusion: c_out = f_args[0] if f_args else c_rgb c_out = make_divisible(min(c_out, max_channels) * width, 8) # 从 f_args 中提取你自定义的参数 your_param = f_args[1] if len(f_args) > 1 else default_value fusion_layers.append(mod_cls(c_rgb, c_ir, c_out, your_param))
else: # 默认分支 (AddFusion 等) c_out = f_args[0] if f_args else c_rgb c_out = make_divisible(min(c_out, max_channels) * width, 8) fusion_layers.append(mod_cls(c_rgb, c_ir, c_out))
fused_channels[fi] = c_out # 记录输出通道数,供 Head 使用解析器关键说明:
c_rgb和c_ir由解析器自动从骨干网络的输出通道推断,不需要在 YAML 中指定f_args对应 YAML 配置中的args列表width和max_channels来自scales配置,用于按模型规模缩放通道数make_divisible(x, 8)确保通道数是 8 的倍数 (GPU 计算效率)- 如果你的模块遵循
(c_rgb, c_ir, c_out)标准接口,通常可以直接使用else默认分支,无需添加专门的elif
1.5 AMP 混合精度兼容性
如果你的模块内部使用了以下操作,必须注意 AMP 兼容性:
| 容易引发问题的操作 | 原因 | 解决方案 |
|---|---|---|
nn.LayerNorm | 内部计算强制 FP32 | 在输出时转回输入 dtype |
torch.softmax | 数值稳定性强制 FP32 | 同上 |
nn.Parameter(torch.FloatTensor(...)) | 显式 FP32 初始化 | 输出前 .to(input.dtype) |
动态创建 nn.Conv2d / nn.ConvTranspose2d | 新层权重为 FP32 | 避免在 forward 中创建层 |
推荐做法:在 forward 方法末尾添加 dtype 安全转换:
def forward(self, x_rgb, x_ir): input_dtype = x_rgb.dtype # 记录输入 dtype
# ... 你的融合逻辑 ...
# 确保输出 dtype 与输入一致 (AMP 兼容) output = output.to(input_dtype) return output1.6 完整示例:实现 SE-Fusion 模块
以下用一个基于 Squeeze-and-Excitation 的融合模块作为完整示例:
第 1 步:编写模块 (block.py)
# 在 ultralytics/nn/modules/block.py 末尾添加
class SEFusion(nn.Module): """Squeeze-and-Excitation based fusion for dual-stream features.
Uses channel attention to adaptively weight RGB and IR features before element-wise addition.
Reference: Hu et al., "Squeeze-and-Excitation Networks", CVPR 2018 """
def __init__(self, c_rgb, c_ir, c_out, reduction=16): """Initialize SEFusion.
Args: c_rgb (int): RGB input channels. c_ir (int): IR input channels. c_out (int): Output channels. reduction (int): SE block channel reduction ratio. """ super().__init__() self.proj_rgb = Conv(c_rgb, c_out, 1) if c_rgb != c_out else nn.Identity() self.proj_ir = Conv(c_ir, c_out, 1) if c_ir != c_out else nn.Identity()
# SE block: 学习通道级注意力权重 self.se = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(c_out * 2, c_out * 2 // reduction), nn.ReLU(inplace=True), nn.Linear(c_out * 2 // reduction, c_out * 2), nn.Sigmoid(), )
def forward(self, x_rgb, x_ir): """Fuse features with SE-guided channel attention.""" input_dtype = x_rgb.dtype
rgb = self.proj_rgb(x_rgb) ir = self.proj_ir(x_ir)
# 计算通道注意力 combined = torch.cat([rgb, ir], dim=1) # (B, 2*C_out, H, W) se_weight = self.se(combined) # (B, 2*C_out) B, C2 = se_weight.shape C = C2 // 2 w_rgb = se_weight[:, :C].view(B, C, 1, 1) w_ir = se_weight[:, C:].view(B, C, 1, 1)
output = rgb * w_rgb + ir * w_ir return output.to(input_dtype) # AMP 安全第 2 步:注册 (__init__.py + tasks.py)
# ultralytics/nn/modules/__init__.py — 添加导出from .block import (..., SEFusion)
# ultralytics/nn/tasks.py — 添加注册from ultralytics.nn.modules.block import (..., SEFusion)
_FUSION_REGISTRY = { ..., "SEFusion": SEFusion,}第 3 步:添加解析分支 (tasks.py)
# 在 parse_dual_stream_model 的融合循环中添加elif mod_cls is SEFusion: c_out = f_args[0] if f_args else c_rgb c_out = make_divisible(min(c_out, max_channels) * width, 8) reduction = f_args[1] if len(f_args) > 1 else 16 fusion_layers.append(mod_cls(c_rgb, c_ir, c_out, reduction))也可以不添加
elif分支:如果不需要额外参数,可直接落入else默认分支。但如果有自定义参数(如reduction),建议添加专门分支。
第 4 步:编写 YAML 配置
fusion: - {rgb_idx: 4, ir_idx: 4, module: SEFusion, args: [512, 16]} # P3, reduction=16 - {rgb_idx: 6, ir_idx: 6, module: SEFusion, args: [512, 16]} # P4 - {rgb_idx: 10, ir_idx: 10, module: SEFusion, args: [1024, 16]} # P5第 5 步:测试
from ultralytics import YOLO
model = YOLO("ultralytics/cfg/models/26/yolo26-rgbir-se.yaml")print(model.model) # 确认 SEFusion 出现在模型结构中
# 快速前向测试import torchrgb = torch.randn(1, 3, 640, 640).cuda()ir = torch.randn(1, 3, 640, 640).cuda()model.model.cuda()out = model.model({"rgb": rgb, "ir": ir})print("✅ Forward pass succeeded!")二、编写模型配置文件
2.1 配置文件结构
双流模型 YAML 配置文件包含 5 个主要部分:
# ============ 1. 全局参数 ============nc: 80 # 类别数 (训练时会被 data.yaml 覆盖)end2end: True # 是否使用端到端检测reg_max: 1 # DFL 回归最大值dual_stream: True # ⚠️ 必须设为 True,框架据此判断是否构建双流模型
# ============ 2. 模型规模 ============scales: n: [0.50, 0.25, 1024] # [depth_multiple, width_multiple, max_channels] s: [0.50, 0.50, 1024]
# ============ 3. 骨干网络 (共享架构) ============backbone: - [-1, 1, Conv, [64, 3, 2]] # Layer 0 - [-1, 1, Conv, [128, 3, 2]] # Layer 1 ... # 与标准 YOLOv26 骨干相同
# ============ 4. 融合层 ============fusion: - {rgb_idx: 4, ir_idx: 4, module: ChannelFusion, args: [512]} - {rgb_idx: 6, ir_idx: 6, module: ChannelFusion, args: [512]} - {rgb_idx: 10, ir_idx: 10, module: TransformerFusion, args: [1024, 4]}
# ============ 5. 检测头 ============head: - [2, 1, nn.Upsample, [None, 2, "nearest"]] # 索引 0,1,2 指向融合输出 - [[-1, 1], 1, Concat, [1]] ...2.2 fusion 段详解
fusion: - {rgb_idx: 4, ir_idx: 4, module: ChannelFusion, args: [512]} # ───────── ──────── ────────────────────── ────────── # │ │ │ │ # │ │ │ └─ 传给模块的额外参数列表 # │ │ └─ 模块类名 (必须在 _FUSION_REGISTRY 中) # │ └─ IR 骨干输出层索引 # └─ RGB 骨干输出层索引参数说明:
| 字段 | 类型 | 说明 |
|---|---|---|
rgb_idx | int | RGB 骨干的输出层索引(骨干网络层号) |
ir_idx | int | IR 骨干的输出层索引(通常与 rgb_idx 相同) |
module | str | 融合模块类名 |
args | list | 额外参数列表(c_rgb 和 c_ir 不需要写,解析器自动填入) |
2.3 骨干层索引对照表
标准 YOLOv26 骨干(n-scale)各层输出对照:
| 层索引 | 模块 | 输出通道 (n-scale) | 步长 | 分辨率 (640px) | 对应特征层 |
|---|---|---|---|---|---|
| 0 | Conv | 16 | 2 | 320×320 | P1 |
| 1 | Conv | 32 | 4 | 160×160 | P2 |
| 2 | C3k2 | 64 | 4 | 160×160 | — |
| 3 | Conv | 64 | 8 | 80×80 | P3 |
| 4 | C3k2 | 128 | 8 | 80×80 | P3 tap ✅ |
| 5 | Conv | 128 | 16 | 40×40 | P4 |
| 6 | C3k2 | 128 | 16 | 40×40 | P4 tap ✅ |
| 7 | Conv | 256 | 32 | 20×20 | P5 |
| 8 | C3k2 | 256 | 32 | 20×20 | — |
| 9 | SPPF | 256 | 32 | 20×20 | — |
| 10 | C2PSA | 256 | 32 | 20×20 | P5 tap ✅ |
通常只在 P3 (层4)、P4 (层6)、P5 (层10) 处进行融合。这些是 FPN 的标准特征提取点。
2.4 完整配置示例
nc: 80end2end: Truereg_max: 1dual_stream: True
scales: n: [0.50, 0.25, 1024] s: [0.50, 0.50, 1024]
# 骨干: 直接复制标准 YOLOv26 骨干backbone: - [-1, 1, Conv, [64, 3, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 2, C3k2, [256, False, 0.25]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 2, C3k2, [512, False, 0.25]] # Layer 4 → P3 - [-1, 1, Conv, [512, 3, 2]] - [-1, 2, C3k2, [512, True]] # Layer 6 → P4 - [-1, 1, Conv, [1024, 3, 2]] - [-1, 2, C3k2, [1024, True]] - [-1, 1, SPPF, [1024, 5, 3, True]] - [-1, 2, C2PSA, [1024]] # Layer 10 → P5
# 融合: 你可以混合使用不同模块fusion: - {rgb_idx: 4, ir_idx: 4, module: SEFusion, args: [512, 16]} - {rgb_idx: 6, ir_idx: 6, module: ChannelFusion, args: [512]} - {rgb_idx: 10, ir_idx: 10, module: TransformerFusionBlock, args: [16, 16, 4]}
# 检测头: 与标准 yolo26-rgbir 完全相同,无需修改# 融合输出索引: 0=P3-fused, 1=P4-fused, 2=P5-fusedhead: - [2, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 1], 1, Concat, [1]] - [-1, 2, C3k2, [512, True]] - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 0], 1, Concat, [1]] - [-1, 2, C3k2, [256, True]] - [-1, 1, Conv, [256, 3, 2]] - [[-1, 5], 1, Concat, [1]] - [-1, 2, C3k2, [512, True]] - [-1, 1, Conv, [512, 3, 2]] - [[-1, 2], 1, Concat, [1]] - [-1, 1, C3k2, [1024, True, 0.5, True]] - [[8, 11, 14], 1, Detect, [nc]]三、数据集准备
3.1 目录结构规范
双流数据集需要 RGB 和 IR 图像一一对应,目录结构如下:
datasets/YourDataset/├── rgb/│ ├── train/│ │ └── images/│ │ ├── 000001.jpg # RGB 图像│ │ ├── 000002.jpg│ │ └── ...│ └── val/│ └── images/│ └── ...├── ir/│ ├── train/│ │ └── images/│ │ ├── 000001.jpg # IR 图像 (与 RGB 同名)│ │ ├── 000002.jpg│ │ └── ...│ └── val/│ └── images/│ └── ...└── rgb/ ├── train/ │ └── labels/ # YOLO 格式标注 (写在 RGB 路径下) │ ├── 000001.txt │ └── ... └── val/ └── labels/ └── ...关键规则:
- RGB 和 IR 图像必须同名(用于自动配对)
- 标注文件 (
labels/) 放在 RGB 路径下(标准 YOLO 位置规则:将images替换为labels) - IR 目录下不需要标注文件
- 图像格式: JPG/PNG 均可,RGB 和 IR 可以不同格式
3.2 数据集配置文件
# 数据集根目录 (绝对路径或相对于项目根目录)path: ../datasets/YourDataset
# RGB 图像路径 (相对于 path)train: rgb/train/imagesval: rgb/val/imagestest: # 可选
# IR 图像路径 (相对于 path)ir_train: ir/train/imagesir_val: ir/val/images
# 类别定义names: 0: person 1: car 2: bicycle配置文件位置:放在以下任意位置均可
- 项目根目录:
YourDataset.yaml - 标准目录:
ultralytics/cfg/datasets/YourDataset.yaml
3.3 适配自己的数据集
场景 1: RGB 和 IR 图像在同一目录但不同前缀/后缀
如果你的数据集结构是这样的:
dataset/├── images/│ ├── 001_rgb.jpg│ ├── 001_ir.jpg│ └── ...└── labels/ ├── 001_rgb.txt └── ...需要先用脚本整理成标准结构:
"""将非标准数据集转换为双流标准结构"""import shutilfrom pathlib import Path
src = Path("dataset/images")dst_rgb = Path("dataset_new/rgb/train/images")dst_ir = Path("dataset_new/ir/train/images")dst_rgb.mkdir(parents=True, exist_ok=True)dst_ir.mkdir(parents=True, exist_ok=True)
for f in src.glob("*_rgb.*"): base = f.name.replace("_rgb", "") shutil.copy(f, dst_rgb / base)
for f in src.glob("*_ir.*"): base = f.name.replace("_ir", "") shutil.copy(f, dst_ir / base)
# 标注文件同样处理src_labels = Path("dataset/labels")dst_labels = Path("dataset_new/rgb/train/labels")dst_labels.mkdir(parents=True, exist_ok=True)
for f in src_labels.glob("*_rgb.txt"): base = f.name.replace("_rgb", "") shutil.copy(f, dst_labels / base)
print("✅ 数据集转换完成!")场景 2: 只有 RGB 数据,想先跑通流程
系统自动支持灰度回退。只需:
- 不设置
ir_train/ir_val,或让 IR 路径为空 - 系统会自动将 RGB 图像转为灰度图作为合成 IR
# 最小化配置 (无 IR 数据)path: ../datasets/YourDatasettrain: images/trainval: images/val
# 不指定 ir_train/ir_val → 自动灰度回退
names: 0: person场景 3: RGB 和 IR 分辨率不同
没问题。框架会在数据加载时将 RGB 和 IR 统一缩放到 imgsz (默认 640×640)。不需要预处理。
场景 4: 使用 KAIST/FLIR 等公开数据集
请按以下步骤操作:
- 下载数据集并解压
- 用 KAIST 标注转换工具 将标注转为 YOLO 格式
- 按照 3.1 的标准结构整理目录
- 编写 YAML 配置文件
3.4 配对规则与灰度回退
配对流程:
RGB 图像: rgb/train/images/000123.jpg │ ▼ 提取文件名 000123.jpg │ ▼ 在 ir_train 目录中查找IR 图像: ir/train/images/000123.jpg │ ┌──────────┴──────────┐ ▼ ▼ 找到 → 使用真实 IR 找不到 → 灰度回退 │ ▼ RGB → 灰度 → 3通道复制注意事项:
- 灰度回退是逐图像生效的,即使部分图像缺少 IR 配对也不会报错
- 训练日志中会输出
✅ Resolved IR train path确认路径正确 - 如果
ir_train路径不存在,所有图像都会使用灰度回退
四、验证与调试
快速验证模型构建
from ultralytics import YOLO
model = YOLO("ultralytics/cfg/models/26/yolo26-rgbir-custom.yaml")
# 检查模型类型assert type(model.model).__name__ == "DualStreamDetectionModel"
# 检查融合模块数量fusion_count = len(model.model.fusion)print(f"融合模块数量: {fusion_count}") # 应为 3
# 前向传播测试import torchrgb = torch.randn(1, 3, 640, 640).cuda()ir = torch.randn(1, 3, 640, 640).cuda()model.model.cuda().eval()with torch.no_grad(): out = model.model({"rgb": rgb, "ir": ir})print(f"✅ 前向传播成功! 输出键: {list(out.keys()) if isinstance(out, dict) else type(out)}")
### 推理验证 (Inference)
本项目已实现 `DualStreamPredictor`,支持直接使用 `model.predict()` 进行双流推理。
```pythonfrom ultralytics import YOLO
# 加载训练好的双流模型model = YOLO("runs/detect/train/weights/best.pt")
# 推理: 指定 RGB 图像路径# 系统会自动查找同名的 IR 图像 (替换路径中的 rgb -> ir)# 并自动进行 LetterBox 对齐results = model.predict(source="datasets/LLVIP/rgb/val/images/010001.jpg")
# 或者显式指定 IR 路径 (高级)results = model.predict( source="datasets/LLVIP/rgb/val/images/010001.jpg", ir_source="datasets/LLVIP/ir/val/images/010001.jpg")
for r in results: r.show() # 显示结果 r.save() # 保存结果自动配对规则:
- 传入 RGB 图像路径
- 查找路径字符串中的
rgb,替换为ir - 检查替换后的路径是否存在
- 如果存在,读取并使用
LetterBox对齐 - 如果不存在,发出警告并使用 RGB 转灰度作为伪 IR
### IR 敏感性测试
验证 IR 分支是否真正参与计算:
```pythonimport torch
model.model.cuda().eval()rgb = torch.randn(1, 3, 640, 640).cuda()ir1 = torch.randn(1, 3, 640, 640).cuda()ir2 = torch.zeros(1, 3, 640, 640).cuda() # 全零 IR
with torch.no_grad(): out1 = model.model({"rgb": rgb, "ir": ir1}) out2 = model.model({"rgb": rgb, "ir": ir2})
# 如果 IR 生效,两次输出应该不同diff = sum((a - b).abs().sum().item() for a, b in zip(out1, out2) if isinstance(a, torch.Tensor))print(f"IR 敏感性 Delta: {diff}")assert diff > 0, "❌ IR 输入未影响输出,融合模块可能未正常工作"print("✅ IR 分支参与计算,融合模块正常!")AMP 兼容性测试
import torchfrom torch.amp import autocast
model.model.cuda().half().eval()rgb = torch.randn(1, 3, 640, 640).cuda().half()ir = torch.randn(1, 3, 640, 640).cuda().half()
# 测试 1: Half 模型,无 autocast (验证路径)with torch.no_grad(): try: out = model.model({"rgb": rgb, "ir": ir}) print("✅ Half + no autocast: PASS") except RuntimeError as e: print(f"❌ Half + no autocast: FAIL — {e}") print("提示: 请在 forward() 末尾添加 output.to(input.dtype)")
# 测试 2: FP32 模型 + autocast (训练路径)model.model.float()rgb_f = rgb.float()ir_f = ir.float()with autocast("cuda"): out = model.model({"rgb": rgb_f, "ir": ir_f}) print("✅ FP32 + autocast: PASS")AI 操作提示词模板
本文档提供一系列结构化提示词 (Prompt),用于指导 AI 在本项目中执行常见操作。
使用方式:复制对应提示词,替换{{占位符}}中的内容,直接发送给 AI。
目录
1. 数据集处理提示词
使用场景
拿到一个新的 RGB+IR 数据集,需要让 AI 编写数据处理脚本并生成配置文件。
提示词模板
我有一个新的 RGB+IR 双流数据集需要接入本项目。请帮我完成以下全部工作:
## 数据集信息- 数据集名称: {{数据集名称,例如: KAIST}}- 原始数据路径: {{数据所在路径,例如: /home/user/downloads/KAIST_dataset/}}- 原始目录结构:{{粘贴 tree 命令的输出或手动描述目录结构,例如:KAIST_dataset/├── set00/│ ├── V000/│ │ ├── visible/ (RGB 图像)│ │ ├── lwir/ (IR 图像)│ │ └── ...├── annotations/│ └── ...}}- 标注格式: {{YOLO/VOC/COCO/自定义,描述标注文件的格式}}- 类别列表: {{例如: person, car, bicycle}}- 训练/验证划分: {{例如: 已划分/按比例 8:2/按 set 划分}}
## 必须完成的步骤
### 步骤 1: 数据处理脚本在 `scripts/` 目录下创建 `prepare_{{数据集名称小写}}.py`,该脚本必须:1. 读取原始数据集目录2. 将 RGB 图像复制/软链接到 `../datasets/{{数据集名称}}/rgb/train/images/` 和 `rgb/val/images/`3. 将 IR 图像复制/软链接到 `../datasets/{{数据集名称}}/ir/train/images/` 和 `ir/val/images/`4. RGB 和 IR 图像必须**同名配对**(文件名相同,扩展名可不同)5. 将标注转换为 YOLO 格式 (`class_id cx cy w h`,归一化坐标) 并放在 `rgb/train/labels/` 和 `rgb/val/labels/`6. 打印统计信息:训练/验证图像数量、类别分布、RGB-IR 配对完整性7. 脚本头部写清楚文档字符串说明用途
最终目录结构必须严格符合:
../datasets/{{数据集名称}}/├── rgb/│ ├── train/│ │ ├── images/ ← RGB 训练图像│ │ └── labels/ ← YOLO 格式标注│ └── val/│ ├── images/│ └── labels/└── ir/ ├── train/ │ └── images/ ← IR 训练图像 (与 RGB 同名) └── val/ └── images/
### 步骤 2: 数据集配置文件创建 `ultralytics/cfg/datasets/{{数据集名称}}.yaml`,格式必须严格遵循:
# {{数据集名称}} RGB+IR Datasetpath: ../datasets/{{数据集名称}}train: rgb/train/imagesval: rgb/val/imagestest:
ir_train: ir/train/imagesir_val: ir/val/images
names: 0: {{类别0}} 1: {{类别1}}
### 步骤 3: 验证运行处理脚本后,验证:1. RGB 和 IR 的文件数量一致2. 每个 RGB 图像都有对应的 IR 图像(同名检查)3. 每个 RGB 图像都有对应的 labels 文件4. 标注格式正确(YOLO 格式,坐标归一化 0~1)
## 参考- 现有的处理脚本参考: `scripts/prepare_llvip.py`- 现有的配置文件参考: `ultralytics/cfg/datasets/LLVIP.yaml`
请先打印原始数据集的目录结构和文件样例,确认理解后再编写脚本。2. 训练脚本生成提示词
使用场景
准备好数据集和模型配置后,需要生成训练脚本。
提示词模板
请为本双流项目生成训练脚本。
## 训练配置- 模型配置文件: {{例如: ultralytics/cfg/models/26/yolo26-rgbir-tfblock-all.yaml}}- 数据集配置文件: {{例如: LLVIP.yaml 或 ultralytics/cfg/datasets/KAIST.yaml}}- GPU: {{例如: 2×RTX 3090 (24GB)}}- 目标 epochs: {{例如: 100}}- 实验名称: {{例如: kaist_tfblock_exp1}}
## 强制约束(不可违反)1. 优化器必须使用 `optimizer="AdamW"`(Muon 与 TransformerFusionBlock 的 1D 参数不兼容)2. 必须设置 `compile=False`(双流模型不支持 torch.compile)3. 必须设置 `multi_scale=0`(双流模式不支持多尺度训练)4. 推荐设置 `augment=False`(空间增强会破坏 RGB-IR 像素级对齐)
## 脚本要求1. 文件路径: `scripts/train_{{实验名称}}.py`2. 必须包含 OOM 自动回退机制:尝试 batch 列表 `[{{最大batch}}, {{次大}}, {{次次大}}]`3. 训练日志输出到 `artifacts/{{实验名称}}/train_log.txt`4. 脚本中使用绝对路径 `os.path.abspath()` 设置 `project` 参数5. 训练参数使用 `model.train()` API,不要手写训练循环
## 参考- 现有训练脚本模板: `scripts/train_tfblock_full.py`
## 启动方式脚本创建完成后,告诉我启动命令(格式:`python -u scripts/xxx.py > artifacts/.../train_log.txt 2>&1 &`)
## 补充信息- Python 环境: {{例如: /home/conda-env/yolov26/bin/python}}- 工作目录: {{例如: /home/mjy/20260215}}3. 融合模块迁移提示词
使用场景
从论文的开源代码中提取融合模块,集成到本项目。
提示词模板
请帮我从以下论文项目中提取融合模块,并集成到本项目的双流 RGB-IR 检测框架中。
## 源项目信息- 论文名称: {{论文标题}}- GitHub 仓库: {{仓库 URL,例如: https://github.com/xxx/yyy}}- 本地克隆路径: {{例如: /home/user/papers/yyy/}}(如已克隆)- 融合模块大致位置: {{如果知道,例如: models/fusion.py 中的 CrossModalFusion 类;如果不知道,写"未知,请帮我定位"}}
## 迁移任务(必须按顺序完成)
### 阶段 1: 定位源代码1. 在源项目中搜索融合相关模块,关键词包括但不限于: `fusion`, `cross`, `modal`, `attention`, `merge`, `interact`2. 找到核心融合类后,分析其 `__init__` 参数和 `forward` 方法的输入输出3. 列出该模块的所有依赖类/函数(递归查找,确保不遗漏)4. 汇报找到的模块名、文件路径、参数列表、forward 签名
### 阶段 2: 适配接口本项目的融合模块必须遵循以下接口规范(不可违反):
class YourFusion(nn.Module): def __init__(self, c_rgb, c_ir, c_out, **extra_args): """ 前两个参数必须是 c_rgb 和 c_ir(由解析器自动传入)。 c_out 为输出通道数。 """ super().__init__() ...
def forward(self, x_rgb, x_ir): """ Args: x_rgb: (B, C_rgb, H, W) — RGB 特征 x_ir: (B, C_ir, H, W) — IR 特征 Returns: Tensor: (B, C_out, H, W) — 融合后特征,空间尺寸不变 """ ...
如果源模块的接口与此不同(例如 forward 接受 list 或 tuple),必须进行适配包装。
### 阶段 3: 代码迁移1. 将融合类及其所有依赖复制到 `ultralytics/nn/modules/block.py` 文件末尾2. 所有 import 必须使用已有的依赖(`torch`, `torch.nn`, `torch.nn.functional`)3. 不得引入新的第三方库4. 如果源模块使用了 `from xxx import Conv` 等,替换为本项目的 `from ultralytics.nn.modules.conv import Conv`5. 避免循环导入:`block.py` 中只能从 `conv.py` 导入
### 阶段 4: 框架注册(3 个文件)1. `ultralytics/nn/modules/__init__.py` — 在导出列表中添加新类2. `ultralytics/nn/tasks.py` — 在 `_FUSION_REGISTRY` 字典中注册(约第 1844 行)3. `ultralytics/nn/tasks.py` — 在 `parse_dual_stream_model` 函数的融合循环中(约第 2032 行)添加实例化分支:
elif mod_cls is YourFusion: c_out = f_args[0] if f_args else c_rgb c_out = make_divisible(min(c_out, max_channels) * width, 8) # 从 f_args 提取额外参数 fusion_layers.append(mod_cls(c_rgb, c_ir, c_out, ...))
### 阶段 5: AMP 兼容性检查如果融合模块内部使用了以下任一操作,必须在 `forward()` 末尾添加 `output = output.to(x_rgb.dtype)`:- `nn.LayerNorm` — 强制 FP32- `torch.softmax` — 强制 FP32- `nn.Parameter(torch.FloatTensor(...))` — 显式 FP32- 在 forward 中动态创建 `nn.Conv2d` / `nn.Linear`
### 阶段 6: 创建配置文件创建 `ultralytics/cfg/models/26/yolo26-rgbir-{{模块名小写}}.yaml`:- 复制 `yolo26-rgbir.yaml` 的 backbone 和 head 部分(不要修改)- 只修改 `fusion` 段,使用新注册的模块名- `args` 列表中不要写 `c_rgb`/`c_ir`(解析器自动填入),只写额外参数
### 阶段 7: 验证(不可跳过)1. **构建测试**: `model = YOLO("配置文件路径")` 不报错2. **前向测试**: 用随机张量跑 `model.model({"rgb": rgb, "ir": ir})` 不报错3. **IR 敏感性**: 修改 IR 输入后输出必须变化(delta > 0)4. **AMP 测试**: `model.model.half().eval()` 后前向不报错
请先完成阶段 1(定位源代码),汇报后等我确认再继续。
## 参考- 接口规范参考: `docs/zh/融合模块开发指南.md`- 现有融合模块参考: `ultralytics/nn/modules/block.py` 中的 `ChannelFusion` (最简) 和 `TransformerFusionBlock` (最复杂)- 注册表位置: `ultralytics/nn/tasks.py` 第 1844 行 `_FUSION_REGISTRY`4. 模型配置文件生成提示词
使用场景
注册好新融合模块后,需要生成模型配置文件。
提示词模板
请为本项目生成一个新的双流模型配置文件。
## 配置需求- 融合策略: - P3 (骨干层 4, stride 8): 使用 {{模块名}} 模块,参数: {{参数列表}} - P4 (骨干层 6, stride 16): 使用 {{模块名}} 模块,参数: {{参数列表}} - P5 (骨干层 10, stride 32): 使用 {{模块名}} 模块,参数: {{参数列表}}- 配置文件名: `yolo26-rgbir-{{描述名}}.yaml`
## 强制约束1. 文件路径必须为: `ultralytics/cfg/models/26/yolo26-rgbir-{{描述名}}.yaml`2. 必须包含 `dual_stream: True`3. backbone 和 head 部分直接从 `yolo26-rgbir.yaml` 复制,不做任何修改4. 只修改 `fusion` 段5. `fusion` 段中的 `args` 列表不要写 `c_rgb` 和 `c_ir`(解析器自动从骨干层输出推断)6. 所有 `module` 名称必须已在 `_FUSION_REGISTRY` 中注册
## fusion 段格式
fusion: - {rgb_idx: 4, ir_idx: 4, module: 模块名, args: [额外参数]} - {rgb_idx: 6, ir_idx: 6, module: 模块名, args: [额外参数]} - {rgb_idx: 10, ir_idx: 10, module: 模块名, args: [额外参数]}
## 创建后验证生成配置文件后,运行以下代码确认无误:
from ultralytics import YOLOmodel = YOLO("ultralytics/cfg/models/26/yolo26-rgbir-{{描述名}}.yaml")print(type(model.model).__name__) # 必须输出 DualStreamDetectionModelprint(len(model.model.fusion)) # 必须输出 3
## 参考- 现有配置: `ultralytics/cfg/models/26/yolo26-rgbir.yaml` (基线)- 现有配置: `ultralytics/cfg/models/26/yolo26-rgbir-tfblock-all.yaml` (TFBlock)5. 训练结果分析提示词
使用场景
训练完成后,让 AI 分析训练结果并与基线对比。
提示词模板
请分析本次训练结果并生成报告。
## 训练信息- 实验名称: {{实验名称}}- 模型配置: {{使用的 YAML 配置文件}}- 数据集: {{数据集名称}}- 训练 epochs: {{数量}}- 训练日志路径: {{例如: artifacts/xxx/train_log.txt}}- 结果 CSV 路径: {{例如: runs/detect/xxx/results.csv}}- 基线对比结果 (如有): {{基线的 mAP50/mAP50-95 数值,或基线 results.csv 路径}}
## 必须分析的内容
### 1. 收敛性分析- 绘制 loss 曲线趋势(box_loss, cls_loss, dfl_loss)- 判断是否收敛、是否过拟合(对比 train loss 和 val loss)- 标注最佳 epoch
### 2. 精度分析- 提取关键指标: mAP50, mAP50-95, Precision, Recall- 绘制 mAP 随 epoch 变化曲线- 标注最佳 mAP50 和对应的 epoch
### 3. 对比分析 (如有基线)- 制作对比表格: 基线 vs 本次实验- 分析各指标的提升/下降幅度(百分比)- 给出明确结论: 新融合模块是否优于基线
### 4. 建议- 如果效果不佳,分析可能原因并给出调优建议- 如果效果良好,建议下一步(更多 epochs、更大模型、消融实验等)
## 输出将分析报告写入 `artifacts/{{实验名称}}/analysis_report.md`6. Bug 诊断提示词
使用场景
训练过程中遇到报错,需要 AI 诊断修复。
提示词模板
训练过程中出现了以下错误,请帮我诊断并修复。
## 错误信息{{粘贴完整的 Traceback 错误信息}}
## 训练配置- 模型配置: {{YAML 文件路径}}- 数据集: {{数据集名称}}- GPU: {{GPU 型号和数量}}- Batch size: {{数值}}- 崩溃时的 epoch/batch: {{例如: epoch 1, batch 433/866}}
## 上下文- 训练阶段还是验证阶段崩溃: {{train/val}}- 是否使用 DDP 多卡: {{是/否}}- 是否首次运行就报错: {{是/否,如果否,说明之前成功运行了多久}}
## 项目关键信息(帮助你定位问题)- 融合模块代码: `ultralytics/nn/modules/block.py`(搜索对应的类名)- 模型解析器: `ultralytics/nn/tasks.py`(`DualStreamDetectionModel` 类和 `parse_dual_stream_model` 函数)- 损失计算: `DualStreamDetectionModel.loss()` 方法(位于 `tasks.py` 约第 2288 行)- 训练器: `ultralytics/models/yolo/detect/train.py` (`DualStreamDetectionTrainer`)- 验证器: `ultralytics/models/yolo/detect/val.py` (`DualStreamDetectionValidator`)- 数据加载: `ultralytics/data/dataset.py` (`YOLODualStreamDataset`)
## 常见问题速查1. `RuntimeError: Input type (Float) and weight type (Half)` → AMP dtype 问题,检查融合模块中的 LayerNorm/softmax 是否有 dtype cast2. `AssertionError: len(G.shape) == 2` → Muon 优化器不兼容,改用 `optimizer="AdamW"`3. `KeyError: 'ir_img'` → 数据集配置缺少 `ir_train`/`ir_val` 字段4. `RuntimeError: Expected all tensors on the same device` → DDP 下设备不一致,检查 forward 中是否有 hardcoded device
请先分析 Traceback 定位到出错的具体代码行,然后说明根因和修复方案,最后直接修改代码。7. 模型审计提示词
使用场景
新融合模块集成后,执行标准化审计验证。
提示词模板
请对新集成的融合模块执行标准审计。
## 审计信息- 模型配置: {{YAML 文件路径}}- 融合模块类名: {{例如: SEFusion}}
## 必须通过的审计门(不可跳过任何一项)
### G1: 前向传播测试- 构建模型并执行前向传播- 输入: 随机 RGB (1,3,640,640) 和 IR (1,3,640,640)- 验证: 不报错且输出形状合理
### G2: IR 敏感性测试- 用相同 RGB + 不同 IR 执行两次前向- 验证: 两次输出的差异 > 0(证明 IR 分支参与了计算)
### G3: 梯度回流测试- 对输出计算 loss 并反向传播- 验证: IR 骨干的参数梯度不为零(证明梯度能流过融合模块到 IR 骨干)
### G4: 模型摘要- 打印完整模型摘要(层数、参数量、GFLOPs)- 验证: 融合模块的实例数量正确(通常为 3,对应 P3/P4/P5)
### G5: AMP 兼容性- 测试 1: FP32 前向 → 必须 PASS- 测试 2: FP32 + autocast → 必须 PASS- 测试 3: Half + eval (无 autocast) → 必须 PASS(这是验证时的路径)- 测试 4: Half + autocast → 必须 PASS
## 输出1. 将审计脚本写入 `scripts/audit_{{模块名小写}}.py`2. 运行脚本并汇报结果(PASS/FAIL 表格)3. 如有 FAIL,先修复代码再重新审计
## 参考- 现有审计脚本: `scripts/audit_tfblock_gates.py`- 现有 AMP 测试: `scripts/test_amp_fix.py`8. 上游冲突解决提示词
使用场景
执行 git merge upstream/main 后出现冲突,需要 AI 帮助解决。
提示词模板
我在同步上游 Ultralytics 仓库 (`upstream/main`) 到本项目时出现了 Git 合并冲突,请帮我逐个解决。
## 项目背景本项目是基于 Ultralytics YOLOv26 的 fork,新增了 **RGB+IR 双流检测功能**。我们的改动集中在以下方面:- 新增融合模块(ChannelFusion, AddFusion, TransformerFusion, TransformerFusionBlock 等)- 新增双流模型架构(DualStreamDetectionModel, parse_dual_stream_model)- 新增双流数据加载(YOLODualStreamDataset)- 新增双流训练/验证器(DualStreamDetectionTrainer, DualStreamDetectionValidator)
## 当前冲突文件列表
{{粘贴 git diff --name-only --diff-filter=U 的输出}}
## 强制约束(不可违反)
### 优先级规则1. **上游的 Bug 修复和性能优化** → 必须采纳(用上游的版本)2. **我们新增的双流功能代码** → 必须保留(不能丢失)3. **双方都修改了同一行** → 以上游为准,然后手动将我们的功能加回去4. **上游删除了我们修改过的代码** → 需要仔细分析,将我们的功能适配到上游的新写法
### 受保护的代码区域(绝对不能删除)以下代码是我们双流功能的核心,合并时**绝对不能丢失**:
**`ultralytics/nn/modules/block.py`:**- `ChannelFusion` 类- `AddFusion` 类- `TransformerFusion` 类- `TransformerFusionBlock` 类及其依赖: `CrossTransformerBlock`, `CrossAttention`, `AdaptivePool2d`, `LearnableWeights`, `LearnableCoefficient`- 所有上述类都位于文件**末尾**,通常不会与上游冲突
**`ultralytics/nn/tasks.py`:**- `_FUSION_REGISTRY` 字典(约第 1844 行)- `parse_dual_stream_model()` 函数- `parse_dual_stream_head()` 函数- `DualStreamDetectionModel` 类(包括其 `__init__`, `_predict_once`, `loss` 方法)- `loss()` 方法中的 `torch.amp.autocast("cuda")` 包装(AMP 修复,不能删除)
**`ultralytics/nn/modules/__init__.py`:**- 导出列表中的: `ChannelFusion`, `AddFusion`, `TransformerFusion`, `TransformerFusionBlock`, `CrossTransformerBlock`, `CrossAttention`, `AdaptivePool2d`, `LearnableWeights`, `LearnableCoefficient`
**`ultralytics/data/dataset.py`:**- `YOLODualStreamDataset` 类
**`ultralytics/models/yolo/detect/train.py`:**- `DualStreamDetectionTrainer` 类
**`ultralytics/models/yolo/detect/val.py`:**- `DualStreamDetectionValidator` 类
**`ultralytics/engine/model.py`:**- `dual_stream: True` 路由逻辑
### 处理流程(逐文件执行)对每个冲突文件,请按以下步骤操作:
1. **查看冲突内容**: 打开文件,找到所有 `<<<<<<<` / `=======` / `>>>>>>>` 标记2. **分类冲突**: 判断每处冲突属于以下哪种情况: - (A) 上游修改了原有代码,我们没改 → 直接用上游版本 - (B) 我们新增了代码,上游没有 → 保留我们的代码 - (C) 双方都修改了同一段 → 以上游为基础,手动合并我们的改动 - (D) 上游重构/移动了代码位置 → 将我们的功能适配到新位置3. **解决冲突**: 编辑文件,删除冲突标记,保留正确内容4. **向我汇报**: 每个文件解决后,说明每处冲突的分类 (A/B/C/D) 和处理方式
### 完成后的验证(全部必须通过)
# 1. 无冲突标记残留grep -rn '<<<<<<< \|======= \|>>>>>>> ' ultralytics/ && echo '❌ 仍有冲突标记!' || echo '✅ 无冲突标记'
# 2. 基线双流模型构建python -c "from ultralytics import YOLO; m = YOLO('ultralytics/cfg/models/26/yolo26-rgbir.yaml'); print('✅ 基线模型 OK')"
# 3. TFBlock 模型构建python -c "from ultralytics import YOLO; m = YOLO('ultralytics/cfg/models/26/yolo26-rgbir-tfblock-all.yaml'); print('✅ TFBlock 模型 OK')"
# 4. 前向传播python -c "import torchfrom ultralytics import YOLOm = YOLO('ultralytics/cfg/models/26/yolo26-rgbir.yaml')m.model.cuda().eval()rgb = torch.randn(1,3,640,640).cuda()ir = torch.randn(1,3,640,640).cuda()with torch.no_grad(): out = m.model({'rgb': rgb, 'ir': ir})print('✅ 前向传播 OK')"
# 5. 标准单流模型不受影响python -c "from ultralytics import YOLO; m = YOLO('yolo11n.yaml'); print('✅ 单流模型 OK')"
# 6. 融合注册表完整性python -c "from ultralytics.nn.tasks import _FUSION_REGISTRYrequired = ['ChannelFusion', 'AddFusion', 'TransformerFusion', 'TransformerFusionBlock']for r in required: assert r in _FUSION_REGISTRY, f'❌ {r} 未注册!'print(f'✅ 融合注册表完整: {list(_FUSION_REGISTRY.keys())}')"
全部验证通过后,执行:
git add .git commit -m "merge: sync upstream ultralytics ({{简要描述上游更新内容}})"git push
## 参考- 上游同步完整流程文档: `docs/zh/上游同步指南.md`- 我们改动的文件完整列表: `docs/zh/新增文件清单.md` 第五节使用建议
- 按顺序使用: 数据集处理 → 模型配置 → 训练脚本 → 训练 → 结果分析
- 替换占位符: 所有
{{...}}必须替换为实际值 - 分阶段确认: 融合模块迁移提示词设计为分阶段执行,每阶段完成后确认再继续
- 保留上下文: 同一个任务的多个提示词建议在同一个对话中使用,AI 能保持上下文
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!