新增 action_norm.py 固定物理范围 min-max 归一化,无 stats 文件零状态部署;train.py 引入 _weighted_mse 逐列加权(舵机 1.0/spk 0.2/保留位 0.0)。审计修复: P0-1 pybot 读 normalize_mode, R2-1 NaN 阻断 best, P2-a 后端大小写不敏感, P1-3 utf-8-sig BOM, R1-1 data_dir 覆盖, R2-7 _run.sh 报错, R2-3 空 val 存 best.pt, P2-a2 误报警, R1-2 合并 parse_action_history, P2-b 合并 numpy import。评估修正 oob 物理界+error_growth 按 episode 中位数。推理部署全链支持 fixed_range。
10 KiB
pybot & actpred 审计报告(第二版 —— 归一化+逐列加权 落地后)
审查日期:2026-08-14 (Asia/Shanghai) 审查人:AI Assistant(SOLO Coder) 审查范围:
pybot/(机器人运行时)与actpred/(动作预测)两模块 代码基线:归一化+逐列加权方案落地后(12 个文件改动,531 行新增)本报告对照三份历史审计报告追踪修复状态,并审查本轮新增代码。
一、审计结论摘要
| 级别 | 新发现 | 历史遗留 | 说明 |
|---|---|---|---|
| P0(崩溃/数据损坏) | 2 | 0 | 见 §二 |
| P1(功能风险) | 0 | 5 | 见 §三 |
| P2(代码质量) | 3 | 9 | 见 §四 |
重点:2 个 P0 必须在提交前修复。
二、P0 新发现
P0-1 pybot HailoPredictor 缺失 normalize_mode 传递
return _P(checkpoint, stats_path=stats_path,
predict_delta=predict_delta,
pred_token=pred_token)
问题:HailoPredictor.__init__ 的 normalize_mode 参数默认 "zscore",pybot 调用时未传入此参数。对于 fixed_range 训练的 HEF,推理端应按 action_norm.py 固定物理范围对历史动作做 min-max→[0,1] 归一化,但 pybot 未传参导致 _fixed_range=False,历史动作以原始量纲送入 HEF。
影响:fixed_range 模式训练的 HEF 在 pybot 闭环中推理输出完全错误(舵机会被推到安全范围外)。zscore 模式不受影响(默认值偶然正确)。
修复建议:pybot 从同目录 checkpoint 读取 normalize_mode 并传入 HailoPredictor:
# 在 _load_predictor 中,从 companion checkpoint 读取 normalize_mode
normalize_mode = "zscore"
for name in ("pretrain_best_inference.pt", "best_inference.pt"):
ckpt_path = os.path.join(hef_dir, name)
if os.path.exists(ckpt_path):
try:
import torch
ck = torch.load(ckpt_path, map_location="cpu", weights_only=True)
normalize_mode = ck.get("config", {}).get("data", {}).get(
"normalize_mode", "zscore")
except Exception:
pass
break
return _P(checkpoint, stats_path=stats_path,
predict_delta=predict_delta,
pred_token=pred_token,
normalize_mode=normalize_mode)
P0-2 DataConfig normalize_actions 默认值变更,与 README 不一致
normalize_actions: bool = True # 旧默认值:False
问题:默认值从 False 改为 True,但 README.md 配置表 仍标注 False。CLI 不经 --normalize-actions 参数时,apply_overrides 会使用 dataclass 默认值 True,导致行为变更:
- 旧脚本(
bash _run.sh train --data-dir ./data --epochs 100)之前不归一化,现在开启 zscore 归一化,且会自动计算action_stats.npy。 - 对预训练(
pretrain_mt.py)同样影响:预训练数据从 .mt 脚本提取,zscore 归一化后分布与旧 checkpoint 不同。
影响:所有无 --normalize-actions 或无 --no-normalize-actions 的训练命令行为改变;旧 checkpoint 做 --init-from 时 warn_init_from_mismatch 会警告但不会阻止。
修复建议:二选一:
- 方案 A:恢复默认值
False(保持向后兼容,推荐); - 方案 B:改 README 为
True,并在 CHANGELOG 中标注 breaking change。
三、P1 历史遗留(未修复)
| # | 来源 | 位置 | 问题 | 优先级 |
|---|---|---|---|---|
| P1-3 | 闭环审计 | actpred/mt_parser.py:33 | read_text(encoding="utf-8") 不剥离 BOM,@Action 为首块时表头列名可能粘附 |
低(低概率触发) |
| P2-a | 闭环审计 | pybot/main.py:143 | _load_predictor 仅用 endswith(".hef") 区分后端,大小写敏感,无 ONNX 分支 |
中 |
| R2-1 | actpred 审计 | actpred/train.py:270 | NaN val_loss 永久阻断 best checkpoint 保存(NaN < x 恒 False) |
中 |
| R1-1 | actpred 审计 | actpred/pretrain_mt.py:185 | cfg.data.data_dir = args.mt_dir 覆盖,--save-config 导出语义错误 JSON |
低 |
| R2-7 | actpred 审计 | actpred/_run.sh:14-16 | 未知子命令静默回退到 predict.py,拼写错误难以发现 |
低 |
四、P2 历史遗留(未修复)
| # | 来源 | 位置 | 问题 |
|---|---|---|---|
| P2-b | 闭环审计 | pybot/robot.py 等 3 处 | numpy 在闭环路径 3 个函数各自 import |
| P2-c | 闭环审计 | pybot/tests/test_loop.py | 未覆盖闭环 + recorder 集成、predictor 异常清理 |
| P2-d | 闭环审计 | actpred/dataset.py | 图像 Resize 不保持宽高比 |
| P2-e | 闭环审计 | actpred/train.py | resume 时改 --epochs 会重算 lr scheduler |
| P2-f | 闭环审计 | actpred/export_deploy.py | ManualAttnBlock 依赖 PyTorch 内部布局约定 |
| R2-3 | actpred 审计 | actpred/train.py | 空验证集时 best.pt 从不创建 |
| R2-4 | actpred 审计 | actpred/_set.sh | CUDA 版 torch 误装风险 |
| R1-2 | actpred 审计 | predict/onnx_infer/hailo_infer | parse_action_history 三处重复 |
| R2-9 | actpred 审计 | actpred/predict.py | --action-dim override 参数无用且是陷阱 |
五、已完成修复的审计项
| # | 来源 | 修复内容 |
|---|---|---|
| P1-1 | 闭环审计 | 闭环节拍 overrun 告警已实现(robot.py:307-320),含比例统计 |
| P1-2 | 闭环审计 | 闭环帧源改走 _loop_recent_frames(robot.py:323-338),从守护线程队列取帧 |
| P1-4 | 闭环审计 | DataRecorder output_dir 已有 mkdir(parents=True, exist_ok=True)(recorder.py:46) |
| R2-2 | actpred 审计 | ActionDataset 外部 stats 文件已补 shape 校验(dataset.py:92-95) |
六、本轮新增代码审查(归一化+逐列加权方案)
6.1 action_norm.py — 通过,有 1 处 P2 建议
总体评价:常量表清晰、文档完善、clamp 防御到位。18 个单测覆盖正常值/边界/越界/多维度。
P2-a1(建议):default_column_weights 的 action_dim 默认为 18,但与 action_bounds 的行为不同——action_bounds 对 action_dim > 18 自动补零,default_column_weights 对此类情况返回全零权重(因为 min(10, action_dim) 和 min(12, action_dim) 仅覆盖前 12 列,其余列保持 np.zeros 初始值)。建议对齐行为:action_dim > 18 时对超出列也赋默认权重(如 0.2)。
6.2 config.py — 通过,有 1 处 P2
总体评价:normalize_mode 和 column_weights 的 CLI 解析、值域校验、checkpoint 兼容性警告均实现完整。
P2-a2(建议):warn_init_from_mismatch 中 init_mode = init_data.get("normalize_mode", "zscore") 对旧 checkpoint 正确回退。但旧 checkpoint 的 normalize_actions=False 时,normalize_mode 无意义,此时警告 init_mode != current.data.normalize_mode 是误报。建议仅在 init_norm and current.data.normalize_actions 时比较 normalize_mode。
6.3 dataset.py — 通过
固定范围归一化集成正确:_fixed_range 标志位在 __init__ 中设置,_norm 方法按标志位分发。stats shape 校验(R2-2 修复)已补。
6.4 train.py — 通过,有 1 处 P2 行为变更
总体评价:_weighted_mse 实现正确,weights.mean() 归一化保证加权 Loss 与无权重 Loss 量级可比。column_weights 为 None 时自动取 default_column_weights。
P2-a3(行为变更):column_weights 现在始终生效(None 时取默认值),即使 normalize_actions=False。这意味着不归一化训练的模型也会按列加权损失——模型对舵机列(权重 1.0)的优化力度高于 spk 列(0.2)。这并非 bug,但行为变更需在 README 注明。
6.5 evaluate.py — 通过
物理界 OOB 与 action_norm.py 常量表共用(_action_bounds 调用 action_bounds)。error_growth 按 episode 分别计算取中位数,消除长短 episode 混杂偏差。实现正确。
6.6 推理/部署链 — 通过,P0-1 除外
| 文件 | 审查结论 |
|---|---|
| predict.py | _fixed_range 从 checkpoint config 读取,normalize/denormalize 正确 |
| onnx_infer.py | normalize_mode 构造参数,predict 中 normalize/denormalize 正确 |
| hailo_infer.py | normalize_mode 构造参数,predict 中 normalize/denormalize 正确 |
| gen_calibration.py | fixed_range 模式校准数据走 normalize_action 变换,与推理端一致 |
| export_deploy.py | check_token_ranges 按 normalize_mode 在正确空间采样,stats_path 逻辑正确 |
唯独 pybot 的 _load_predictor 未传 normalize_mode 给 HailoPredictor(P0-1)。
6.7 test_actpred.py — 通过
新增 TestFixedRangeNorm 类 18 个测试用例,覆盖:
- 已知值归一化(舵机中位/边界、spk 中位)
- 越界 clamp(>180→1.0、<0→0.0)
- denormalize 往返一致性
- 默认列权重结构
- 自定义列权重
- 多维度批次归一化
- 全 0 输出验证
全量 148 passed / 11 skipped。
七、修复优先级
| 优先级 | 项 | 理由 |
|---|---|---|
| P0 | P0-1 HailoPredictor normalize_mode 缺失 | fixed_range HEF 在 pybot 闭环中推理完全错误 |
| P0 | P0-2 normalize_actions 默认值变更 | 所有无此参数的命令行为改变,README 不一致 |
| P1 | R2-1 NaN val_loss | 训练健壮性,一行 isfinite 修复 |
| P1 | P2-a 后端检测 | 路径大小写敏感,ONNX 无分支 |
| P2 | P2-a2 误报警告 | 旧 checkpoint 的不必要警告 |
| P2 | P2-a1 action_dim>18 | 未来扩展性 |
| P2 | P2-a3 行为变更文档 | README 注明 column_weights 始终生效 |
本报告为归一化+逐列加权方案落地后的专项审计。建议先修复 2 个 P0 再提交。