Set DDP variables设置分布式训练参数

world_size:表示参与分布式训练的进程总数(GPU 数量)。从环境变量 WORLD_SIZE 中读取(由分布式框架如 torch.distributed 自动设置),若未设置则默认为 1(单机单卡)。
global_rank:表示当前进程的全局排名(从 0 到 world_size-1)。从环境变量 RANK 中读取,若未设置则默认为 -1(非分布式模式).
resume 恢复

opt.data, opt.cfg, opt.hyp = check_file(opt.data), check_file(opt.cfg), check_file(opt.hyp) # check files
检查文件路径:check_file() 确保 opt.data(数据集配置)、opt.cfg(模型配置)、opt.hyp(超参数文件)存在。assert len(opt.cfg) or len(opt.weights), 'either --cfg or --weights must be specified'
assert len(opt.cfg) or len(opt.weights) 确保必须指定 --cfg(模型结构)或 --weights(预训练权重)。opt.img_size.extend([opt.img_size[-1]] * (2 - len(opt.img_size)))
opt.img_size.extend(...) 将 img_size 扩展为 2 个值(训练和测试尺寸相同),例如 [640] → [640, 640]。
DDP Model

if opt.local_rank != -1:如果 opt.local_rank 不是 -1,表示启用分布式训练with open(opt.hyp) as f:从 opt.hyp 指定的 YAML 文件中加载超参数。hyp = yaml.load(f, Loader=yaml.FullLoader) # load hyps使用 yaml.FullLoader 解析 YAML 文件为字典(hyp)。if 'box' not in hyp:检查超参数中是否存在 'box' 键(用于边界框损失计算)。如果不存在,则假设旧版参数 'giou' 存在,并将其重命名为 'box',同时输出兼容性警告。
Train

logger.info(opt)使用 logger 打印 opt(训练配置参数),方便用户查看当前的训练设置。if not opt.evolve:检查是否处于超参数进化模式(evolve)。如果不是,则执行后续的 TensorBoard 初始化逻辑。tb_writer = None # init loggers初始化 tb_writer(TensorBoard 写入器)为 None,后续会根据条件重新赋值。if opt.global_rank in [-1, 0]:检查当前进程的全局排名(global_rank),通常用于分布式训练。-1 表示单机训练,0 表示主进程(在多机多卡训练中)。只有主进程或单机训练时才执行以下操作,避免重复记录。logger.info(f'Start Tensorboard with "tensorboard --logdir {opt.project}", view at http://localhost:6006/')
打印提示信息,告诉用户如何启动 TensorBoard 服务,并指定日志目录为 opt.project(训练结果保存路径)。用户可以通过访问 http://localhost:6006/ 查看训练过程的可视化结果。tb_writer = SummaryWriter(opt.save_dir) # Tensorboard初始化 SummaryWriter(TensorBoard 的日志写入器),日志将保存到 opt.save_dir(模型和日志的保存目录)。train(hyp, opt, device, tb_writer, wandb)调用
train函数开始训练,传入以下参数:
hyp:超参数配置(如学习率、权重衰减等)。
opt:训练选项(如数据集路径、批次大小等)。
device:训练设备(如 cuda:0 或 cpu)。
tb_writer:TensorBoard 写入器,用于记录训练指标。
wandb:可选参数,用于 Weights & Biases 日志记录(如果启用)。

def train(hyp, opt, device, tb_writer=None, wandb=None):
定义训练函数 train,接收以下参数:
hyp:超参数配置(如学习率、权重衰减等)。
opt:训练选项(如数据集路径、批次大小等)。
device:训练设备(如 cuda:0 或 cpu)。
tb_writer(可选):TensorBoard 日志写入器,默认为 None。
wandb(可选):Weights & Biases 日志工具,默认为 None。logger.info(f'Hyperparameters {hyp}')
使用 logger 打印当前的超参数配置 hyp,方便调试和记录。wdir = save_dir / 'weights'定义权重文件的保存目录(save_dir/weights)。wdir.mkdir(parents=True, exist_ok=True) # make dir
parents=True:自动创建父目录(如果不存在)。
exist_ok=True:如果目录已存在,不报错。last = wdir / 'last.pt'保存最后一轮的模型权重。best = wdir / 'best.pt'保存验证集上性能最好的模型权重results_file = save_dir / 'results.txt'保存训练过程中的指标(如损失、精度等)。

