feat(engine): 训练评估循环、CenterNet 损失与指标
- trainer: AMP、梯度裁剪、cosine/step 调度、last/best 检查点,按 monitor 指标挑选 best - losses: penalty-reduced focal loss(heatmap)+ 掩码 L1(wh/offset)+ 分类损失 (多类 BCE / 单类 CE,随 label_mode 切换) - metrics: 定位 IoU 匹配下的 P/R/F1 与 mean IoU;分类多标签 micro-F1 与单标签 accuracy, 两种模式都输出 cls_f1,故 train.monitor 无需跟着改 - optim: BatchNorm 与 bias 不做 weight decay - visualizer: SwanLab 记录损失/学习率/验证指标/显存峰值,未安装或离线时降级为警告 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
af22dc639c
commit
ef64e5bf77
@@ -0,0 +1,61 @@
|
||||
"""优化器与学习率调度。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
|
||||
def build_optimizer(model: nn.Module, cfg: Mapping[str, Any]) -> torch.optim.Optimizer:
|
||||
"""`cfg` 形如 `{name: sgd, lr: 0.01, momentum: 0.9, weight_decay: 1e-4}`。
|
||||
|
||||
BatchNorm 与 bias 不做 weight decay——小数据集上这项影响明显。
|
||||
"""
|
||||
cfg = dict(cfg)
|
||||
name = str(cfg.pop("name", "adamw")).lower()
|
||||
weight_decay = float(cfg.pop("weight_decay", 0.0))
|
||||
|
||||
decay, no_decay = [], []
|
||||
for module in model.modules():
|
||||
for param_name, param in module.named_parameters(recurse=False):
|
||||
if not param.requires_grad:
|
||||
continue
|
||||
if param_name == "bias" or isinstance(module, nn.modules.batchnorm._BatchNorm):
|
||||
no_decay.append(param)
|
||||
else:
|
||||
decay.append(param)
|
||||
groups = [
|
||||
{"params": decay, "weight_decay": weight_decay},
|
||||
{"params": no_decay, "weight_decay": 0.0},
|
||||
]
|
||||
|
||||
if name == "sgd":
|
||||
cfg.setdefault("momentum", 0.9)
|
||||
cfg.setdefault("nesterov", True)
|
||||
return torch.optim.SGD(groups, **cfg)
|
||||
if name == "adam":
|
||||
return torch.optim.Adam(groups, **cfg)
|
||||
if name == "adamw":
|
||||
return torch.optim.AdamW(groups, **cfg)
|
||||
raise ValueError(f"未知优化器: {name}")
|
||||
|
||||
|
||||
def build_scheduler(optimizer: torch.optim.Optimizer, cfg: Mapping[str, Any] | None, epochs: int):
|
||||
"""返回按 epoch 步进的 scheduler;`cfg` 为空则不使用。"""
|
||||
if not cfg:
|
||||
return None
|
||||
cfg = dict(cfg)
|
||||
name = str(cfg.pop("name", "cosine")).lower()
|
||||
if name == "cosine":
|
||||
cfg.setdefault("T_max", epochs)
|
||||
return torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, **cfg)
|
||||
if name == "step":
|
||||
cfg.setdefault("step_size", max(epochs // 3, 1))
|
||||
return torch.optim.lr_scheduler.StepLR(optimizer, **cfg)
|
||||
if name == "multistep":
|
||||
cfg.setdefault("milestones", [int(epochs * 0.6), int(epochs * 0.85)])
|
||||
return torch.optim.lr_scheduler.MultiStepLR(optimizer, **cfg)
|
||||
raise ValueError(f"未知调度器: {name}")
|
||||
Reference in New Issue
Block a user