14794 字
74 分钟

【项目手册 | YOLO26-Dual】从0到1复现、融合实验与维护全流程

[TOC]

YOLO26-Dual 项目手册#

写在前面#

YOLO26-Dual此项目名字就是临时起意取的,YOLO项目推出了最新的26版本(模仿苹果这一块\.)本项目基于YOLO26仓库修改而来,理论上支持后续YOLO版本(只要结构不变)。Dual意为“双重”,所以拼接一下就“临时起意”了一个YOLO26-Dual

本仓库目前仅供内部学习使用,设为Private状态。

本项目通过Vibe Coding实现。

本说明文档将使你学会:

  1. 刚接触 YOLO 也能独立跑通;
  2. 做实验能稳定复现并横向对比融合模块;
  3. 后续接手维护有明确的排障与同步路线。

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 环境#

Terminal window
cd YOLO26-Dual
conda create -n yolo26dual python=3.10 -y
conda activate yolo26dual
pip install -U pip
pip install -e .

如需测试依赖:

Terminal window
pip install -e ".[dev]"

2.2 数据准备(以 LLVIP 数据集为示例,可替换为任意 RGB+IR 数据集)#

Terminal window
python scripts/prepare_llvip.py

prepare_llvip.py 此脚本可将 LLVIP 数据集处理为本项目可用的路径结构。

每个数据集对数据存放的格式不同,对此强烈建议让AI帮你处理。详见本文AI提示词部分。

2.3 健康检查#

Terminal window
python scripts/verify_dataset.py --data ultralytics/cfg/datasets/LLVIP.yaml
python scripts/verify_all_modules.py
python scripts/audit_tfblock_gates.py
pytest tests/test_dual_stream.py -v

verify_dataset.py 此脚本用于快速检查数据集配置文件是否符合双流YOLO训练需要。

verify_all_modules.py 生产环境中无用,

  • 作用:全模块普查
  • 详情:它会自动遍历项目中定义的所有融合模块,挨个加载并跑一次前向传播和反向传播。
  • 目的:确保每个模块都没语法错误,且梯度能回传,防止某个冷门模块坏了没人发现。但是生产环境会有人每次运行前检查所有融合模块吗?

2.4 跑一个 10 epoch baseline#

这里假定你本地安装好了修改后的Ultralytics库,已经切换了环境

Terminal window
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. 双流架构全景图#

flowchart TB A[RGB Input] --> B1["Backbone RGB"] C[IR Input] --> B2["Backbone IR"] B1 --> D1["P3 RGB"] B1 --> D2["P4 RGB"] B1 --> D3["P5 RGB"] B2 --> E1["P3 IR"] B2 --> E2["P4 IR"] B2 --> E3["P5 IR"] D1 --> F1["Fusion@P3"] E1 --> F1 D2 --> F2["Fusion@P4"] E2 --> F2 D3 --> F3["Fusion@P5"] E3 --> F3 F1 --> G["Dual-stream Head/FPN"] F2 --> G F3 --> G G --> H["Detect Output"]

解读这张图:

  • 双骨干独立提特征;
  • 融合发生在 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():融合输出接入 head
    • DualStreamDetectionModel_predict_once()loss()
    • 关键实现:把所有层拼成 flat Sequentialself.model,确保 stride / EMA / state_dict 与 Ultralytics 主流程兼容

4.3 数据与训练验证#

  • 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

4.4 融合模块实现#

  • ultralytics/nn/modules/block.py
    • 基础:ChannelFusion / AddFusion / TransformerFusion
    • 注意力块:TransformerFusionBlock
    • 扩展方法:TFusionCombinedFusionBlocksSFEGCAFFICFusionMaxFusionConcatFusionAddDFSCCSSA

4.5 双流判定与解析流程#

