From 316758d80e9e650563d66c937fc68deca7ce75a3 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Thu, 13 Aug 2026 06:43:51 +0000 Subject: [PATCH] =?UTF-8?q?fix(actpred):=20=E4=BF=AE=E5=A4=8D=E5=8A=A8?= =?UTF-8?q?=E4=BD=9C=E5=BD=92=E4=B8=80=E5=8C=96=E7=BB=9F=E8=AE=A1=E9=87=8F?= =?UTF-8?q?=E6=9C=AA=E8=A6=86=E7=9B=96=20history=20=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=9A=84=20z-score=20=E7=88=86=E7=82=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两个问题(本轮归一化编译链路上实测暴露): 1. 统计量只按 targets 计算:targets 中恒定的列 std 落到 1e-8 地板值,而 history 窗口可能含该列的其他取值(.mt 脚本前几行), z-score 爆炸(实测 (90-45)/1e-8=4.5e9)→ act_tokens ±1e9 → DFC optimize 报 NegativeSlopeExponentNonFixable(desired shift 23 > 15 data bits)。pretrain_mt.py 与 dataset.py 的统计量改为 history∪target 联合计算,修复后校准数据 max|z| = 4.77。 2. action_stats_path 存成含目录的相对路径("./checkpoints/ action_stats.npy"),推理端(predict/export_deploy/gen_calibration) 按 checkpoint 目录解析时二次拼接成 checkpoints/./checkpoints, 导出直接 FileNotFoundError。train.py/pretrain_mt.py 改存裸文件名 "action_stats.npy"(stats 与 checkpoint 同目录,语义一致)。 新增回归测试 test_stats_cover_history_values(history 独有取值不 得被 1e-8 地板值打爆),全量 77 通过。 归一化重编译全链路通过:token 量级 |img|=0.53 |act|=2.93 健康, optimize 通过,连通性检查 5/5,HEF 2.02MB 已部署 Pi 待 _npu_test 复验。 --- actpred/dataset.py | 4 +++- actpred/pretrain_mt.py | 14 ++++++++++++-- actpred/test_actpred.py | 24 ++++++++++++++++++++++++ actpred/train.py | 3 ++- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/actpred/dataset.py b/actpred/dataset.py index db3f25b..1911729 100644 --- a/actpred/dataset.py +++ b/actpred/dataset.py @@ -76,7 +76,9 @@ class ActionDataset(Dataset): raise ValueError( f"数据集为空(0 样本),无法计算归一化统计量。" f"请检查 data_dir={data_dir} 中的 episode 数据。") - all_actions = np.stack([s[2] for s in self.samples]) + all_actions = np.stack( + [np.concatenate([s[1], s[2][None]]) for s in self.samples] + ).reshape(-1, self.action_dim) self._action_mean = all_actions.mean(axis=0) self._action_std = all_actions.std(axis=0) + 1e-8 diff --git a/actpred/pretrain_mt.py b/actpred/pretrain_mt.py index 08446a0..eebce47 100644 --- a/actpred/pretrain_mt.py +++ b/actpred/pretrain_mt.py @@ -217,13 +217,23 @@ def main(): f"{cfg.data.action_stats_path}") stats = np.load(cfg.data.action_stats_path) else: - all_actions = np.stack([s[1] for s in train_ds.samples]) + # 统计量必须覆盖 history 与 target 并集:只按 targets 算时, + # targets 中恒定的列 std 落到 1e-8 地板值,而 history 窗口 + # 可能含该列的其他取值(如 .mt 脚本前几行),z-score 会爆炸 + # (实测 (90-45)/1e-8=4.5e9),后续 act_tokens ±1e9 直接 + # 打爆 DFC 量化(NegativeSlopeExponentNonFixable) + all_actions = np.stack( + [np.concatenate([s[0], s[1][None]]) for s in train_ds.samples] + ).reshape(-1, cfg.data.action_dim) stats = np.stack([all_actions.mean(axis=0), all_actions.std(axis=0) + 1e-8]) stats_path = os.path.join(cfg.train.checkpoint_dir, "action_stats.npy") np.save(stats_path, stats) - cfg.data.action_stats_path = stats_path + # 只存裸文件名:推理端按 checkpoint 目录解析相对路径 + # (predict.py/export_deploy.py/gen_calibration.py 同语义), + # 存 "./checkpoints/..." 会被二次拼接成 checkpoints/./checkpoints + cfg.data.action_stats_path = "action_stats.npy" print(f"action_stats 已保存: {stats_path}(训练集 targets 统计)") if stats.shape != (2, cfg.data.action_dim): raise ValueError( diff --git a/actpred/test_actpred.py b/actpred/test_actpred.py index 00e2664..994570b 100644 --- a/actpred/test_actpred.py +++ b/actpred/test_actpred.py @@ -261,6 +261,30 @@ class TestActionDataset(unittest.TestCase): self.assertIn("WARNING", buf.getvalue()) self.assertEqual(len(ds), 8 - 3) # 按较少者(8 帧图像)截断 + def test_stats_cover_history_values(self): + """归一化统计量必须覆盖 history 独有取值,否则 z-score 爆炸 + + col0 仅在 history(首行)出现 100,targets 全为 0:只按 targets + 算统计时 std 落到 1e-8 地板值,z=(100-0)/1e-8=1e10。 + """ + with tempfile.TemporaryDirectory() as tmpdir: + base = Path(tmpdir) + ep_dir = base / "episode_0001" + images_dir = ep_dir / "images" + images_dir.mkdir(parents=True, exist_ok=True) + for i in range(7): + Image.fromarray(np.random.randint(0, 255, (16, 16, 3), dtype=np.uint8) + ).save(images_dir / f"{i:04d}.jpg") + actions = np.zeros((7, 4), dtype=np.float32) + actions[0, 0] = 100.0 + np.save(ep_dir / "actions.npy", actions) + ds = ActionDataset(tmpdir, history_len=5, action_dim=4, + normalize_actions=True) + self.assertGreater(ds._action_std[0], 1.0) # 而非 1e-8 地板值 + z_max = (np.abs(actions[:, 0] - ds._action_mean[0]) + / ds._action_std[0]).max() + self.assertLess(z_max, 10.0) + class TestConfig(unittest.TestCase): """测试配置和 CLI 参数""" diff --git a/actpred/train.py b/actpred/train.py index b99956a..57fa1e3 100644 --- a/actpred/train.py +++ b/actpred/train.py @@ -89,7 +89,8 @@ def main(): stats_path = os.path.join(cfg.train.checkpoint_dir, "action_stats.npy") np.save(stats_path, train_ds.get_action_stats()) print(f"Action stats saved to: {stats_path}") - cfg.data.action_stats_path = stats_path + # 只存裸文件名:推理端按 checkpoint 目录解析相对路径 + cfg.data.action_stats_path = "action_stats.npy" val_ds = ActionDataset( cfg.data.data_dir, cfg.data.history_len, cfg.data.action_dim, cfg.data.image_width, cfg.data.image_height, cfg.data.normalize_actions,