# YOLO26 / Ground3D 训练流程

## Overview

本文描述当前仓库里训练主干的真实流程，并额外指出 Ground3D 分支相对通用检测训练的差异。对应代码主要在：

- `ultralytics/engine/trainer.py`
- `ultralytics/models/yolo/detect/train.py`
- `ultralytics/data/dataset.py`
- `ultralytics/utils/loss.py`
- `train_mono3d.py`

## 通用主干流程

### 1. Trainer 初始化

`DetectionTrainer` / `Ground3DDetectionTrainer` 最终都会先走 `BaseTrainer.__init__()`：

1. `get_cfg()` 合并默认配置与 overrides
2. 处理 `resume`
3. 选择 device
4. 初始化随机种子
5. 创建 `save_dir` / `weights` 目录
6. 初始化 callbacks
7. 调用 `get_dataset()` 读取数据集元信息

### 2. 进入训练

`trainer.train()` 的逻辑是：

- 多卡且非已分布式启动时：生成 DDP 命令并拉起子进程
- 否则：直接进入 `_do_train()`

### 3. `_setup_train()`

训练正式开始前会做这些事：

1. `setup_model()`
2. `model.to(device)`
3. `set_model_attributes()`
4. `attempt_compile()`，若开启 `compile`
5. 冻结指定层
6. 检查 AMP
7. DDP 包装
8. `check_imgsz()`
9. 若 `batch < 1` 且支持 AutoBatch，则估计 batch size
10. `_build_train_pipeline()`
11. `get_validator()`
12. 初始化 EMA
13. 可选画 label 图
14. 初始化 early stopping
15. `resume_training()`

### 4. `_build_train_pipeline()`

当前代码里的真实行为是：

1. 构建 train dataloader
2. 构建 val dataloader
   - 大多数任务用 `batch_size // 4`
   - `obb` 例外，沿用 train batch size
3. 根据 `nbs` 计算梯度累积步数
4. `build_optimizer()`
   - `optimizer=auto` 时，`iterations > 10000` 选 `MuSGD`，否则选 `AdamW`
5. `_setup_scheduler()`
   - `cos_lr=True` 用 cosine
   - 否则用 linear decay

### 5. 主训练循环

每个 epoch 的主流程：

1. `scheduler.step()`
2. `model.train()`
3. 若到 `close_mosaic` 阶段，调用 `_close_dataloader_mosaic()`
4. 遍历 train loader
5. warmup 阶段动态调 lr / momentum / accumulate
6. `preprocess_batch(batch)`
7. `preds = model(batch["img"])`
8. `loss, loss_items = model.loss(batch, preds)`
9. 反向传播
10. 满足累积步数后 `optimizer_step()`
11. 记录进度条和 callbacks

epoch 末尾会：

1. 跑 validation
2. 写 `results.csv`
3. early stopping 检查
4. 保存 `last.pt` / `best.pt`
5. 到训练结束后做 `final_eval()`

## Ground3D 分支差异

### 1. 模型与 loss

Ground3D 使用的是：

- model: `Ground3DDetectionModel`
- trainer: `Ground3DDetectionTrainer`
- validator: `Ground3DDetectionValidator`
- loss:
  - `end2end=True` 时为 `E2EGround3DLoss`
  - 否则为 `v8Detection3DLoss`

这和通用检测的 `E2ELoss / v8DetectionLoss` 不是一回事。

### 2. 数据集

Ground3D 训练不是普通 `YOLODataset`，而是 `YOLOGround3DDataset`。

它的关键特征：

- 样本来自 GT list 文件，不是直接扫图片目录
- `get_image_and_label()` 内部显式执行 ROI crop 或 virtual-camera 变换
- 训练时会携带：
  - `labels_3d`
  - `calib`
  - `camera_mode`
  - `difficulty_levels`
  - 预计算 edge GT

### 3. 增强

Ground3D 当前不走标准几何增强。

`YOLOGround3DDataset.build_transforms()` 只追加：

1. `RandomHSV`
2. `Format`

因此：

- mosaic / mixup / random perspective / random flip / letterbox 不参与 Ground3D 训练增强
- `close_mosaic` 仍然存在于 trainer 主干里，但对 Ground3D 数据集本身基本没有实际几何增强可关闭

### 4. 预处理

`GroundDetectionTrainer.preprocess_batch()` / `Ground3DDetectionTrainer.preprocess_batch()` 的真实行为是：

- 先把 tensor 移到 device
- 图像做 `/ 256`
- 若启用 `multi_scale`，对 batch 图像做双线性 resize

注意这里不是 `/ 255`。

### 5. batch size

Ground3D 显式禁用了 AutoBatch：

- `Ground3DDetectionTrainer.auto_batch()` 直接报错
- 必须手动传 `--batch`

### 6. 2D / 3D / difficulty loss 的关系

Ground3D loss 由三块组成：

1. 2D detection loss
   - box
   - cls
   - dfl
2. difficulty loss
3. 3D loss

其中：

- `camera_mode == "virtual"` 的样本仍参与 `cls`
- 但会被从 2D `box/dfl` 中 mask 掉
- `difficulty loss` 单独相加
- 3D 项受 `loss_3d_weight` 控制

所以 `loss_3d_weight=0` 的 warmup 阶段并不是“只训练 2D box/cls/dfl”，而是“训练 2D + difficulty，不训练 3D 项”。

### 7. 3D 权重调度

`Ground3DDetectionTrainer.preprocess_batch()` 会在每个 batch 前更新当前 loss 权重：

- `epoch < loss_3d_warmup_epochs`：`loss_3d_weight = 0`
- 之后在 `loss_3d_ramp_epochs` 内线性拉升
- 最大到 `loss_3d_weight_max`

### 8. validation

Ground3D validation 在通用 2D metrics 之外，还会：

1. 从 `one2one` 头抓取 `preds_3d_selected` / `preds_edge_selected`
2. 用 `batch["calib"]` 的 `depth_scale` 恢复预测深度
3. 计算 3D metrics
4. 若 `roi_metrics_only=True`，可跳过 virtual-camera 样本，只统计 ROI 样本

## 一句话总结

当前仓库的训练主干仍然是标准 `BaseTrainer -> _setup_train -> _do_train` 框架，但 Ground3D 在数据集、loss、validation 和 ROI/virtual-camera 预处理上都已经是独立分支，不能再用“普通 YOLO detect 训练流”去概括。