with open(save_dir / 'hyp.yaml', 'w') as f:将超参数 hyp 保存到 save_dir/hyp.yaml 文件。with open(save_dir / 'opt.yaml', 'w') as f:将训练选项 opt(通过 vars(opt) 转换为字典)保存到 save_dir/opt.yaml 文件。

yaml.dump(hyp, f, sort_keys=False)
sort_keys=False 保持参数原始顺序,不按字母排序。plots = not opt.evolveplots:是否生成训练曲线图(如果是超参数进化模式 evolve,则不生成)。cuda = device.type != 'cpu'检查当前设备是否为 GPUdata_dict = yaml.load(f, Loader=yaml.FullLoader)
从 opt.data(数据集配置文件路径,如 data/coco.yaml)加载 YAML 文件,解析为字典 data_dict。

train_path = data_dict['train']test_path = data_dict['val']
从 data_dict 中获取训练集和验证集的路径

nc = 1 if opt.single_cls else int(data_dict['nc'])
类别数量。如果启用单类别模式(opt.single_cls),则强制设为 1;否则使用 data_dict['nc']。names = ['item'] if opt.single_cls and len(data_dict['names']) != 1 else data_dict['names']
类别名称列表。如果是单类别模式且配置文件中名称不唯一,则设为 ['item'];否则使用 data_dict['names']。assert len(names) == nc, '%g names found for nc=%g dataset in %s' % (len(names), nc, opt.data)检查类别数量 nc 和名称列表 names 的长度是否一致,否则报错。
Model

pretrained = weights.endswith('.pt')通过检查 weights 路径是否以 .pt 结尾,判断是否使用预训练权重with torch_distributed_zero_first(rank):确保分布式训练中,主进程先下载权重,其他进程等待。attempt_download(weights)如果本地不存在 weights 文件,则从网络下载(如从 YOLOv5 官方仓库下载)。ckpt = torch.load(weights, map_location=device)加载 .pt 文件到 ckpt(字典形式),并指定加载到 device(如 cuda:0 或 cpu)。ckpt 通常包含模型权重 state_dict、超参数 hyp、模型配置 yaml 等。if hyp.get('anchors'):如果超参数 hyp 中指定了 anchors,则强制覆盖模型配置中的锚点参数。ckpt['model'].yaml['anchors'] = round(hyp['anchors'])round(hyp['anchors']) 对锚点值取整,确保其为整数。


model = Model(opt.cfg or ckpt['model'].yaml, ch=3, nc=nc).to(device)
如果 opt.cfg(模型配置文件路径)存在,则优先使用;否则使用预训练模型中的配置 ckpt['model'].yaml。
ch=3:输入图像的通道数(RGB)。
nc=nc:类别数量(从数据集配置中获取)。
.to(device):将模型移动到指定设备(GPU/CPU)。

exclude = ['anchor'] if opt.cfg or hyp.get('anchors') else []如果使用自定义配置(opt.cfg)或覆盖锚点(hyp['anchors']),则排除 anchor 相关权重(防止冲突)。state_dict = ckpt['model'].float().state_dict()将预训练权重转换为 FP32 格式。state_dict = intersect_dicts(state_dict, model.state_dict(), exclude=exclude)筛选预训练权重中与当前模型结构匹配的部分(忽略不匹配的层)。model.load_state_dict(state_dict, strict=False)加载权重,允许部分加载logger.info('Transferred %g/%g items from %s' % (len(state_dict), len(model.state_dict()), weights))记录成功加载的权重数量(例如 Transferred 300/320 items from yolov5s.pt)。else:model = Model(opt.cfg, ch=3, nc=nc).to(device) # create如果没有提供预训练权重(pretrained=False),则直接根据 opt.cfg 创建新模型,并随机初始化权重。
Freeze
freeze = []定义一个空列表 freeze,用于存储需要冻结的层名称。用户可以通过修改 freeze 列表(例如 freeze = ['backbone'])来冻结模型的某些部分。for k, v in model.named_parameters():遍历模型中所有可训练参数,k 是参数名称(如 model.0.conv.weight),v 是参数张量。v.requires_grad = True默认将所有参数的 requires_grad 设为 True,表示在训练时更新这些参数的权重。默认启用梯度计算if any(x in k for x in freeze): print('freezing %s' % k) v.requires_grad = False检查当前参数名称 k 是否包含 freeze 列表中的任何子字符串(例如 k 包含 backbone)。
如果匹配,则打印冻结日志,并将该参数的 requires_grad 设为 False(冻结权重,不参与梯度更新)
若 freeze = ['backbone'],则所有名称中包含 backbone 的层(如backbone.conv1.weight)会被冻结。
optimizer 优化器

nbs = 64 # nominal batch size名义批次大小(Nominal Batch Size),默认为 64,作为基准值。accumulate = max(round(nbs / total_batch_size), 1)梯度累积步数。当实际总批次大小(total_batch_size,如多卡训练的全局批次)小于 nbs 时,通过多次前向-反向传播累积梯度,再更新权重(等效增大批次大小)。hyp['weight_decay'] *= total_batch_size * accumulate / nbs根据实际批次大小调整权重衰减系数,保持与名义批次的一致性。pg0, pg1, pg2 = [], [], []
将模型参数分为三组,用于差异化优化策略:
pg0:BatchNorm2d 层的权重(不应用权重衰减)。
pg1:普通卷积层的权重(应用权重衰减)。
pg2:所有层的偏置参数(不应用权重衰减)。if opt.adam:
如果使用 Adam 优化器(opt.adam=True):
初始化 Adam,设置学习率 lr0,并将 beta1 替换为超参数 momentum。
默认使用 SGD 优化器:
初始化 SGD,设置学习率 lr0、动量 momentum,并启用 Nesterov 加速。optimizer.add_param_group({'params': pg1, 'weight_decay': hyp['weight_decay']})optimizer.add_param_group({'params': pg2})
将分组参数添加到优化器:
pg1(卷积权重)附带权重衰减。
pg2(偏置)不附带权重衰减。lf = one_cycle(1, hyp['lrf'], epochs)one_cycle:生成一个余弦退火学习率调度函数,初始值为 1,最终值降至 hyp['lrf']scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
LambdaLR:将调度函数 lf 应用到优化器,实现学习率动态调整。
Resume

start_epoch:初始化为 0,表示训练从第 0 个 epoch 开始。
best_fitness:初始化为 0.0,用于记录模型的最佳性能指标
如果 pretrained 为 True,表示需要加载预训练模型的参数。
ckpt 是预训练模型的检查点(checkpoint)字典。
如果检查点中包含优化器状态(ckpt['optimizer'] 不为 None),则将其加载到当前优化器(optimizer)中,以恢复训练时的优化器参数(如动量、学习率等)。
从检查点中加载之前保存的最佳性能指标(best_fitness),用于后续模型保存和早停(early stopping)等逻辑。
Results

ckpt 是预训练模型的检查点字典。使用 ckpt.get('training_results') 检查检查点中是否包含键 'training_results'(历史训练结果,如损失、精度等指标)。如果存在(不为 None),则执行后续操作。
results_file 是保存训练结果的文件路径(默认是 results.txt)。以写入模式('w')打开文件,并将检查点中的 training_results内容写入文件中。这样做的目的是恢复之前的训练记录,方便后续继续训练时能够跟踪完整的训练历史。
Epochs

从检查点 ckpt 中加载之前训练到的 epoch 值(ckpt['epoch']),并 +1 作为本次训练的起始 epoch。
如果用户通过命令行参数 --resume 显式要求恢复训练:
使用 assert 检查 start_epoch 是否大于 0。如果为 0,说明之前已经完成了全部 epochs 的训练,无需恢复,直接报错提示。
错误消息示例:"model.pt training to 200 epochs is finished, nothing to resume."
如果用户设置的 epochs(本次训练的总 epoch 数)小于 start_epoch(恢复的起始 epoch):
说明用户可能想继续微调(fine-tune)模型,因此将总 epoch 数调整为 原 epochs + 已训练的 epoch 数。
例如:检查点已训练 50 个 epoch,用户设置 epochs=10,则实际总 epoch 数为 50 + 10 = 60。
del ckpt, state_dict删除检查点 ckpt 和模型状态字典 state_dict,释放内存资源。
Image size

gs = int(model.stride.max())
model.stride 是模型各层的下采样步长(如 [8, 16, 32])。
max() 取最大步长(如 32),并转换为整数 gs。
作用:gs 表示特征图的最小网格尺寸(grid size),用于后续调整图像尺寸对齐。nl = model.model[-1].nl
model.model[-1] 是模型的最后一层(检测头,如 Detect 层)。
nl 是检测头的层数(YOLOv5 默认为 3,对应 3 种尺度的检测输出)。imgsz, imgsz_test = [check_img_size(x, gs) for x in opt.img_size]
opt.img_size 是用户设置的训练和测试图像尺寸(如 [640, 640])。
check_img_size(x, gs) 是一个函数,确保图像尺寸 x 是 gs 的整数倍(避免特征图尺寸出现小数)
输出:
imgsz:调整后的训练图像尺寸。
imgsz_test:调整后的测试图像尺寸。
假设 gs=32,用户设置 img_size=640:
检查 640 % 32 == 0(满足条件,直接返回 640)。
如果用户设置 img_size=641,则自动向下舍入到最近的合法值 640。
DP model 数据并行(Data Parallelism)

cuda:布尔值,表示是否启用 CUDA(即是否使用 GPU)。
rank == -1:rank 是分布式训练的进程编号,-1 表示当前是非分布式训练(单机多 GPU)。
torch.cuda.device_count() > 1:检测当前系统中可用的 GPU 数量是否大于 1。
条件满足时:执行 DataParallel 包装,启用多 GPU 数据并行。
SyncBatchNorm 同步批归一化

opt.sync_bn:布尔值,表示用户是否通过命令行参数 --sync-bn 显式启用了同步批归一化。
cuda:布尔值,表示是否使用 GPU 训练。
rank != -1:rank 是分布式训练的进程编号,-1 表示单机单 GPU 训练,非 -1 表示当前是分布式训练(如多 GPU 或多节点)
EMA 创建指数移动平均
ema = ModelEMA(model) if rank in [-1, 0] else None
ModelEMA(model):这是一个封装类,用于计算模型的指数移动平均(EMA)。
EMA 是一种平滑模型参数的技术,可以减少训练过程中的波动,提升模型的泛化能力。
它会维护一个影子模型(shadow model),在每次参数更新时,按一定衰减率(如 0.999)更新影子模型的参数。
rank in [-1, 0]:rank 表示当前进程的编号,用于分布式训练(如 torch.distributed 或 multiprocessing)。
rank = -1:单机单卡(非分布式训练)。
rank = 0:分布式训练的主进程(负责日志、保存模型等)。
Trainloader 创建数据加载器

train_path:训练数据路径(如 data.yaml 中定义的路径)。
imgsz:输入图像的尺寸(如 640)。
batch_size:每个批次的样本数。
gs:模型步长(grid size),用于确保图像尺寸能被 gs 整除。
opt:命令行参数对象(包含超参数配置)。
hyp:超参数字典(如学习率、数据增强参数等)。
augment=True:启用数据增强(如翻转、缩放等)。
cache=opt.cache_images:是否缓存图像到内存(加速后续训练)。
rect=opt.rect:是否使用矩形训练(减少填充,提升效率)。
rank:当前进程编号(分布式训练时使用)。
world_size:总进程数(分布式训练时使用)。
workers:数据加载的子进程数。
image_weights:是否按样本权重采样(解决类别不平衡)。
quad:是否使用四合一拼接数据增强(Mosaic 增强的变种)。
mlc = np.concatenate(dataset.labels, 0)[:, 0].max()从数据集的标签中提取所有类别编号,并计算最大值 mlc(Max Label Class)。
dataset.labels:数据集中所有图像的标签列表(每个标签的格式为 [class_id, x_center, y_center, width, height])。
np.concatenate(dataset.labels, 0):将所有标签拼接成一个二维数组。
[:, 0]:提取所有标签的类别编号(第 0 列)。
.max():计算类别编号的最大值。
process 0

plot_labels(labels, save_dir, loggers)生成 labels.jpg 文件,显示类别频率和框的宽高比。check_anchors(dataset, model=model, thr=hyp['anchor_t'], imgsz=imgsz)
根据训练数据集的标签,自动优化模型的锚框尺寸。
hyp['anchor_t']:锚框匹配阈值(超参数,默认 4.0)。
imgsz:输入图像尺寸。
model parameter

hyp['cls'] *= nc / 80.根据实际类别数 nc 调整分类损失权重 hyp['cls'](默认基于 COCO 数据集的 80 类)。hyp['obj'] *= imgsz ** 2 / 640. ** 2 * 3. / nl根据输入图像尺寸 (imgsz) 和输出层数 (nl) 调整目标存在损失权重 hyp['obj']。
start train

t0 = time.time()记录训练开始的时间戳,用于后续计算总训练耗时。nw = max(round(hyp['warmup_epochs'] * nb), 1000)确定学习率热身(Warmup)阶段的迭代次数。
hyp['warmup_epochs']:热身轮次数(默认 3 轮)。
nb:每个 epoch 的批次数量(len(dataloader))。
max(..., 1000):至少热身 1000 次迭代(避免小数据集热身不足)。maps = np.zeros(nc)存储每个类别的 mAP(初始为 0)。results = (0, 0, 0, 0, 0, 0, 0)
初始化评估结果元组,包含:
P:精确率(Precision)
R:召回率(Recall)
mAP@.5:IoU 阈值为 0.5 的 mAP
mAP@.5-.95:IoU 阈值从 0.5 到 0.95 的平均 mAP
val_loss:验证损失(边界框、目标存在、分类损失)scheduler.last_epoch = start_epoch - 1设置学习率调度器的起始轮次,last_epoch 必须比当前轮次小 1,否则调度器会跳过第一轮调整。scaler = amp.GradScaler(enabled=cuda)启用混合精度训练,enabled=cuda:仅在 GPU 训练时启用。
Update image weights (optional)

mean losses

mloss = torch.zeros(4, device=device)初始化一个长度为 4 的张量,用于累积当前 epoch 的四种损失均值:
mloss[0]:边界框损失(box_loss)
mloss[1]:目标存在损失(obj_loss)
mloss[2]:分类损失(cls_loss)
mloss[3]:总损失(total_loss)pbar = enumerate(dataloader)logger.info(('\n' + '%10s' * 8) % ('Epoch', 'gpu_mem', 'box', 'obj', 'cls', ``'total', 'targets', 'img_size'))
日志输出示例:
Epoch gpu_mem box obj cls total targets img_size
gpu_mem:GPU 显存占用(GB)
box/obj/cls/total:各项损失值
targets:当前批次的目标数量
img_size:图像尺寸pbar = tqdm(pbar, total=nb)
仅在主进程(单卡或 rank=0)显示进度条,避免多进程日志混乱。for i, (imgs, targets, paths, _) in pbar:
imgs:批次的输入图像(形状为 [batch_size, 3, H, W])
targets:标注信息(形状为 [num_targets, 6],每行格式为 [batch_index, class_id, x, y, w, h])
paths:图像文件路径(用于调试)ni = i + nb * epoch累计迭代次数 ni:用于学习率热身和日志记录。imgs = imgs.to(device, non_blocking=True).float() / 255.0
.to(device):将数据转移到 GPU(若 cuda=True)。
non_blocking=True:异步传输,提升数据加载效率。
/ 255.0:将像素值从 [0, 255] 归一化到 [0.0, 1.0]。
Warm up

accumulate = max(1, np.interp(ni, xi, [1, nbs / total_batch_size]).round())nbs:名义批次大小(nominal batch size,如 64)。
total_batch_size:实际批次大小(单卡 batch_size × 卡数)。
Multi scale

Forward Backward optimize

with amp.autocast(enabled=cuda):启用自动混合精度(Automatic Mixed Precision, AMP)训练(如果CUDA可用),以减少显存占用并加速计算。amp.autocast:自动将部分操作转换为低精度(FP16),同时保持关键部分的精度(FP32)。pred = model(imgs)将输入图像 imgs 输入模型,得到预测结果 pred。
model(imgs):调用模型的前向传播方法。loss, loss_items = compute_loss(pred, targets.to(device), model)计算损失函数,返回总损失 loss 和各部分损失项 loss_items。
compute_loss:YOLOv5的损失函数,包括分类损失、边界框损失和置信度损失。
targets.to(device):将标注数据(目标)移动到与模型相同的设备(如GPU)。loss *= opt.world_size如果使用分布式训练(DDP模式),将损失乘以 world_size(GPU数量),以便后续梯度平均。
rank:当前进程的排名(-1表示单机训练)。
opt.world_size:参与训练的GPU总数。scaler.scale(loss).backward()使用梯度缩放(Gradient Scaling)执行反向传播。
scaler.scale(loss):在混合精度训练中,缩放损失值以避免梯度下溢。
.backward():计算梯度。if ni % accumulate == 0:每隔 accumulate 步(梯度累积步数)执行一次参数更新。
ni:当前迭代次数。
accumulate:梯度累积的步数(模拟更大的batch size)。scaler.step(optimizer)更新模型参数(优化器步进)。
scaler.step:在混合精度训练中,先反缩放梯度,再调用 optimizer.step()。scaler.update()调整梯度缩放因子(根据梯度动态调整缩放比例)optimizer.zero_grad()清空优化器的梯度缓存。ema.update(model)如果启用指数移动平均(EMA),更新EMA模型的参数。
ema.update(model):将当前模型的参数按一定权重(动量)更新到EMA模型中,用于提升模型泛化性。
Print plot

mloss = (mloss * i + loss_items) / (i + 1)计算并更新平均损失(mloss)。
mloss:历史平均损失的滑动平均值。
loss_items:当前批次的各项损失值(分类、边界框、置信度等)。
i:当前批次索引(用于滑动平均计算)mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0)获取当前GPU显存占用(单位:GB)。
torch.cuda.memory_reserved():返回已分配的显存总量。
1E9:转换为GB单位。s = ('%10s' * 2 + '%10.4g' * 6) % ('%g/%g' % (epoch, epochs - 1), mem, *mloss, targets.shape[0], imgs.shape[-1])格式化训练日志字符串,包括:
epoch/epochs-1:当前轮次/总轮次(如 1/300)。
mem:显存占用。
*mloss:展开平均损失值(分类、边界框、置信度等)。
targets.shape[0]:当前批次的标注目标数量。
imgs.shape[-1]:输入图像的尺寸(如 640)。pbar.set_description(s)更新进度条(tqdm)的显示信息,实时展示训练状态。Thread(target=plot_images, args=(imgs, targets, paths, f), daemon=True).start()启动一个后台线程(Thread)调用 plot_images 函数,绘制当前批次的图像和标注框。
imgs:输入图像张量。
targets:标注数据(类别、边界框)。
paths:图像文件路径(用于显示文件名)。
daemon=True:线程随主进程退出而终止。
模型评估 MAP

ema.update_attr(model, include=['yaml', 'nc', 'hyp', 'gr', 'names', 'stride', 'class_weights'])
如果启用了指数移动平均(EMA),则更新 EMA 模型的属性(如配置文件、类别数、超参数等),确保 EMA 模型与原始模型同步关键参数。final_epoch = epoch + 1 == epochs判断当前 epoch 是否是最后一个 epoch(epoch + 1 等于总 epoch 数 epochs)。if not opt.notest or final_epoch:如果未设置 --notest 参数(即需要测试)或当前是最终 epoch,则执行测试逻辑。results, maps, times = test.test调用 test.py 中的 test() 函数计算 mAP,传入以下参数:
opt.data:数据集配置文件路径(如 data/coco.yaml)。
batch_size:测试时的批次大小。
imgsz:测试图像尺寸。
model:使用 EMA 模型(如果启用)进行推理。
single_cls:是否单类别检测(由 --single-cls 参数控制)。
dataloader:测试数据加载器。
save_dir:结果保存目录。
plots:仅在最终 epoch 时生成评估图表(如 PR 曲线、混淆矩阵等)。
log_imgs:如果启用 WandB 日志,记录测试图像数量(由 --log-imgs 控制)。
输出结果:
results:包含精度(precision)、召回率(recall)、mAP@0.5、mAP@0.5:0.95 等指标。
maps:各类别的 mAP 值。
times:推理耗时统计。
Write/Log/Update best mAP

