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: