docs(actpred): 模型 B zscore 等权稳定基线文档 + spk 两列固定常量

B 是六模型闭环评估中唯一稳定配置(2445/0.082),固化完整方案作为
部署基线与实验对照基准,并总结归一化系列失败机理(反馈回路需误差
有界且收缩)。配套实现:spk.F/spk.delay 权重归零、推理/反馈固定
常量(bins.FIXED_OUTPUT),拔除闭环发散源,为 B2(zscore+v2)与
F2(onehot+固定)铺路。
This commit is contained in:
qing committed 2026-08-15 02:01:04 +00:00
1 parent 7f21b65428
commit 9000bb4b04
6 files changed
+149 -15

No files matched your search

+5 -7
View File
@@ -85,15 +85,13 @@ def denormalize_action(norm_action, action_dim: int = None,
def default_column_weights(action_dim: int = 18) -> np.ndarray:
"""逐列损失权重默认表:舵机 1.0(安全关键)、spk.F 0.2(保留梯度)、
spk.delay 0.0(步级数据恒 10ms——微动作分解的隐式节拍,时长信息
已编码在步数中)、保留位 0.0(恒 0 纯噪声,参与只会稀释有效梯度)。
action_dim>18 的扩展列默认 1.0:超出当前 18 列语义布局的通道视为
新执行器,静默给 0 会让其完全不参与训练(P2-a1)。"""
"""逐列损失权重默认表:舵机 1.0(安全关键);spk.F / spk.delay 0.0
(只影响声音/节奏不影响运动,推理端固定常量,见 bins.FIXED_OUTPUT);
保留位 0.0(恒 0 纯噪声)。action_dim>18 的扩展列默认 1.0:超出
当前 18 列语义布局的通道视为新执行器,静默给 0 会让其完全不参与
训练(P2-a1)。"""
w = np.zeros(action_dim, dtype=np.float32)
w[:min(10, action_dim)] = 1.0
if action_dim > 10:
w[10] = 0.2
if action_dim > 18:
w[18:] = 1.0
return w
+20
View File
@@ -29,6 +29,26 @@ TOTAL_BINS = int(START[-1]) # 1864
LOG_COLS = (10, 11) # 对数分箱列
LOG_HI = {10: 5000.0, 11: 2000.0}
# 权重 0 列的输出固定值(原始量纲):spk.F 固定 0(只影响声音不影响
# 运动,闭环中持续发声无意义);spk.delay 在步级数据中恒 10ms(微动作
# 分解的隐式节拍)。无监督头在闭环中会输出中段垃圾污染历史,
# 推理/反馈时直接固定为常量。其余权重 0 列(保留位)固定 0。
FIXED_OUTPUT = {10: 0.0, 11: 10.0}
def fixed_zero_columns(weights, action_dim: int = 18) -> np.ndarray:
"""按列权重表返回权重<=0 列的固定输出向量(其余列 0)
与 default_column_weights 联动:列权重 0 → 推理输出固定为
FIXED_OUTPUT 常量(缺省 0),避免无监督头在闭环反馈中输出垃圾。
"""
w = np.asarray(weights, dtype=np.float32)
fixed = np.zeros(action_dim, dtype=np.float32)
for i in range(min(action_dim, len(w))):
if w[i] <= 0.0:
fixed[i] = FIXED_OUTPUT.get(i, 0.0)
return fixed
def bin_edges(col: int) -> np.ndarray:
"""列 col 的箱边界数组(长度 bins+1),原始量纲递增"""
+10 -2
View File
@@ -10,7 +10,7 @@ from config import parse_checkpoint_config
from imu_norm import normalize_imu
from action_norm import (normalize_action, denormalize_action,
is_range_mode)
from bins import decode as bin_decode
from bins import decode as bin_decode, fixed_zero_columns
from model import ActionPredictor
from utils import parse_action_history
@@ -120,8 +120,16 @@ class ActionPredictorInference:
).unsqueeze(0).to(self._device)
pred = self._model(img_tensor, history, imu_tensor).squeeze(0) # 归一化空间
if self._onehot:
# 多头 logits → 期望值解码 → 原始量纲(箱生活在原始空间)
# 多头 logits → 期望值解码 → 原始量纲(箱生活在原始空间);
# 权重 0 列固定为常量(delay=10ms 等),防闭环垃圾回写
result = bin_decode(pred.cpu().numpy(), self.action_dim)
col_w = self._data_cfg.column_weights
if col_w is None:
from action_norm import default_column_weights
col_w = default_column_weights(self.action_dim)
fixed = fixed_zero_columns(col_w, self.action_dim)
m = fixed != 0.0
result[m] = fixed[m]
if not np.all(np.isfinite(result)):
result = np.nan_to_num(result, nan=0.0, posinf=0.0, neginf=0.0)
return result
+16 -5
View File
@@ -2004,14 +2004,12 @@ class TestFixedRangeNorm(unittest.TestCase):
from action_norm import default_column_weights
w18 = default_column_weights(18)
np.testing.assert_allclose(w18[:10], 1.0)
self.assertAlmostEqual(float(w18[10]), 0.2, places=6) # spk.F 保留梯度
np.testing.assert_allclose(w18[11:], 0.0) # spk.delay 步级恒值 + 保留位
np.testing.assert_allclose(w18[10:], 0.0) # spk 两列 + 保留位全 0
# 小 action_dim:全部落在舵机段 → 全 1.0
np.testing.assert_allclose(default_column_weights(4), 1.0)
# 12 列:舵机 1.0 + spk.F 0.2,delay 0
# 12 列:舵机 1.0,spk 两列 0
w12 = default_column_weights(12)
self.assertAlmostEqual(float(w12[10]), 0.2, places=6)
self.assertEqual(float(w12[11]), 0.0)
np.testing.assert_allclose(w12[10:], 0.0)
# 20 列:超出 18 列语义布局的扩展列默认 1.0(新执行器须参与训练)
w20 = default_column_weights(20)
np.testing.assert_allclose(w20[12:18], 0.0)
@@ -2502,6 +2500,19 @@ class TestOneHot(unittest.TestCase):
with self.assertRaises(ValueError):
validate_config_values(cfg.data, cfg.model, cfg.train)
def test_fixed_zero_columns(self):
"""权重 0 列固定常量:spk.F→0、spk.delay→10ms、保留位→0"""
from bins import fixed_zero_columns
from action_norm import default_column_weights
fixed = fixed_zero_columns(default_column_weights(18), 18)
np.testing.assert_allclose(fixed[:10], 0.0)
self.assertEqual(float(fixed[10]), 0.0)
self.assertEqual(float(fixed[11]), 10.0)
np.testing.assert_allclose(fixed[12:], 0.0)
# 全 1 权重 → 无固定列
np.testing.assert_allclose(
fixed_zero_columns(np.ones(4), 4), 0.0)
def test_onehot_model_output_shape(self):
"""onehot 模型前向输出 TOTAL_BINS logits"""
cfg = Config()
+7 -1
View File
@@ -9,7 +9,8 @@ from tqdm import tqdm
from config import (Config, build_argparser, apply_overrides,
validate_config_values, warn_init_from_mismatch)
from action_norm import normalize_action, is_range_mode
from bins import encode as bin_encode, decode as bin_decode, START, HEAD_COLS
from bins import (encode as bin_encode, decode as bin_decode,
fixed_zero_columns, START, HEAD_COLS)
from model import ActionPredictor
from dataset import ActionDataset
from action_norm import default_column_weights
@@ -126,6 +127,11 @@ def train_one_epoch(model, loader, optimizer, device, max_grad_norm: float = Non
if onehot:
raw = bin_decode(pred.detach().cpu().numpy(),
action_dim)
# 权重 0 列固定为常量,防自预测垃圾进入历史
fixed = fixed_zero_columns(
weights.cpu().numpy(), action_dim)
m = fixed != 0.0
raw[m] = fixed[m]
abs_action = torch.from_numpy(normalize_action(
raw, action_dim, mode=range_mode)).to(device)
else:
@@ -0,0 +1,91 @@
# actpred 模型 B 方案文档——zscore 等权稳定基线
> 文档类型:方案文档
> 模块:actpred
> 作者:自动生成
> 日期:2026-08-15
> 相关实验:`pybot_actpred_dataset_training_eval_20260814.md`(A/B 对照)、
> `actpred_fixed_range_weighted_experiment_20260814.md`(C/D/E 失败系列)、
> `actpred_onehot_multihead_dev_plan_20260814.md`(F 系列结构性方案)
---
## 1. 定位
模型 B 是六模型闭环评估(A/B/C/D/E/F)中**唯一稳定**的配置:overall_mse 2445、error_growth 0.082,远优于其余五个(6447~968459)。本文档固化其完整方案,作为:
1. 部署与实机测试的**首选基线**
2. 后续所有实验的**对照基准**
3. 归一化系列失败教训的**参照系**
## 2. 完整配置(checkpoint 实读)
来源:`actpred/checkpoints/recorded_run_norm/best_inference.pt`(epoch 26 早停,best val=0.0288)
### 2.1 数据侧(DataConfig)
| 项 | 值 |
|---|---|
| 分辨率 | 640×480 |
| action_dim / history_len / frame_len | 18 / 5 / 3 |
| normalize_actions | **True(z-score)** |
| action_stats_path | `action_stats.npy`(272B,逐列 (2,18) mean/std,训练集统计) |
| imu_dim | 0 |
| column_weights | None(**等权**) |
### 2.2 训练侧(TrainConfig)
| 项 | 值 |
|---|---|
| 数据 | v1(`data/pybot_record`,804 样本,12/1 episode 划分,seed 42) |
| 训练范式 | **单步 teacher forcing**(rollout_len=1, ss_prob=1.0) |
| 增强 | ColorJitter(--augment) |
| batch / lr / patience | 4 / 1e-3 / 15 |
| predict_delta | False |
### 2.3 模型侧(ModelConfig)
SimpleCNN(128) → Transformer(d=128, 4 头, 2 层, pre-LN) → MLP 头。409K 参数。
### 2.4 闭环表现(v1 数据,R4)
- overall_mse **2445.4**,error_growth **0.082**(<1 无发散),oob_rate 28.1%
- 逐 episode:stand 134 / jump 221 / hand 6850·8023 / tilt 60-752(唯一全部 episode 不爆表的模型)
## 3. 为什么 zscore 稳定(失败系列的对照结论)
| 方案 | 操作点 | 闭环行为 | 机理 |
|---|---|---|---|
| raw 等权(A) | 原始量纲 | 稳定但 spk.F 崩 | 舵机误差数值占优,spk.F 被忽略 |
| **zscore(B)** | **数据均值 0** | **稳定** | **各列单位方差,归一化误差等量纲;误差收缩** |
| fixed_range(C/D) | [0,1] 低端聚集 | 发散(growth 13.4/3.4) | 低端精度错配:小绝对误差=大相对误差 |
| log_range(E) | log 空间 | 爆炸(growth 157) | 指数反变换在高端放大:0.81→1.0 = 1000→5000Hz |
| onehot(F) | 箱(有界) | 部分稳定(oob=0,MSE 33K) | 有界生效,但 spk 无监督头闭环污染 |
**结论**:反馈回路需要**误差有界且收缩**的映射。z-score 的零均值单位方差恰好满足;固定范围类方案各自引入一端(低/高)的放大;one-hot 的有界性正确但需配 spk 列固定(见 F2)。
## 4. 已知局限
1. **依赖 stats 文件**:推理端需随 checkpoint 携带 action_stats.npy(与 fixed_range/onehot 的零状态部署相反)
2. **训练于 v1 数据**:delay 语义混杂(首行 200ms 未分解),spk.delay 权重 0.2——两者已被后续修复(v2 均匀数据 + delay 权重 0)
3. **spk.F 仍弱**:hand 类 episode 8023/6850 的 MSE 主导源是 spk.F(10→1000Hz 大跨度,等权下被均值化)
## 5. 演进建议:B2
模型 B 的两处已知缺陷(v1 数据、delay 权重)都已有修复就绪,B2 = **zscore + v2 数据 + 新权重(spk 列 0)**:
- 预期:闭环 MSE 进一步下降(delay 垃圾不再进历史)
- 若 B2 稳定,作为部署基线导出 ONNX/HEF
- 若 B2 需要 spk.F 频率跟踪(未来语音反馈需求),再叠加加权或 one-hot 方案
## 6. 复现命令
```bash
# 模型 B(v1 数据,旧默认权重)
./venv/bin/python train.py --data-dir /opt/micro_servers/data/pybot_record \
--checkpoint-dir checkpoints/recorded_run_norm --augment --epochs 100
# B2(v2 数据,新权重:spk 两列 0)
./venv/bin/python train.py --data-dir /opt/micro_servers/data/pybot_record_v2 \
--checkpoint-dir checkpoints/recorded_run_norm_v2 --augment --epochs 100
```