with open(results_file, 'a') as f:results_file:存储评估结果的文本文件路径(如 runs/train/exp/results.txt)。
'a' 模式:以追加(append)方式打开文件,保留历史记录。f.write(s + '%10.4g' * 7 % results + '\n') # P, R, mAP@.5, mAP@.5-.95,val_loss(box, obj, cls)
'%10.4g' * 7 % results:将 results 中的 7 个数值格式化为固定宽度(10 字符)、4 位有效数字的浮点数。通常对应:
Precision (P)
Recall (R)
mAP@0.5
mAP@0.5:0.95
验证损失(box, obj, cls)
'\n':换行符,确保每次写入的结果单独成行。if len(opt.name) and opt.bucket:opt.name:实验名称(非空时触发上传)。
opt.bucket:GCS 存储桶名称(如 my-bucket)。os.system('gsutil cp %s gs://%s/results/results%s.txt' % (results_file, opt.bucket, opt.name))使用 gsutil cp 命令将本地文件 results_file 复制到 GCS 路径 gs://{bucket}/results/results{name}.txt。
例如:若 opt.name='exp1' 且 opt.bucket='my-bucket',文件会被上传到 gs://my-bucket/results/results_exp1.txt。tags =
训练损失:
train/box_loss(边界框回归损失)、
train/obj_loss(目标置信度损失)、
train/cls_loss(分类损失)。
评估指标:
metrics/precision(精度)
metrics/recall(召回率)
metrics/mAP_0.5(mAP@0.5)
metrics/mAP_0.5:0.95(mAP@0.5:0.95)。
验证损失:
val/box_loss
val/obj_loss
val/cls_loss(与训练损失对应,但基于验证集计算)。
学习率:
x/lr0、x/lr1、x/lr2 分别对应优化器中不同参数组的学习率for x, tag in zip(list(mloss[:-1]) + list(results) + lr, tags):
mloss[:-1]:训练损失的均值(排除可能存在的额外统计项)。
results:评估指标(如 precision、recall、mAP 等,来自 test.test() 的返回值)。
lr:学习率列表(对应不同参数组)。
通过 zip() 将数值 (x) 和标签 (tag) 一一配对。tb_writer.add_scalar(tag, x, epoch) # tensorboard将数值 x 记录到 TensorBoard,标签为 tag,横轴为 epoch。fi = fitness(np.array(results).reshape(1, -1))
results 是一个包含模型评估指标的列表(如精确度 P、召回率 R、mAP@0.5、mAP@0.5-0.95 等)。
np.array(results).reshape(1, -1) 将 results 转换为一个 NumPy 数组,并调整形状为 (1, n),其中 n 是原始 results 的长度。
fitness() 是一个自定义函数,用于计算模型的适应度值。通常,适应度是这些指标的加权组合(如 0.1P + 0.9R 或其他权重配置),用于综合评估模型性能。if fi > best_fitness:比较当前适应度 fi 和历史最佳适应度 best_fitness。
如果当前适应度更高,说明模型性能有所提升。best_fitness = fi更新 best_fitness 为当前更高的适应度值 fi,用于后续比较和模型保存逻辑。
save model

save = (not opt.nosave) or (final_epoch and not opt.evolve)根据命令行参数 opt.nosave 和训练阶段(是否最后一轮 final_epoch 或是否在超参数进化模式 opt.evolve)判断是否需要保存模型。
opt.evolve:是否处于超参数进化模式(一种自动化调参方法)。with open(results_file, 'r') as f:读取训练结果文件

torch.save(ckpt, last)保存最后一次训练的模型if best_fitness == fi:
torch.save(ckpt, best)条件 best_fitness == fi 表示当前模型的适应度(fi)达到了历史最佳(best_fitness)。如果当前适应度是历史最佳,额外保存为最佳模型
Strip optimizers 移除优化器

检查当前进程:仅在主进程(rank 为 -1 或 0)执行操作。
选择最终模型:优先使用性能最佳的模型(best.pt),若不存在则使用最后一次训练的模型(last.pt)
移除优化器状态:对 last.pt 和 best.pt 文件调用 strip_optimizer(),以减小文件体积。
上传模型到云存储:如果指定了 Google Cloud Storage 的存储桶(opt.bucket),将最终模型上传。
Plots

Test best.pt

logger.info('%g epochs completed in %.3f hours.\n' % (epoch - start_epoch + 1, (time.time() - t0) / 3600))
记录训练完成的 epoch 数量和总耗时。
epoch - start_epoch + 1:计算实际训练的 epoch 数量(从 start_epoch 到当前 epoch)。
(time.time() - t0) / 3600:将训练时间(秒)转换为小时,保留 3 位小数。
通过 logger.info 输出日志,例如:"10 epochs completed in 2.345 hours."dist.destroy_process_group()
在分布式训练(多 GPU/多节点训练)结束时,清理并释放进程组资源。
如果训练时使用了 PyTorch 的分布式训练(如 torch.distributed),需要在训练结束后调用此函数,以避免资源泄漏或进程残留。torch.cuda.empty_cache()
释放 PyTorch 在 CUDA 设备(GPU)上未使用的缓存内存。
在训练或推理过程中,PyTorch 会缓存一部分 GPU 内存以加速后续操作(如张量分配、计算等)。
调用此函数会强制释放这些缓存,但不会影响已分配给张量的显存。