判定逻辑(_is_dual_stream()#

models/yolo/model.py
if self._is_dual_stream():
task_map["detect"] = {
"model": DualStreamDetectionModel,
"trainer": DualStreamDetectionTrainer,
"validator": DualStreamDetectionValidator,
"predictor": DetectionPredictor # MVP 回退
}

模型会从这几类信息判断是否走双流路由:

  1. 配置文件名是否包含 rgbir
  2. YAML 里是否有 dual_stream: True
  3. 已加载模型是否是 DualStreamDetectionModel

解析流程(parse_dual_stream_model#

flowchart TD A["读取双流YAML"] --> B["parse_model 构建 RGB backbone"] A --> C["parse_model 构建 IR backbone"] B --> D["按 fusion 配置构建融合层"] C --> D D --> E["parse_dual_stream_head 构建 head"] E --> F["组装为 DualStreamDetectionModel"]

这块是上游同步后最容易被覆写的逻辑之一,在同步合并冲突时一定优先核对。

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 会非常痛苦。

双流数据在训练中的生命周期如下:

sequenceDiagram participant DS as YOLODualStreamDataset participant CL as collate_fn participant TR as DualStreamDetectionTrainer participant MO as DualStreamDetectionModel DS->>DS: 读取 RGB 图 DS->>DS: 按同名规则找 IR 图 alt 找到IR DS->>DS: 加载真实 IR else 未找到IR DS->>DS: RGB转灰度作为伪IR end DS->>DS: LetterBox 对齐 + CHW + /255 DS->>CL: labels["img"], labels["ir_img"] CL->>CL: stack 成 batch["img"], batch["ir_img"] CL->>TR: 进入训练器 preprocess_batch TR->>TR: ir_img to(device) + dtype处理 TR->>MO: loss(batch) MO->>MO: _predict_once({"rgb": img, "ir": ir_img}) MO-->>TR: loss tuple

关键张量形状(默认)可按下面理解:

阶段张量形状示例
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/LLVIP
train: rgb/train/images
val: rgb/val/images
ir_train: ir/train/images
ir_val: ir/val/images
names:
0: person

⚠️ 注意:如果不写 ir_train / ir_val,你跑出来的是“伪双流”(RGB 转灰度作为 IR)。

如果找不到对应的 IR 图像,系统自动使用 *灰度回退*(将 RGB 转为灰度图并复制到 3 通道作为合成 IR)。适用于管线验证,但实际效果需要真实 IR 数据。

6.3 自定义数据集YAML模板#

path: ../datasets/YourDataset
train: rgb/train/images
val: rgb/val/images
ir_train: ir/train/images
ir_val: ir/val/images
names:
0: person
1: car

如果你的原始数据不是这个结构,先写一个预处理脚本把目录整理成这个格式再训练。(可让 AI 帮忙完成)

6.4 避免隐式失败:数据集校验工具#

由于 YOLODualStreamDataset 在找不到 IR 图像时会静默回退到灰度图,这可能导致你误以为自己在跑双流,实际跑的是“假双流”。

强烈建议在训练前运行校验脚本:

Terminal window
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 路线总览#

flowchart TD A["环境+数据准备"] --> B["审计与单测"] B --> C["10 epoch 基线"] C --> D["加入融合模块"] D --> E["Top-2/Top-3长训 50-100e"] E --> F["结果汇总与结论"]

8.2 第一阶段:健康检查#

Terminal window
python scripts/verify_dataset.py --data ultralytics/cfg/datasets/LLVIP.yaml
pytest tests/test_dual_stream.py -v

8.3 第二阶段:基线短训#

Terminal window
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为例)#

Terminal window
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 长时间不动,优先检查:

  1. 数据标注与配对是否正确;
  2. 是否出现了“伪双流”(IR 没真正参与);
  3. 学习率和 batch 是否过激导致震荡。
指标说明
Box(P)精确率 (Precision)
R召回率 (Recall)
mAP50IoU=0.5 时的均值平均精度
mAP50-95IoU=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 常用的索引对照#

骨干层索引对应层级常见用途
4P3高分辨率融合点
6P4中分辨率融合点
10P5低分辨率融合点

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)通道数融合模块说明
P3480×80 (1/8)128 (n-scale)ChannelFusionConcat + 1×1 Conv,轻量高效
P4640×40 (1/16)128 (n-scale)ChannelFusion同上
P51020×20 (1/32)256 (n-scale)TransformerFusion多头注意力 + FFN

9.4 TFBlock-All 配置: yolo26-rgbir-tfblock-all.yaml#

特征层骨干层索引分辨率 (640px)通道数融合模块说明
P3480×80 (1/8)128 (n-scale)TransformerFusionBlock交叉注意力融合,16×16 网格
P4640×40 (1/16)128 (n-scale)TransformerFusionBlock同上
P51020×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 可用融合模块#

