Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a3ead5d3d | ||
|
|
4d224e586a |
@@ -8,6 +8,7 @@
|
||||
- [问题一完整解答](docs/问题一解答.md)
|
||||
- [问题三搜索与总耗时改进方案(含七点覆盖证明和自建消融)](docs/algo-3.md)
|
||||
- [问题3/4 强化学习选点(PPO + CUDA)](docs/algo-rl.md)
|
||||
- [强化学习审核、修复与独立验证(2026-09-11)](docs/强化学习适用性审核与改进-20260911.md)
|
||||
- [论文](docs/paper/main.tex)
|
||||
|
||||
## 项目结构
|
||||
|
||||
+26
-15
@@ -2,10 +2,17 @@
|
||||
|
||||
## 0. 摘要
|
||||
|
||||
**2026-09-11审核更新:**本文件下方的训练成绩与BC准确率是第一版历史记录。
|
||||
复核发现训练/部署特征错位、BC激活函数不一致以及评测只检查completed等问题。
|
||||
当前修复、Maskable PPO新模型和独立配对验证见
|
||||
[强化学习适用性审核与改进](强化学习适用性审核与改进-20260911.md)。
|
||||
新模型未替换原默认检查点;通过`--rl-model`显式加载。
|
||||
|
||||
本项目在保持 P0–P5 保守性与可解释证明不变的前提下,为问题3(全向源)与
|
||||
问题4(定向源)增加了一个可选的**强化学习测量点选择策略**:
|
||||
|
||||
- 把“下一检测点去哪”建模为离散决策马尔可夫决策过程(MDP);
|
||||
- 把“下一检测点去哪”建模为离散动作的局部决策问题;401维特征是压缩观测,
|
||||
不能认定为完整马尔可夫状态,尤其第四问仍有未知朝向和被省略的历史约束;
|
||||
- 状态由逐频道信念的几何特征 + 60 个候选检测点的几何特征组成(401 维);
|
||||
- 动作为 60 选 1(保证接收盘环候选、对称交会候选、定向遮挡环探候选);
|
||||
- 使用 Stable-Baselines3 的 PPO 训练,MLP 策略与价值网络在 **CUDA GPU**
|
||||
@@ -13,11 +20,11 @@
|
||||
- 训练环境基于自建评测机 `referee`(内存回环,与真实 HTTP 协议同边界);
|
||||
- 运行闭环仅替换既有选点排序器,覆盖证明、清除证书、定向后备清除全部保留。
|
||||
|
||||
结论(自建配对消融,5 个种子):Q3 用 12 万步随机初始化 PPO(与 `bayes`
|
||||
历史结果(第一版自建配对消融,5 个种子):Q3 用 12 万步随机初始化 PPO(与 `bayes`
|
||||
基本持平,+1.18%);Q4 用**专家行为克隆初始化 + PPO 微调**,混合源平均
|
||||
虚拟时间相对 `directional` 基线降低 **7.16%**(14250.1s → 13230.2s)。
|
||||
Q3 的 BC 初始化在消融中反而退化,因此保留随机初始化模型;Q4 的 BC 初始化
|
||||
是当前最优。官方演练验证见 §5:两次 Q4 演练(3 定向混合源、10 定向全定向
|
||||
是当时五场比较中均值最好的版本。官方演练验证见 §5:两次 Q4 演练(3 定向混合源、10 定向全定向
|
||||
场景)与一次 Q3 演练均全清,且通过离线审计。
|
||||
|
||||
## 1. 环境搭建(CUDA)
|
||||
@@ -33,9 +40,9 @@ uv run python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_
|
||||
`pyproject.toml` 中 `[tool.uv.sources] torch = { index = "pytorch-cu128" }`
|
||||
与 `[[tool.uv.index]]` 指向 PyTorch 官方 CUDA 12.8 轮子;下载约 2.6 GB,
|
||||
首次同步建议 `$env:UV_HTTP_TIMEOUT='300'`。默认 PyPI 的 Windows torch 轮子
|
||||
为 `+cpu`,不能直接训练。
|
||||
为 `+cpu`,可以使用CPU训练;本轮修订模型即采用CPU,CUDA并非训练前提。
|
||||
|
||||
## 2. MDP 定义
|
||||
## 2. 第一版局部决策环境
|
||||
|
||||
训练环境:`src/mcm26b/rl/env.py::LocalizationEnv`。
|
||||
|
||||
@@ -49,7 +56,9 @@ uv run python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_
|
||||
|
||||
约束说明:候选点全部来自既有几何生成器(`planner.candidate_points`、
|
||||
`planner.safe_disk_radius`、定向环探梯子),RL 不生成新的空间候选;它只做
|
||||
排序。因此 RL 决策不能破坏“保证接收盘内必收到方向”与清除证书的数学保证。
|
||||
排序。清除仍须通过几何证书;但“保证收到方向”只适用于全向源且实际点位于
|
||||
距离保证盘内。第一版的52—59号环探点可能在盘外,第四问的定向遮挡也不受
|
||||
距离盘保证。v2对第三问加入盘外动作屏蔽,第四问保留探索分支。
|
||||
|
||||
## 3. 训练与自建模拟器逻辑利用
|
||||
|
||||
@@ -58,8 +67,8 @@ uv run python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_
|
||||
uv run python scripts/pretrain_rl_expert.py --problem 3 --episodes 3000
|
||||
uv run python scripts/pretrain_rl_expert.py --problem 4 --episodes 3000
|
||||
|
||||
# 2) 从 BC 初始化继续 PPO 微调(CUDA)
|
||||
uv run python scripts/train_rl.py --problem 4 --timesteps 40000 \
|
||||
# 2) 从当前v2 BC初始化继续普通PPO(算法类型需显式匹配)
|
||||
uv run python scripts/train_rl.py --problem 4 --algorithm ppo --timesteps 40000 \
|
||||
--init output/rl/q4_ppo_bc_init.zip
|
||||
|
||||
# Q3 保留随机初始化长训练版本:
|
||||
@@ -73,8 +82,8 @@ uv run python scripts/train_rl.py --problem 3 --timesteps 120000
|
||||
|
||||
实测:Q3 专家数据 5141 条、BC 验证准确率 38.9%,微调后自建消融退化
|
||||
(保留 12 万步随机初始化模型);Q4 专家数据 14181 条、BC 验证准确率
|
||||
65.3%,微调后自建消融从 −3.84% 提升到 **−7.16%**。这说明自建模拟器逻辑
|
||||
在遮挡分支上可直接转化为 RL 的强先验,而全向分支的随机 PPO 已足够。
|
||||
65.3%,微调后自建消融从 −3.84% 提升到 **−7.16%**。但审核确认BC与PPO的
|
||||
激活函数不一致,这些数字不能证明成功迁移了专家策略,也不足以判断全向分支已最优。
|
||||
|
||||
输出:`output/rl/q3_ppo.zip`、`output/rl/q4_ppo.zip` 及同名 JSON 报告;
|
||||
训练脚本先打印 `train_start`(含 device/gpu 字段),再执行 PPO 并在独立
|
||||
@@ -99,7 +108,7 @@ Q3 12 万步约 625s(RTX 4060 Laptop,2 个 CPU 环境)。
|
||||
uv run python scripts/benchmark_rl.py --seeds 5
|
||||
```
|
||||
|
||||
同种子同场景配对比较,结果写入 `output/rl/benchmark.json`。当前版本:
|
||||
同种子同场景配对比较,结果写入 `output/rl/benchmark.json`。以下为第一版历史结果:
|
||||
|
||||
| 场景 | 基线 | 平均虚拟时间(基线 / RL) | 相对差 | 全清 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
@@ -107,7 +116,7 @@ uv run python scripts/benchmark_rl.py --seeds 5
|
||||
| Q4(10 源、3 定向 ×5 种子,BC+PPO) | `directional` | 14250.1s / 13230.2s | −7.16% | 5/5 |
|
||||
|
||||
训练局评估:Q3 单源 40 局成功率 100%、平均 3.55 步;Q4 定向单源 40 局
|
||||
成功率 90%、平均 4.45 步(其余由后备清除/二次定位兜底)。随机策略对照:
|
||||
成功率 90%、平均 4.45 步;其余单源训练局未完成,不能由此宣称已兜底成功。随机策略对照:
|
||||
Q3 平均回报 13.28 vs PPO 17.71;Q4 平均回报 8.72 vs BC+PPO 12.62。
|
||||
|
||||
### 5.1 官方模拟器演练验证(仅演练,未触碰正式测试)
|
||||
@@ -142,11 +151,13 @@ SHA-256 校验全部通过。归档目录:`output/rl/official-practice/`。
|
||||
|
||||
## 6. 结论与边界
|
||||
|
||||
以下是第一版结论;BC权重拷贝缺陷使“同构初始化”的解释不成立,性能判断
|
||||
应以新审核报告的同场景验证及不确定性为准,而不能由BC分类准确率推出。
|
||||
|
||||
- RL 已接入问题3/4 完整闭环,并在自建 referee 演练与官方模拟器演练中
|
||||
全清验证;这是方法验证,不是对正式成绩的承诺。
|
||||
- 利用自建模拟器逻辑做专家行为克隆,对 Q4 遮挡分支带来明确收益
|
||||
(−3.84% → −7.16%);Q3 全向分支空间有限,BC 初始化无收益,保留随机
|
||||
初始化的长训练模型。
|
||||
- 第一版Q4的BC初始化后均值变化为−3.84% → −7.16%,但初始化存在激活函数
|
||||
缺陷,样本数也不足以支持“明确收益”的归因;当前判断见新审核报告。
|
||||
- 训练环境使用自建场景假设(源均匀分布、逐点固定误差场、定向源朝向随机);
|
||||
官方隐藏分布未必相同。正式测试仍应默认使用经过演练验证的手写策略;
|
||||
`--strategy rl` / `--q4-strategy rl` 适合消融、方法对比与后续调参。
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,165 @@
|
||||
# 第三、四问强化学习适用性审核与改进
|
||||
|
||||
本轮审核第一版强化学习代码、专家数据、模型及官方演练归档,修复训练与部署不一致的问题,训练两个新的 Maskable PPO 模型,并用独立实现的本地 `referee` 完成同场景对照。所有新性能数字均为本地结果,没有启动新的官方演练。
|
||||
|
||||
结论:强化学习适合在现有几何算法内尝试“下一检测点”的排序,第四问比第三问更有收益空间。当前证据支持第四问的混合方案优于手写 `directional` 基线;尚不能证明新模型稳定优于旧 PPO。第三问没有稳定增益,应保留 `bayes` 默认策略。新模型作为实验版本显式加载,没有覆盖旧默认模型。
|
||||
|
||||
## 1. 题设和方法适用范围
|
||||
|
||||
附录2(3)的交会必须使用带误差的角域,不能把两条示向线的交点当作真值;同地重测不会提供新的独立误差样本。附录2(4)的单接收机逐频道检测,结合动作规则,须计入移动、5秒检测及实际1秒换频。第三问的全向假设允许用无信号排除接收圆盘;第四问的无信号还可能来自方向遮挡,不能沿用这一排除结论。题设及几何算法的完整审核见[算法审核与优化](算法审核与优化-20260911.md)。
|
||||
|
||||
当前 RL 实际只替换 `RLBayesLoop._select_point` 和 `RLDirectionalLoop._next_probe`。七站/49站覆盖、频道发现、全局任务调度、保守候选集、清除证书和第四问后备清除仍由原算法负责。因此,完整场景全清验证的是混合系统,不是神经网络独立获得了完备性证明。
|
||||
|
||||
401维输入概括几何、历史读数和60个候选点,但省略完整历史、候选多边形细节及位置—朝向联合约束,不能直接称为完整马尔可夫状态。局部单源训练也没有学习全场覆盖路线与多源调度。PPO可以作为这种压缩观测下的近似策略尝试,但其目标并不自动等于全场最短时间;评估必须使用独立完整场景。[SB3官方训练与评估建议](https://stable-baselines3.readthedocs.io/en/master/guide/rl_tips.html)也强调独立评估、多随机种子和状态信息完整性。
|
||||
|
||||
## 2. 确认的实现缺陷及修复
|
||||
|
||||
| 缺陷 | 证据和影响 | 已实施修复 |
|
||||
| --- | --- | --- |
|
||||
| 训练传入整个 `BeliefState`,部署传入 `ChannelBelief` | 同一状态的特征索引7、14、15、17、18不同;训练丢失测量次数、末次结果和方位信息 | `env._observe` 使用目标频道信念;测试逐位比较部署输入,并覆盖跨频道交接 |
|
||||
| BC使用ReLU,接收权重的PPO使用Tanh | 仅复制线性权重无法保留函数。原始专家输入探针中,Q3的90.96%、Q4的67.42%出现不同argmax | BC改为Tanh;复制时检查完整前向输出,重新初始化critic;旧ReLU/旧特征检查点拒绝直接迁移 |
|
||||
| BC按观测行随机拆分 | 同一episode的相关状态可能同时出现在训练和验证中,分类准确率不足以说明独立场景泛化 | 保存episode编号,按episode划分训练/验证 |
|
||||
| 第四问训练遮挡状态与运行时不一致 | 训练只记录距离保证盘内的无信号,且获得新方向后未正确重置连续遮挡;后备清除触发轨迹偏离部署 | 所有连续无信号记录为探测序列,仅盘内无信号用于确认定向;正观测清空序列,统一4次遮挡/8次定位阈值 |
|
||||
| 奖励鼓励首次遮挡等中间事件 | “获得遮挡证据”额外+2、半径收缩等奖励会改变实际时间目标;较高回报不一定更快 | 主要奖励改为实际虚拟耗时的负值,成功清除统一+20,失败/截断重罚;取消这些中间奖励,采用gamma=1 |
|
||||
| 未屏蔽同地重测、重合候选和第三问盘外梯子点 | 浪费固定误差条件下的检测时间;旧文档“所有候选保证接收”不成立 | 训练/推理共用动作屏蔽,排除已测0.5m格位置、重复候选、越界点;第三问存在距离保证盘时排除盘外点 |
|
||||
| 保证盘半径恰为0时动作数量可能改变 | 依赖去重后的候选生成器会破坏固定60动作接口 | 即使位置重合也保留编号,交由动作屏蔽处理 |
|
||||
| 只校验401/60维度不能识别语义变化 | 接收机频道被目标频道替代;旧模型与新模型同形但输入含义不同 | 加入特征版本与算法元数据,v2使用实际接收机频道,旧版保留原输入语义;拒绝问题编号或屏蔽模型元数据不匹配 |
|
||||
| 基准将 `completed` 当作全清 | 内部状态自洽不代表全部真实源已清除 | 用评测端真实频道集合对照成功清除集合,并检查真实频道没有被误判缺席、正常退出、异常及逐步计时;旧基准新增 `all_valid` 和失败退出码 |
|
||||
| 官方审计没有严格对应问题与策略名称 | 第三问/第四问摘要可能交叉通过名称检查 | `verify_rl_practice.py` 分别要求 `rl` / `rl_q4` |
|
||||
|
||||
BC探针是对旧网络和“拷贝后换激活”的网络进行比较,并不是新模型错误率,也不是重新测得的BC验证准确率。旧报告38.9%/65.3%的分类准确率不能直接解释为已迁移PPO的模仿准确率。本轮训练的新模型均从随机初始化开始,未使用BC;BC修复通过前向一致性测试,未据此声称重新训练BC已经带来收益。
|
||||
|
||||
动作屏蔽采用官方 `MaskablePPO` 的 `action_masks` 接口,训练和确定性推理都传入相同规则。[SB3-Contrib官方文档](https://sb3-contrib.readthedocs.io/en/master/modules/ppo_mask.html)说明了这两处的用法。第四问仍允许探索盘外候选,距离保证不等于方向可见。退化到所有动作均被屏蔽时保留中心动作,由步数/预算边界处理,因此不能把本次零重复观测扩展为所有可能状态的严格保证。
|
||||
|
||||
正常成功episode的累计奖励为 `20 - 局部实际虚拟时间/100`;环境仍保留异常与失败处理,包括证书清除失败的额外惩罚。局部时间从首测与可选交接完成后开始,不能用训练报告中的局部均值替代整场成绩。第三问训练自动清除仍使用证书圆心,而完整 `BayesLoop` 可在证书范围内平移清除点;全局调度、预算与局部环境也有差异,完整闭环验证因此不可省略。
|
||||
|
||||
## 3. 训练和数据
|
||||
|
||||
第一版Q3为120000步随机初始化PPO;第一版Q4为BC初始化后40000步PPO。原专家数据分别为5141、14181条,原5场完整测试均值分别是Q3:3088.33/3124.76秒、Q4:14250.15/13230.20秒(手写/RL)。这些历史数字受旧环境和特征影响,不与本轮新课程的单源成功率直接比较。
|
||||
|
||||
修订课程保留首次有效测向,并加入35%的跨频道、跨位置交接,20%的区域边缘且最小接收半径场景。定向首测覆盖朝向±89.9°,更接近半平面边界。源位置、朝向、半径和逐点误差的抽样仍是自建分布,不是已知的官方生成分布;真值仅用于布设和评测,不进入策略观测或动作屏蔽。
|
||||
|
||||
两模型使用 `[128,128]` Tanh MLP、2个内存环境、`n_steps=512`、`batch_size=128`、6轮更新、学习率0.0003。实际均完成60416步,来自完整rollout批次向上取整;元数据中的60000是请求步数。安装版本为SB3 2.9.0、SB3-Contrib 2.9.0、Torch 2.11.0+cu128。本次选择CPU、单Torch线程,机器CUDA可用。
|
||||
|
||||
| 项目 | Q3新模型 | Q4新模型 |
|
||||
| --- | ---: | ---: |
|
||||
| 训练种子 | 310001 | 410001 |
|
||||
| 定向概率 | 0 | 0.75 |
|
||||
| 训练墙钟时间 | 276.6s | 250.6s |
|
||||
| 独立单源评估成功 | 59/60 | 60/60 |
|
||||
| 单源平均局部时间 | 270.04s | 349.51s |
|
||||
| 平均动作步数 | 2.42 | 3.13 |
|
||||
|
||||
Q3有一次局部训练任务未完成,不能声称单源成功率100%。完整闭环仍有自身调度与后备路径。两问题各只有一个新模型训练种子,未覆盖训练随机性。
|
||||
|
||||
## 4. 完整场景同条件对照
|
||||
|
||||
比较三种固定策略:当前手写基线(Q3 `bayes`、Q4 `directional`)、旧默认PPO、新Maskable PPO。相同场景使用相同源位置、频道、半径、朝向及逐点固定误差种子。`referee` 为单独实现,策略不读取模拟器真值。每场保存场景、完整请求/响应、summary、模型哈希和计时核验。
|
||||
|
||||
开发集为种子700—702、10/16源,各问题6场。随后固定新模型,不再据验证成绩调参;验证集为种子91000—91005、10/13/16源,各问题18场。第四问定向数覆盖1、约半数、仅一个全向三种组成。另加两种圆周边界压力场景。开发、验证、压力共52个不同场景,三策略共156次完整运行,全部满足真实源全清、缺席无误判、正常exit、零异常、逐步耗时复算一致。
|
||||
|
||||
### 开发集平均整场时间
|
||||
|
||||
| 问题 | 手写基线 | 旧PPO | 新模型 |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Q3,6场 | 3294.62s | 3206.20s | 3264.62s |
|
||||
| Q4,6场 | 14289.76s | 12443.43s | 11758.72s |
|
||||
|
||||
### 独立验证集
|
||||
|
||||
| 问题/指标 | 手写基线 | 旧PPO | 新模型 |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Q3有效全清 | 18/18 | 18/18 | 18/18 |
|
||||
| Q3平均时间 | 3330.72s | 3254.61s | 3307.65s |
|
||||
| Q3中位时间 | 3309.16s | 3262.64s | 3222.50s |
|
||||
| Q3最大时间 | 3690.11s | 3786.98s | 3776.55s |
|
||||
| Q4有效全清 | 18/18 | 18/18 | 18/18 |
|
||||
| Q4平均时间 | 13778.82s | 12070.53s | 11797.57s |
|
||||
| Q4中位时间 | 14587.80s | 12891.83s | 12488.23s |
|
||||
| Q4最大时间 | 17273.98s | 15365.76s | 15894.44s |
|
||||
| Q4累计未成功clear尝试 | 616 | 719 | 1380 |
|
||||
| Q4累计同频道同点重测 | 12 | 28 | 0 |
|
||||
|
||||
Q3三组都没有失败clear或同点重测。Q4新模型减少了重测,但更多使用后备光学清除尝试,平均耗时降低的同时尾部表现有所退化。失败clear本身可来自允许的后备搜索,不能仅凭该计数认定证书错误;其移动和3秒动作耗时全部纳入结果。
|
||||
|
||||
对独立验证场景做10000次配对bootstrap,负值表示新模型更快:
|
||||
|
||||
| 对比 | 均值相对变化 | 95%场景重采样区间 | 新模型更快场数 |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Q3新模型对手写 | -0.69% | [-4.50%, +2.95%] | 9/18 |
|
||||
| Q3新模型对旧PPO | +1.63% | [-1.64%, +5.19%] | 8/18 |
|
||||
| Q4新模型对手写 | -14.38% | [-17.93%, -11.48%] | 18/18 |
|
||||
| Q4新模型对旧PPO | -2.26% | [-6.08%, +1.71%] | 13/18 |
|
||||
|
||||
新旧PPO的区间均跨0,不能据小幅平均变化宣称稳定改进。区间只反映固定模型在这些场景上的差异,不覆盖训练种子、未知官方分布或所有合法边界。此外本轮同时修改多项训练因素,这不是能归因到单个修复的消融实验。
|
||||
|
||||
### 边界压力与4000秒目标
|
||||
|
||||
源全部放在半径1800m圆周、接收半径1000m;第四问隔一个源采用向外发射方向。10/16源各一场,各策略都完成全清。
|
||||
|
||||
| 问题 | 手写平均 | 旧PPO平均 | 新模型平均 |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Q3 | 4472.58s | 4429.04s | 4648.26s |
|
||||
| Q4 | 18248.87s | 14404.01s | 15210.08s |
|
||||
|
||||
新Q3的两场分别为4659.06s、4637.46s,仍超过4000s;新Q4为14573.62s、15846.54s,且比旧PPO退化。此前几何优化在另一独立Q3验证组达到平均3315.89s、最大3981.74s,本轮新Q3验证组最大3776.55s;二者只能说明各自验证组达标,不能承诺全部题设场景小于4000s。第四问尚未达到4000s。
|
||||
|
||||
## 5. 官方证据与测试
|
||||
|
||||
重新审计已有官方归档,三次均通过题号/策略、官方包SHA、源数、完整清除、正常退出、请求时窗及逐步计时检查,未改写原归档:
|
||||
|
||||
| 问题 | 既有官方案例 | 全清 | 整场时间 |
|
||||
| --- | --- | ---: | ---: |
|
||||
| Q3 | F38Y-9Q9J-B82V-CB9Z | 13/13 | 3538.81s |
|
||||
| Q4,3个定向源 | X43X-VMRF-8GRZ-3SYA | 10/10 | 14003.09s |
|
||||
| Q4,10个定向源 | BXSA-TFQ3-2DJ6-DHH7 | 10/10 | 14562.82s |
|
||||
|
||||
这些是旧模型的官方可运行证据,不是新模型成绩;不同案例也不能直接计算优化百分比。新模型目前通过本地验证,未在官方新演练中运行。
|
||||
|
||||
全量回归:`python -m pytest -q --tb=short`,243项通过。初次沙箱运行有24个临时目录权限导致的fixture错误,获得权限后重跑全部通过。新增5项测试覆盖训练/部署观测、动作屏蔽、方向读数重置遮挡、实际成本奖励和BC完整前向一致性。之后对基准/官方审计入口的小修补分别用4次完整场景冒烟运行及三份官方归档复核通过。
|
||||
|
||||
## 6. 产物与复现
|
||||
|
||||
主目录为 `output/rl/review-20260911/`:
|
||||
|
||||
- `q3_masked.zip` / `.json`、`q4_masked.zip` / `.json`:新模型与训练元数据,必须成对保存。
|
||||
- `evaluation/development/{controls,revised}/results.json`:开发对照。
|
||||
- `evaluation/validation/final/results.json`:独立验证与每场完整日志。
|
||||
- `evaluation/stress/final/results.json`:边界压力结果与日志。
|
||||
- `review_evidence.json`:配对区间、BC/特征错位探针、旧官方重审结果和代码哈希。
|
||||
- `benchmark-smoke.json`:旧基准有效性口径修复后的冒烟结果。
|
||||
|
||||
新Q3模型SHA-256:`b8b3a2bb103588348d8d7791bde9a2b0635c04778f0ac64f91c6f5c7cf87ca6e`。
|
||||
新Q4模型SHA-256:`6865d541ff9bdee81886d327345f2660d5f7d72f4b39810a58ae4b867ab37e42`。
|
||||
|
||||
从项目根目录复现训练时,使用新目录保留本轮检查点:
|
||||
|
||||
```powershell
|
||||
.venv/Scripts/python.exe scripts/train_rl.py --problem 3 --algorithm maskable_ppo --timesteps 60000 --seed 310001 --device cpu --model output/rl/reproduction/q3_masked.zip --eval-episodes 60 --verbose 0
|
||||
.venv/Scripts/python.exe scripts/train_rl.py --problem 4 --algorithm maskable_ppo --timesteps 60000 --seed 410001 --directional-probability 0.75 --device cpu --model output/rl/reproduction/q4_masked.zip --eval-episodes 60 --verbose 0
|
||||
```
|
||||
|
||||
复核本轮冻结模型的完整对照(label须使用尚不存在的名称):
|
||||
|
||||
```powershell
|
||||
.venv/Scripts/python.exe scripts/evaluate_rl_revision.py --split validation --label rerun-validation
|
||||
.venv/Scripts/python.exe scripts/evaluate_rl_revision.py --split stress --label rerun-stress
|
||||
.venv/Scripts/python.exe scripts/summarize_rl_revision.py
|
||||
```
|
||||
|
||||
本地内存自检可显式加载对应检查点;`--self-test`不会连接官方模拟器:
|
||||
|
||||
```powershell
|
||||
.venv/Scripts/mcm26b.exe run --robot-id 2026001 --self-test --strategy rl --rl-model output/rl/review-20260911/q3_masked.zip
|
||||
.venv/Scripts/mcm26b.exe run --robot-id 2026001 --self-test --problem 4 --q4-strategy rl --rl-model output/rl/review-20260911/q4_masked.zip
|
||||
```
|
||||
|
||||
在用户另行启动相应问题的官方演练后,使用实际登录队号和`--base-url`连接。例如第三问(路径使用本轮尚未创建的官方演练目录):
|
||||
|
||||
```powershell
|
||||
.venv/Scripts/mcm26b.exe run --robot-id 2026001 --base-url http://127.0.0.1:2026 --problem 3 --strategy rl --rl-model output/rl/review-20260911/q3_masked.zip --log output/rl/review-20260911/practice-q3/session.jsonl
|
||||
```
|
||||
|
||||
该命令保存请求/响应日志,汇总输出到终端;官方结果文件与汇总还需按原归档流程保存。第四问改用`--problem 4 --q4-strategy rl`及对应模型。本轮未执行上述官方连接命令。
|
||||
|
||||
进一步研究应先缩小单源训练与完整调度的差异,并处理Q4后备清除和边界退化,再做多个训练种子及新的冻结验证集。当前结果不足以将新模型自动替换为默认策略,也不足以把训练步数增加当作达到4000秒的依据。
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"config": {
|
||||
"seeds": 1,
|
||||
"sources": 10,
|
||||
"directional": 3
|
||||
},
|
||||
"q3": [
|
||||
{
|
||||
"seed": 20260910,
|
||||
"bayes": {
|
||||
"completed": true,
|
||||
"all_cleared": true,
|
||||
"correct_absence": true,
|
||||
"anomalies": [],
|
||||
"timing_mismatches": 0,
|
||||
"normal_exit": true,
|
||||
"virtual_time_s": 3089.77,
|
||||
"measures": 114,
|
||||
"total_requests": 124,
|
||||
"move_distance_m": 11833.9,
|
||||
"wall_s": 3.535
|
||||
},
|
||||
"rl": {
|
||||
"completed": true,
|
||||
"all_cleared": true,
|
||||
"correct_absence": true,
|
||||
"anomalies": [],
|
||||
"timing_mismatches": 0,
|
||||
"normal_exit": true,
|
||||
"virtual_time_s": 2977.745,
|
||||
"measures": 118,
|
||||
"total_requests": 128,
|
||||
"move_distance_m": 11153.7,
|
||||
"wall_s": 5.478
|
||||
}
|
||||
}
|
||||
],
|
||||
"q4": [
|
||||
{
|
||||
"seed": 20260910,
|
||||
"directional": {
|
||||
"completed": true,
|
||||
"all_cleared": true,
|
||||
"correct_absence": true,
|
||||
"anomalies": [],
|
||||
"timing_mismatches": 0,
|
||||
"normal_exit": true,
|
||||
"virtual_time_s": 13178.205,
|
||||
"measures": 550,
|
||||
"total_requests": 560,
|
||||
"move_distance_m": 49401.0,
|
||||
"wall_s": 1.589
|
||||
},
|
||||
"rl": {
|
||||
"completed": true,
|
||||
"all_cleared": true,
|
||||
"correct_absence": true,
|
||||
"anomalies": [],
|
||||
"timing_mismatches": 0,
|
||||
"normal_exit": true,
|
||||
"virtual_time_s": 12692.704,
|
||||
"measures": 572,
|
||||
"total_requests": 587,
|
||||
"move_distance_m": 46348.5,
|
||||
"wall_s": 0.206
|
||||
}
|
||||
}
|
||||
],
|
||||
"q3_summary": {
|
||||
"mean_virtual_s": {
|
||||
"bayes": 3089.77,
|
||||
"rl": 2977.745
|
||||
},
|
||||
"mean_delta_s": -112.02500000000009,
|
||||
"relative_delta_pct": -3.6256744029490897,
|
||||
"mean_measures": {
|
||||
"bayes": 114,
|
||||
"rl": 118
|
||||
},
|
||||
"all_cleared": true,
|
||||
"all_valid": true
|
||||
},
|
||||
"q4_summary": {
|
||||
"mean_virtual_s": {
|
||||
"directional": 13178.205,
|
||||
"rl": 12692.704
|
||||
},
|
||||
"mean_delta_s": -485.5010000000002,
|
||||
"relative_delta_pct": -3.6841208647156436,
|
||||
"mean_measures": {
|
||||
"directional": 550,
|
||||
"rl": 572
|
||||
},
|
||||
"all_cleared": true,
|
||||
"all_valid": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"comparisons": {
|
||||
"q3-revised-vs-baseline": {
|
||||
"relative_change_pct": -0.6927149477129024,
|
||||
"paired_bootstrap_95_pct": [
|
||||
-4.498553709698716,
|
||||
2.946109407935289
|
||||
],
|
||||
"faster_cases": 9,
|
||||
"runs": 18
|
||||
},
|
||||
"q3-revised-vs-legacy": {
|
||||
"relative_change_pct": 1.6296939233583885,
|
||||
"paired_bootstrap_95_pct": [
|
||||
-1.639291360386112,
|
||||
5.191944866303212
|
||||
],
|
||||
"faster_cases": 8,
|
||||
"runs": 18
|
||||
},
|
||||
"q4-revised-vs-baseline": {
|
||||
"relative_change_pct": -14.378947944816856,
|
||||
"paired_bootstrap_95_pct": [
|
||||
-17.92759937325484,
|
||||
-11.480044833864715
|
||||
],
|
||||
"faster_cases": 18,
|
||||
"runs": 18
|
||||
},
|
||||
"q4-revised-vs-legacy": {
|
||||
"relative_change_pct": -2.261375942162047,
|
||||
"paired_bootstrap_95_pct": [
|
||||
-6.079501282327849,
|
||||
1.7095590628835715
|
||||
],
|
||||
"faster_cases": 13,
|
||||
"runs": 18
|
||||
}
|
||||
},
|
||||
"official": [
|
||||
{
|
||||
"passed": true,
|
||||
"source": "official_practice",
|
||||
"strategy": "rl",
|
||||
"problem": 3,
|
||||
"case_code": "F38Y-9Q9J-B82V-CB9Z",
|
||||
"official_jammer_count": 13,
|
||||
"directional_jammer_count": 0,
|
||||
"cleared_channels": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
14,
|
||||
15,
|
||||
19,
|
||||
20
|
||||
],
|
||||
"accepted_requests_including_enter_exit": 126,
|
||||
"measures": 111,
|
||||
"clear_attempts": 13,
|
||||
"clear_successes": 13,
|
||||
"virtual_time_s": 3538.811466,
|
||||
"real_elapsed_server_s": 2.321,
|
||||
"checks": {
|
||||
"official_problem_matches_summary": true,
|
||||
"official_package_sha256_matches": true,
|
||||
"normal_enter_and_exit": true,
|
||||
"responses_within_official_run_window": true,
|
||||
"unique_accepted_request_ids": true,
|
||||
"all_official_sources_cleared": true,
|
||||
"summary_completed_and_consistent": true,
|
||||
"all_timestamps_match_microsecond_recomputation": true,
|
||||
"within_real_and_virtual_limits": true
|
||||
},
|
||||
"limits": "仅验证本官方演练案例;不触碰正式测试,不解读官方加密日志内容。"
|
||||
},
|
||||
{
|
||||
"passed": true,
|
||||
"source": "official_practice",
|
||||
"strategy": "rl_q4",
|
||||
"problem": 4,
|
||||
"case_code": "X43X-VMRF-8GRZ-3SYA",
|
||||
"official_jammer_count": 10,
|
||||
"directional_jammer_count": 3,
|
||||
"cleared_channels": [
|
||||
2,
|
||||
7,
|
||||
10,
|
||||
12,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20
|
||||
],
|
||||
"accepted_requests_including_enter_exit": 621,
|
||||
"measures": 585,
|
||||
"clear_attempts": 34,
|
||||
"clear_successes": 10,
|
||||
"virtual_time_s": 14003.086251,
|
||||
"real_elapsed_server_s": 5.723,
|
||||
"checks": {
|
||||
"official_problem_matches_summary": true,
|
||||
"official_package_sha256_matches": true,
|
||||
"normal_enter_and_exit": true,
|
||||
"responses_within_official_run_window": true,
|
||||
"unique_accepted_request_ids": true,
|
||||
"all_official_sources_cleared": true,
|
||||
"summary_completed_and_consistent": true,
|
||||
"all_timestamps_match_microsecond_recomputation": true,
|
||||
"within_real_and_virtual_limits": true
|
||||
},
|
||||
"limits": "仅验证本官方演练案例;不触碰正式测试,不解读官方加密日志内容。"
|
||||
},
|
||||
{
|
||||
"passed": true,
|
||||
"source": "official_practice",
|
||||
"strategy": "rl_q4",
|
||||
"problem": 4,
|
||||
"case_code": "BXSA-TFQ3-2DJ6-DHH7",
|
||||
"official_jammer_count": 10,
|
||||
"directional_jammer_count": 10,
|
||||
"cleared_channels": [
|
||||
2,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
8,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
17
|
||||
],
|
||||
"accepted_requests_including_enter_exit": 693,
|
||||
"measures": 670,
|
||||
"clear_attempts": 21,
|
||||
"clear_successes": 10,
|
||||
"virtual_time_s": 14562.81673,
|
||||
"real_elapsed_server_s": 6.39,
|
||||
"checks": {
|
||||
"official_problem_matches_summary": true,
|
||||
"official_package_sha256_matches": true,
|
||||
"normal_enter_and_exit": true,
|
||||
"responses_within_official_run_window": true,
|
||||
"unique_accepted_request_ids": true,
|
||||
"all_official_sources_cleared": true,
|
||||
"summary_completed_and_consistent": true,
|
||||
"all_timestamps_match_microsecond_recomputation": true,
|
||||
"within_real_and_virtual_limits": true
|
||||
},
|
||||
"limits": "仅验证本官方演练案例;不触碰正式测试,不解读官方加密日志内容。"
|
||||
}
|
||||
],
|
||||
"feature_probe": {
|
||||
"mismatched_indices": [
|
||||
7,
|
||||
14,
|
||||
15,
|
||||
17,
|
||||
18
|
||||
],
|
||||
"fixed_history_count": 0.0833333358168602,
|
||||
"old_history_count": 0.0
|
||||
},
|
||||
"bc_probe": {
|
||||
"q3": {
|
||||
"samples": 1029,
|
||||
"different_argmax_fraction": 0.909621000289917,
|
||||
"max_logit_difference": 10.808266639709473
|
||||
},
|
||||
"q4": {
|
||||
"samples": 1013,
|
||||
"different_argmax_fraction": 0.674234926700592,
|
||||
"max_logit_difference": 32.212066650390625
|
||||
}
|
||||
},
|
||||
"limitations": "置信区间仅重采样固定模型的场景,不覆盖训练随机种子不确定性。",
|
||||
"code_sha256": {
|
||||
"src\\mcm26b\\rl\\__init__.py": "670d3134aec149e9a2e03cde00cb56c3ab16c590584ff0afd1f522212ebd2242",
|
||||
"src\\mcm26b\\rl\\_constants.py": "88998fee55f227c755e8770a6e8aca0abd2508ab417696b0c1f7e45c189f74eb",
|
||||
"src\\mcm26b\\rl\\env.py": "d01a11a5ebb5eff921b095442caee05b007a8d61a1aea154831cd1e24f46bf7b",
|
||||
"src\\mcm26b\\rl\\features.py": "406995743e0f7fbe09581f7aca55cf28882149c4b7f2eae2aba89ef32c69fdcf",
|
||||
"src\\mcm26b\\rl\\policy.py": "5bee6329f14c5b5e88173e63624e68292949db5b7b92d3dcabf3948f8aa53755"
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,9 @@ def main(argv=None) -> int:
|
||||
for row in rows]
|
||||
cleared = all(row["rl"]["all_cleared"] and row[baseline]["all_cleared"]
|
||||
for row in rows)
|
||||
valid = all(part['completed'] and part['all_cleared'] and part['correct_absence']
|
||||
and part['normal_exit'] and not part['anomalies'] and not part['timing_mismatches']
|
||||
for row in rows for part in (row[baseline],row['rl']))
|
||||
report[key + "_summary"] = {
|
||||
"mean_virtual_s": {
|
||||
baseline: statistics.mean(row[baseline]["virtual_time_s"] for row in rows),
|
||||
@@ -107,6 +110,7 @@ def main(argv=None) -> int:
|
||||
"rl": statistics.mean(row["rl"]["measures"] for row in rows),
|
||||
},
|
||||
"all_cleared": cleared,
|
||||
"all_valid": valid,
|
||||
}
|
||||
output = Path(args.output)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -116,7 +120,7 @@ def main(argv=None) -> int:
|
||||
"q3": report["q3_summary"],
|
||||
"q4": report["q4_summary"]},
|
||||
ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
return 0 if report['q3_summary']['all_valid'] and report['q4_summary']['all_valid'] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""汇总配对置信区间、历史官方证据与BC/特征错位探针,不连接模拟器。"""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import hashlib
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
import verify_rl_practice
|
||||
from mcm26b.rl.env import LocalizationEnv
|
||||
from mcm26b.rl.features import build_observation
|
||||
|
||||
|
||||
def main():
|
||||
root = Path('output/rl/review-20260911')
|
||||
records = json.loads((root/'evaluation/validation/final/results.json').read_text(encoding='utf-8'))
|
||||
rng = np.random.default_rng(20260911)
|
||||
comparisons = {}
|
||||
for problem in (3,4):
|
||||
part = [r for r in records['rows'] if r['problem']==problem]
|
||||
for label in ('baseline','legacy'):
|
||||
baseline = {r['case']:r for r in part if r['variant']==label}
|
||||
selected = sorted((r for r in part if r['variant']=='revised'),key=lambda r:r['case'])
|
||||
a = np.array([baseline[r['case']]['virtual_s'] for r in selected])
|
||||
b = np.array([r['virtual_s'] for r in selected])
|
||||
samples = rng.integers(0,len(a),(10000,len(a)))
|
||||
changes = (b[samples].mean(axis=1)/a[samples].mean(axis=1)-1)*100
|
||||
comparisons[f'q{problem}-revised-vs-{label}'] = dict(
|
||||
relative_change_pct=(b.mean()/a.mean()-1)*100,
|
||||
paired_bootstrap_95_pct=np.quantile(changes,[.025,.975]).tolist(),
|
||||
faster_cases=int(sum(b<a)),runs=len(a))
|
||||
# 重新计算原始官方日志的检查,派生报告单独保存,原始归档不写入。
|
||||
verify_rl_practice.save_json = lambda path,value: None
|
||||
official = [verify_rl_practice.audit(p.parent) for p in sorted(
|
||||
Path('output/rl/official-practice').rglob('*.result.json'))]
|
||||
env = LocalizationEnv(handoff_probability=0,boundary_probability=0)
|
||||
fixed,_ = env.reset(seed=1)
|
||||
# 复现旧版把整个BeliefState作为单频道对象传入的错误。
|
||||
wrong = build_observation(env._belief.candidate_set(env._channel),env._client.state.position,
|
||||
env._channel,env.config,env.problem,env._blocked,env._belief,
|
||||
virtual_time_s=env._client.state.virtual_time_s)
|
||||
features = dict(mismatched_indices=np.flatnonzero(fixed!=wrong).tolist(),
|
||||
fixed_history_count=float(fixed[7]),old_history_count=float(wrong[7]))
|
||||
env.close()
|
||||
torch.set_num_threads(1)
|
||||
torch.manual_seed(0)
|
||||
bc = {}
|
||||
for problem in (3,4):
|
||||
# 使用旧检查点和原始专家输入,比对原ReLU网络与拷贝后的Tanh网络。
|
||||
checkpoint = torch.load(f'output/rl/bc/q{problem}_bc.pt',map_location='cpu',weights_only=True)
|
||||
data = np.load(f'output/rl/bc/q{problem}_expert.npz',allow_pickle=False)
|
||||
x = torch.from_numpy(data['obs'][::max(1,len(data['obs'])//1000)].copy())
|
||||
def network(activation):
|
||||
net = torch.nn.Sequential(torch.nn.Linear(401,128),activation(),
|
||||
torch.nn.Linear(128,128),activation(),torch.nn.Linear(128,60))
|
||||
net.load_state_dict({k.removeprefix('net.'):v for k,v in checkpoint['state_dict'].items()})
|
||||
return net
|
||||
with torch.no_grad():
|
||||
before,after=network(torch.nn.ReLU)(x),network(torch.nn.Tanh)(x)
|
||||
bc[f'q{problem}'] = dict(samples=len(x),
|
||||
different_argmax_fraction=float((before.argmax(1)!=after.argmax(1)).float().mean()),
|
||||
max_logit_difference=float((before-after).abs().max()))
|
||||
report = dict(comparisons=comparisons,official=official,feature_probe=features,bc_probe=bc,
|
||||
limitations='置信区间仅重采样固定模型的场景,不覆盖训练随机种子不确定性。',
|
||||
code_sha256={str(p):hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
for p in sorted(Path('src/mcm26b/rl').glob('*.py'))})
|
||||
(root/'review_evidence.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8')
|
||||
print(json.dumps(dict(comparisons=comparisons,feature_probe=features,bc_probe=bc,
|
||||
official=[dict(case=r['case_code'],passed=r['passed']) for r in official]),indent=2))
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
@@ -35,7 +35,7 @@ def audit(folder: Path) -> dict:
|
||||
|
||||
checks["official_problem_matches_summary"] = (
|
||||
official["problem_no"] in (3, 4)
|
||||
and summary["strategy"] in ("rl", "rl_q4")
|
||||
and summary["strategy"] == ("rl" if official["problem_no"] == 3 else "rl_q4")
|
||||
and 10 <= official["jammer_count"] <= 16
|
||||
and official["omnidirectional_jammer_count"] + official["directional_jammer_count"]
|
||||
== official["jammer_count"])
|
||||
|
||||
@@ -104,7 +104,7 @@ class LocalizationEnv(gym.Env):
|
||||
heading = rng.uniform(0.0, 360.0) if directional else 0.0
|
||||
|
||||
# 首次检测点:距离 [300, min(1450, r_eff-20)] 内随机,定向源取朝向
|
||||
# ±80°(远离 90° 边界,保证 direction)。机器狗可到 Ω 外 [题面]。
|
||||
# ±89.9°(接近但不跨越90°边界,保证direction)。机器狗可到Ω外 [题面]。
|
||||
max_distance = max(310.0, min(1450.0, r_eff - 20.0))
|
||||
if max_distance < 310.0:
|
||||
max_distance = 310.0
|
||||
|
||||
Reference in New Issue
Block a user