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:
1 parent
7f21b65428
commit
9000bb4b04
6 files changed
+149
-15
No files matched your search
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
```
|
||||
Reference in new issue
Block a user