diff --git a/docs/探索日志.md b/docs/探索日志.md index 6a61028..2c16616 100644 --- a/docs/探索日志.md +++ b/docs/探索日志.md @@ -299,6 +299,7 @@ - 与基线对比:到达 **0→42.2%**、碰撞 **99.6→2.9%**、ratio2s **0.755→0.270**;代价:DAgger 微调使开环 ol_fk 5.98→15.7 mm。 - 相对 §9.2:到达 42.2% vs ≥98%(差距大);碰撞 2.9% vs 0(接近策略单独 <3% 线);用时 ≤老师×1.3 为 29.1%;推理+认证 P99 未测(桌面均值 12.58 ms,预算 20 ms)。 - 未进入大规模训练(需用户确认 §9.2 锚点)。 +- 训练方法探索(轮次 19–22):APG(管线修复、尺度失控)、反馈蒸馏(未内化)、DAgger-3(39.5%)、冻结编码器(41.8%)——均未超过 dag2 基线 43.8%。 ## 轮次 19:APG 闭环策略梯度试跑(exp: `exp/dagger-deploy`,管线修复但暂不采用) @@ -347,6 +348,23 @@ - **结论**:DAgger-3 到达 **-4.3 pp**(39.5 vs 43.8),time_ok -7.8 pp;碰撞/刹停略好但主指标下降。开环进一步退化(ol_fk 19.5 mm vs dag2 15.7)。**负结论**:继续微调(15000 步 lr 3e-4)会退化主分布,训练侧收益递减。 - **下一步**:冻结编码器、只微调解码器(假设:微调退化来自表征漂移),用同一份数据与预算验证。 +## 轮次 22:冻结编码器微调(exp: `exp/dagger-deploy`,负结论) + +- **假设**:微调导致的分布退化来自表征漂移;冻结编码器(enc + enc_norm)、只调解码器/头,可保留开环精度同时吸收 DAgger-3 的部署分布样本。 +- **判据**:部署口径 success > 43.8%(dag2)且 ol_fk 不退化(≤16 mm)。 +- **配方**:`train.py --freeze-enc`(优化器只含 requires_grad 参数),数据 = 主数据 + DAgger-3 样本,lr 3e-4、5000 步,从 dag2 best 初始化;n=256 部署口径。 +- **对照**:dag2 + gain1.8(43.8% / 28.9% / 1.6% / 0.216 / 40.0 / 956 ms)。 +- **指标**: + +| 模型 | gain | 到达 | time_ok_1p3 | 碰撞 | ratio2s | 刹停/题 | 墙钟/题 | ol_fk | +|---|---|---|---|---|---|---|---|---| +| 冻结编码器 | 1.5 | 39.8% | 22.3% | 1.2% | 0.293 | 25.4 | 936 ms | 15.8 mm | +| 冻结编码器 | 1.8 | 41.8% | 26.2% | 2.3% | 0.274 | 41.4 | 940 ms | 15.8 mm | +| dag2(基线) | 1.8 | **43.8%** | **28.9%** | 1.6% | **0.216** | 40.0 | 956 ms | 15.7 mm | + +- **结论**:冻结编码器**确实减缓了开环退化**(ol_fk 15.8 vs 全微调 19.5),但部署到达仍比 dag2 低 2.0 pp——**无提升**。至此训练侧四种方法(APG、反馈蒸馏、DAgger-3、冻结编码器)均未超过 dag2 基线(43.8%),训练侧在当前数据/预算下已到平台。 +- **下一步(超出小规模范围,需确认)**:更大规模训练(24000 步从头 + 8000×2 轮 DAgger)或改特征/架构(目标条件化、状态扰动训练需重算特征)。 + ## 分支清理(2026-10-20) - `feat/fb-mix`(8e5431c):反馈混合方案的中间分支,**已验证是 `exp/dagger-deploy` 的祖先**(所有提交都在后者历史里),本地/远端分支删除,内容保留在 exp/dagger-deploy。 diff --git a/ml/train.py b/ml/train.py index ad6ddf6..b5ff0bd 100644 --- a/ml/train.py +++ b/ml/train.py @@ -73,6 +73,7 @@ def main(): ap.add_argument('--config', default='ml/configs/base.json'); ap.add_argument('--steps', type=int, default=None) ap.add_argument('--resume', default=None); ap.add_argument('--no-eval', action='store_true') ap.add_argument('--init', default=None, help='只载入权重(优先 EMA)、优化器与步数从 0 开始(DAgger 微调用)') + ap.add_argument('--freeze-enc', action='store_true', help='冻结编码器(enc + enc_norm),只微调解码器/头') ap.add_argument('--lr', type=float, default=None); ap.add_argument('--warmup', type=int, default=None) ap.add_argument('--stop-at', type=int, default=0, help='自检用:跑到第 N 步按停止请求处理(存 last.pt、rc 143)') ap.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu'); ap.add_argument('--seed', type=int, default=0) @@ -106,7 +107,7 @@ def main(): print(f'train samples {data.n}, val tasks {len(val_tasks)}, act_scale {act_scale}, data_ver {data.data_ver}, device {dev}', flush=True) model = build(cfg).to(dev); model.act_scale = act_scale print(f'params {sum(p.numel() for p in model.parameters()) / 1e6:.2f} M', flush=True) - opt = torch.optim.AdamW(model.parameters(), lr=tc['lr'], betas=tuple(tc['betas']), weight_decay=tc['wd']) + opt = torch.optim.AdamW([p_ for p_ in model.parameters() if p_.requires_grad], lr=tc['lr'], betas=tuple(tc['betas']), weight_decay=tc['wd']) step0, best, bad_evals = 0, -1.0, 0 def check_scale(ck, src): s0 = float(ck.get('act_scale', 1.0))