模块位置参数原理
ChannelFusionnn.modules.block[c_out]拼接 + 1×1 Conv
AddFusionnn.modules.block[c_out]投影 + 逐元素加
TransformerFusionnn.modules.block[c_out, num_heads]简单 MHA + FFN
TransformerFusionBlocknn.modules.block[vert, horz, h]交叉注意力 + 空间锚点
TFusionnn.modules.block[c_out]3D 卷积 + 变形卷积
CombinedFusionBlocksnn.modules.block[c_out]多模块集成融合 (Ensemble)
SFEGnn.modules.block[c_out]空间特征提取与门控
CAFFnn.modules.block[c_out]通道注意力特征融合
ICFusionnn.modules.block[c_out]交互式通道融合
MaxFusionnn.modules.block[]逐元素取最大值
ConcatFusionnn.modules.block[c_out]拼接 + 降维 (同 ChannelFusion)
Addnn.modules.block[]简单逐元素加 (无投影)
DFSCnn.modules.block[c_out]密集特征选择卷积
CSSAnn.modules.block[]通道-空间自注意力

当前仓库可直接使用的实验配置:

  • yolo26n-rgbir-add.yaml -> Add
  • yolo26n-rgbir-caff.yaml -> CAFF
  • yolo26n-rgbir-combined.yaml -> CombinedFusionBlocks
  • yolo26n-rgbir-concatfusion.yaml -> ConcatFusion
  • yolo26n-rgbir-cssa.yaml -> CSSA
  • yolo26n-rgbir-dfsc.yaml -> DFSC
  • yolo26n-rgbir-icfusion.yaml -> ICFusion
  • yolo26n-rgbir-maxfusion.yaml -> MaxFusion
  • yolo26n-rgbir-sfeg.yaml -> SFEG
  • yolo26n-rgbir-tfblock-all.yaml -> TransformerFusionBlock
  • yolo26n-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. 先进行模块可用性验证;
  2. 再做 1~3 epoch 快速筛选;
  3. 挑 Top 候选做 50~100 epoch 长训;
  4. 只比较同条件实验(相同 seed、batch、imgsz、数据划分)。

10. 审计门(G1~G5)与证据#

10.1 审计门定义#

  • G1:前向传播与形状检查
  • G2:IR 敏感性(改 IR 后输出应变化)
  • G3:IR 梯度回流(IR 分支应有梯度)
  • G4:模型结构检查(例如 TFBlock 数量)
  • G5:解析器语义审计(AST 级检查)

10.2 当前关键审计指标#

项目结果
IR sensitivity delta159772.03125
IR grad max6.3955717
parse_model AST Hash6971f247988db3686ae172b00eedeac49bc61544cfdd65127b1a494871156845
10e 验证 best mAP500.0495
10e 验证 best mAP50-950.0161

对应文件:

  • artifacts/tfblock_all/ir_sensitivity.json
  • artifacts/tfblock_all/ir_grad_flow.json
  • artifacts/tfblock_all/parse_model_semantic_proof.txt
  • artifacts/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 不一致;
  • 处理:
    1. 融合模块输出 cast 回输入 dtype;
    2. 检查双流 loss() 是否在 autocast 上下文中重算 preds。

12.3 KeyError: 'ir_img'#

  • 原因:数据配置或加载链路断了;
  • 处理:
    • 检查 ir_train/ir_val 是否存在;
    • 检查 IR 路径与同名配对是否成立;
    • 检查是否走到了 YOLODualStreamDataset

12.4 输出对 IR 不敏感#

Terminal window
python scripts/audit_tfblock_gates.py

重点看 Gate2 与 Gate3。

12.5 git pull 后冲突或异常#

先保护本地改动:

Terminal window
git add .
git commit -m "wip" # 或 git stash

再进行同步,不要直接硬拉。具体可询问 AI 如何解决。


13. 如何新增融合模块#

建议严格按这 6 步执行:

  1. ultralytics/nn/modules/block.py 新增模块类;
  2. ultralytics/nn/modules/__init__.py 导出;
  3. tasks.py_FUSION_REGISTRY 注册;
  4. 如参数签名特殊,在 parse_dual_stream_model() 增加分支;
  5. 新建配置 ultralytics/cfg/models/26/yolo26n-rgbir-xxx.yaml
  6. 跑验证脚本与单测。

最小接口模板:

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_rgb
c_out = make_divisible(min(c_out, max_channels) * width, 8)

如果你想要完整示例(比如 SEFusion 从实现到注册到 YAML),可以直接对照:

  • docs/zh/融合模块开发指南.md 的完整示例节;
  • 现有实现 ChannelFusion(最简)与 TransformerFusionBlock(复杂)。

14. 上游同步与长期维护#

14.1 同步流程图#

flowchart LR A["fetch upstream"] --> B["merge upstream/main"] B --> C{"有冲突?"} C -- 否 --> D["运行最低验证"] C -- 是 --> E["按冲突高发文件逐个处理"] E --> D D --> F["commit + push"]

14.2 常规命令#

Terminal window
git remote add upstream https://github.com/ultralytics/ultralytics.git # 首次
git fetch upstream
git log --oneline main..upstream/main | head -20
git checkout main
git merge upstream/main

14.3 冲突处理最小流程#

当你在合并输出里看到 CONFLICT 或冲突标记时:

<<<<<<< HEAD
...你的内容...
=======
...upstream内容...
>>>>>>> upstream/main

处理原则:

  1. 上游 bugfix/性能修复优先保留;
  2. 双流核心能力必须补回;
  3. 处理后确认冲突标记已清理干净。

14.4 冲突高发文件#

  • ultralytics/nn/modules/block.py
  • ultralytics/nn/tasks.py
  • ultralytics/models/yolo/model.py
  • ultralytics/data/dataset.py
  • ultralytics/models/yolo/detect/train.py
  • ultralytics/models/yolo/detect/val.py

14.5 合并后的最低验证#

Terminal window
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.py

14.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.jsonGate2 指标
ir_grad_flow.jsonGate3 指标
model_summary.txt模型结构摘要
parse_model_semantic_proof.txtAST 哈希证明
train_10ep_log.txt10 epoch 训练日志

建议:跑长训之前至少执行一次 verify_all_modules.py + audit_tfblock_gates.py,并保存对应 artifacts。

新增文件清单与说明#

下面列出项目中所有新增的(不属于原版 YOLOv26 的)文件及其用途。


一、scripts/ — 脚本目录#

数据准备#

文件用途
prepare_llvip.pyLLVIP 数据集准备脚本。将下载的 LLVIP 压缩包解压并整理为框架要求的标准目录结构(rgb/train/imagesir/train/imageslabels/ 等)。

审计与测试#

文件用途
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.pyAMP 兼容性测试。对 TransformerFusionBlock 进行 5 种混合精度场景的测试(FP32、AMP 训练、Half+Eval、Half+Autocast、FP32+Autocast),验证 dtype 修复是否生效。
test_2ep_train.py2-epoch 快速管线测试。用 DDP 双卡跑 2 个 epoch(含训练 + 验证),验证完整训练管线是否能跑通,不关注精度。
verify_tfblock_roundtrip.py权重往返验证。加载训练保存的 best.pt,重新前向传播并与原始推理对比,确保模型保存/加载过程无损。

训练#

文件用途
train_tfblock_smoke.py烟雾测试 (1 epoch)。最小化训练运行,仅验证从构建模型到完成 1 个 epoch 的整个流程无报错。
train_tfblock_10ep.py10-epoch 效果验证。快速验证训练,用于判断融合模块是否在学习有效特征(loss 是否下降、mAP 是否上升)。
train_tfblock_full.py100-epoch 完整训练。正式训练脚本,含 OOM 自动降 batch 回退机制(64→32→16)。输出日志到 artifacts/tfblock_all/train_100e_log.txt

二、artifacts/tfblock_all/ — 审计产出物#

文件来源脚本内容说明
model_summary.txtaudit_tfblock_gates.py模型完整结构摘要(层数、参数量、GFLOPs)
ir_sensitivity.jsonaudit_tfblock_gates.pyIR 敏感性检查结果:修改 IR 输入后输出变化量 (Delta)
ir_grad_flow.jsonaudit_tfblock_gates.py梯度回流检查结果:IR 骨干参数的最大梯度值
parse_model_semantic_proof.txtaudit_parse_semantic.pyparse_model 函数的 AST 哈希值,用于追踪代码变更
git_diff.patch手动生成所有代码改动的 Git diff 补丁文件
test_2ep_log.txttest_2ep_train.py2-epoch DDP 测试的完整日志
train_10ep_log.txttrain_tfblock_10ep.py10-epoch 验证训练的完整日志
final_report.md手动编写TFBlock-All 审计总报告(汇总所有门、训练结果、Bug 修复记录)

三、ultralytics/cfg/ — 配置文件#

模型配置 (cfg/models/26/)#

文件说明
yolo26-rgbir.yaml基线双流模型。P3/P4 使用 ChannelFusion,P5 使用 TransformerFusion
yolo26-rgbir-tfblock-all.yamlTFBlock-All 实验模型。P3/P4/P5 全部使用 TransformerFusionBlock(16×16 网格、4 头注意力)。

数据集配置 (cfg/datasets/)#

文件说明
LLVIP.yamlLLVIP RGB+IR 行人检测数据集配置。定义了 RGB/IR 图像路径和类别(1 类: person)。

验证脚本 (scripts/)#

文件用途
verify_all_modules.py全模块验证脚本。自动遍历所有 11 个融合模块的配置文件,验证加载、前向传播形状和反向传播梯度流,确保模块可用性。

新增融合模块配置 (cfg/models/26/)#

文件说明
yolo26n-rgbir-tfblock-all.yamlTransformerFusionBlock (交叉注意力)
yolo26n-rgbir-tfusion.yamlTFusion (3D 卷积 + DeformConv)
yolo26n-rgbir-combined.yamlCombinedFusionBlocks (集成融合)
yolo26n-rgbir-sfeg.yamlSFEG (空间特征门控)
yolo26n-rgbir-caff.yamlCAFF (通道注意力融合)
yolo26n-rgbir-icfusion.yamlICFusion (交互式通道融合)
yolo26n-rgbir-maxfusion.yamlMaxFusion (最大值特征融合)
yolo26n-rgbir-concatfusion.yamlConcatFusion (拼接融合)
yolo26n-rgbir-add.yamlAdd (直接相加)
yolo26n-rgbir-dfsc.yamlDFSC (密集特征选择)
yolo26n-rgbir-cssa.yamlCSSA (通道空间注意力)

四、docs/zh/ — 中文文档#

文件说明
rgb_ir_使用说明文档.md用户使用手册。面向使用者,涵盖快速开始、数据集准备、模型配置、训练参数、常见问题。
rgb_ir_修改文档.md代码修改记录。记录对 Ultralytics 源码的所有改动点(block.pytasks.pydataset.pytrain.pyval.py 等)。
融合模块开发指南.md开发者指南。面向研究者,讲解如何添加自定义融合模块、编写配置文件、准备数据集。含完整 SE-Fusion 示例。

五、ultralytics/ — 框架源码改动#

以下文件虽属于原版 YOLOv26,但包含了双流功能的新增代码:

文件新增内容
nn/modules/block.py新增 8 个类:ChannelFusionAddFusionTransformerFusionTransformerFusionBlockCrossTransformerBlockCrossAttentionAdaptivePool2dLearnableWeightsLearnableCoefficient
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. 拉取上游最新代码#

Terminal window
git fetch upstream

2. 查看上游有哪些新提交#

Terminal window
# 查看上游比你多了多少提交
git log --oneline main..upstream/main | head -20

3. 合并上游更新#

Terminal window
# 确保在 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_REGISTRYparse_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*.yaml
  • ultralytics/cfg/datasets/LLVIP.yaml
  • scripts/*
  • artifacts/*
  • ultralytics/models/yolo/detect/dual_stream_predict.py
  • docs/zh/*

5. 冲突解决实操#

当 Git 提示冲突时,打开冲突文件会看到类似:

<<<<<<< HEAD
我们的代码
=======
上游的代码
>>>>>>> upstream/main

处理原则

  1. 上游的原有代码 → 用上游的版本(他们修 bug、优化性能等)
  2. 我们新增的代码 → 保留(双流功能)
  3. 同一行的不同修改 → 需逐行判断,通常以上游为准再手动加回我们的改动
Terminal window
# 解决所有冲突后
git add .
git commit -m "merge: sync upstream ultralytics (描述上游更新内容)"
git push

6. 验证合并结果#

合并后务必运行以下验证:

Terminal window
# 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 torch
from ultralytics import YOLO
m = 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. 添加自己的(或论文中的)融合模块
  2. 编写模型配置文件
  3. 准备和适配数据集

目录#


一、添加自定义融合模块#

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_rgbc_ir,由解析器自动从骨干网络的通道数推断并传入
  • forward 必须接受两个位置参数 (x_rgb, x_ir)
  • 返回值必须是单个张量,尺寸与输入的空间分辨率一致 (H, W 不变)

1.2 编写模块代码#

ultralytics/nn/modules/block.py 文件末尾添加你的模块。以下是现有模块的参考实现:

最简实现:通道融合#

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 中导出#

ultralytics/nn/modules/__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_rgbc_ir 由解析器自动从骨干网络的输出通道推断,不需要在 YAML 中指定
  • f_args 对应 YAML 配置中的 args 列表
  • widthmax_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 output

1.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 配置#

ultralytics/cfg/models/26/yolo26-rgbir-se.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 torch
rgb = 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_idxintRGB 骨干的输出层索引(骨干网络层号)
ir_idxintIR 骨干的输出层索引(通常与 rgb_idx 相同)
modulestr融合模块类名
argslist额外参数列表(c_rgbc_ir 不需要写,解析器自动填入)

2.3 骨干层索引对照表#

标准 YOLOv26 骨干(n-scale)各层输出对照:

层索引模块输出通道 (n-scale)步长分辨率 (640px)对应特征层
0Conv162320×320P1
1Conv324160×160P2
2C3k2644160×160
3Conv64880×80P3
4C3k2128880×80P3 tap
5Conv1281640×40P4
6C3k21281640×40P4 tap
7Conv2563220×20P5
8C3k22563220×20
9SPPF2563220×20
10C2PSA2563220×20P5 tap

通常只在 P3 (层4)、P4 (层6)、P5 (层10) 处进行融合。这些是 FPN 的标准特征提取点。

2.4 完整配置示例#

ultralytics/cfg/models/26/yolo26-rgbir-custom.yaml
nc: 80
end2end: True
reg_max: 1
dual_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-fused
head:
- [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 数据集配置文件#

YourDataset.yaml
# 数据集根目录 (绝对路径或相对于项目根目录)
path: ../datasets/YourDataset
# RGB 图像路径 (相对于 path)
train: rgb/train/images
val: rgb/val/images
test: # 可选
# IR 图像路径 (相对于 path)
ir_train: ir/train/images
ir_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 shutil
from 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 数据,想先跑通流程#

系统自动支持灰度回退。只需:

  1. 不设置 ir_train / ir_val,或让 IR 路径为空
  2. 系统会自动将 RGB 图像转为灰度图作为合成 IR
# 最小化配置 (无 IR 数据)
path: ../datasets/YourDataset
train: images/train
val: images/val
# 不指定 ir_train/ir_val → 自动灰度回退
names:
0: person

场景 3: RGB 和 IR 分辨率不同#

没问题。框架会在数据加载时将 RGB 和 IR 统一缩放到 imgsz (默认 640×640)。不需要预处理。

场景 4: 使用 KAIST/FLIR 等公开数据集#

请按以下步骤操作:

  1. 下载数据集并解压
  2. KAIST 标注转换工具 将标注转为 YOLO 格式
  3. 按照 3.1 的标准结构整理目录
  4. 编写 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 torch
rgb = 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()` 进行双流推理。
```python
from 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() # 保存结果

自动配对规则:

  1. 传入 RGB 图像路径
  2. 查找路径字符串中的 rgb,替换为 ir
  3. 检查替换后的路径是否存在
  4. 如果存在,读取并使用 LetterBox 对齐
  5. 如果不存在,发出警告并使用 RGB 转灰度作为伪 IR
### IR 敏感性测试
验证 IR 分支是否真正参与计算:
```python
import 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 torch
from 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. 数据集处理提示词
  2. 训练脚本生成提示词
  3. 融合模块迁移提示词
  4. 模型配置文件生成提示词
  5. 训练结果分析提示词
  6. Bug 诊断提示词
  7. 模型审计提示词
  8. 上游冲突解决提示词

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 Dataset
path: ../datasets/{{数据集名称}}
train: rgb/train/images
val: rgb/val/images
test:
ir_train: ir/train/images
ir_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 YOLO
model = YOLO("ultralytics/cfg/models/26/yolo26-rgbir-{{描述名}}.yaml")
print(type(model.model).__name__) # 必须输出 DualStreamDetectionModel
print(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 cast
2. `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 torch
from ultralytics import YOLO
m = 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_REGISTRY
required = ['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` 第五节

使用建议#

  1. 按顺序使用: 数据集处理 → 模型配置 → 训练脚本 → 训练 → 结果分析
  2. 替换占位符: 所有 {{...}} 必须替换为实际值
  3. 分阶段确认: 融合模块迁移提示词设计为分阶段执行,每阶段完成后确认再继续
  4. 保留上下文: 同一个任务的多个提示词建议在同一个对话中使用,AI 能保持上下文

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

【项目手册 | YOLO26-Dual】从0到1复现、融合实验与维护全流程
https://mjy.js.org/posts/yolo26-dual-practical-guide/
作者
MaJianyu
发布于
2026-02-16
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
MaJianyu
永远相信,美好的事情即将发生。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
33
分类
6
标签
96
总字数
169,561
运行时长
0
最后活动
0 天前

目录