Files
blackbird/actpred/bins.py
T
qing a18a7de1ca feat(actpred): 多头 one-hot 分类输出——18 组独立 softmax 头替代连续回归
分箱(bins.py):舵机 1°/箱 ×10(180 箱,匹配 0.75° 微动作插值粒度)、
spk.F/spk.delay 对数 32 箱 ×2,共 1864 logits;保留位无头。软标签
(onehot_sigma=1.0 高斯扩散)缓解小样本数据稀疏;期望值解码恢复
连续分辨率(箱生活在原始空间,无反变换放大)。

训练损失为逐头加权 CE(列权重沿用),闭环反馈经解码→范围归一化
回写历史;predict 输出仍为 18 维原始量纲,pybot 契约零变化。

结构性消除尺度放大(每箱等距)与物理界问题(箱天然有界),
为 A-E 归一化实验失败后的正解路线。要求 normalize_mode 为范围类,
与 predict_delta 互斥。测试 +10(含 CLI 映射回归)。
2026-08-15 00:17:58 +00:00

115 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""多头 one-hot 分箱 —— 18 维连续动作向量与多组 one-hot 分类目标互转
分箱设计(actpred_onehot_multihead_dev_plan_20260814.md):
0-9 舵机角:均匀 1°/箱 → 180 箱(匹配 0.75° 微动作插值数据粒度)
10 spk.F:对数分箱 log10(1+x) ∈ [0, log10(5001)] → 32 箱
11 spk.delay:对数分箱 log10(1+x) ∈ [0, log10(2001)] → 32 箱
12-17 保留位:无头(恒 0,不参与分类)
编码/解码与 action_norm 的关系:箱生活在原始物理空间(舵机箱=角度),
decode 直接产出原始量纲,无需反归一化;输入历史仍按 normalize_mode
归一化(与现状一致),仅监督目标换为 one-hot。
软标签:encode(x, sigma) 把真值 one-hot 高斯扩散到邻近箱,缓解
804→1864 分类面的数据稀疏(多数箱极少被访问)。
仅依赖 numpy,推理/部署文件可直接复用。
"""
import numpy as np
# 每组 one-hot 的箱数(按列索引;保留位无头)
BINS_PER_COL = [180] * 10 + [32, 32]
# 有头的列
HEAD_COLS = tuple(range(12))
# 各组起始位置(组 i 占 [START[i], START[i+1]))
START = np.concatenate([[0], np.cumsum(BINS_PER_COL)])
TOTAL_BINS = int(START[-1]) # 1864
LOG_COLS = (10, 11) # 对数分箱列
LOG_HI = {10: 5000.0, 11: 2000.0}
def bin_edges(col: int) -> np.ndarray:
"""列 col 的箱边界数组(长度 bins+1),原始量纲递增"""
n = BINS_PER_COL[col]
if col in LOG_COLS:
hi = LOG_HI[col]
lo_log, hi_log = 0.0, np.log10(hi + 1.0)
# 对数空间均匀边界 → 原始空间箱边界
return np.power(10.0, np.linspace(lo_log, hi_log, n + 1)) - 1.0
lo, hi = 0.0, 180.0
return np.linspace(lo, hi, n + 1)
def bin_centers(col: int) -> np.ndarray:
"""列 col 的箱中心(decode 期望值的坐标),原始量纲"""
e = bin_edges(col)
return 0.5 * (e[:-1] + e[1:])
def encode(x: np.ndarray, action_dim: int = 18, sigma: float = 0.0,
out: np.ndarray = None) -> np.ndarray:
"""连续动作向量 (..., D) → 多组 one-hot (..., TOTAL_BINS)
sigma > 0 时启用软标签:真值箱高斯扩散 exp(-d²/2σ²),d=与真值箱的
距离(按箱索引);归一化后总和为 1。保留位列(12+)无头,恒不编码。
"""
a = np.asarray(x, dtype=np.float32)
flat = a.reshape(-1, a.shape[-1])
n_rows = flat.shape[0]
if out is None:
out = np.zeros(a.shape[:-1] + (TOTAL_BINS,), dtype=np.float32)
out_flat = out.reshape(n_rows, TOTAL_BINS)
rows = np.arange(n_rows)
for col in HEAD_COLS:
if col >= flat.shape[1]:
break
edges = bin_edges(col)
idx = np.clip(np.searchsorted(edges, flat[:, col], side="right") - 1,
0, BINS_PER_COL[col] - 1)
s, e = START[col], START[col + 1]
if sigma <= 0.0:
out_flat[rows, s + idx] = 1.0
else:
n = BINS_PER_COL[col]
d = np.abs(np.arange(n)[None, :] - idx[:, None])
g = np.exp(-(d * d) / (2.0 * sigma * sigma))
g = g / g.sum(axis=1, keepdims=True)
out_flat[:, s:s + n] = g
return out
def decode(logits: np.ndarray, action_dim: int = 18,
temperature: float = 1.0) -> np.ndarray:
"""多头 logits (..., TOTAL_BINS) → 连续动作向量 (..., D),原始量纲
逐头 softmax → 期望值 Σ pᵢ·箱中心(分辨率超越箱宽);保留位列填 0。
temperature 用于探索采样(=1 为正常推理)。
"""
l = np.asarray(logits, dtype=np.float32)
out = np.zeros(l.shape[:-1] + (action_dim,), dtype=np.float32)
for col in HEAD_COLS:
if col >= action_dim:
break
s, e = START[col], START[col + 1]
z = l[..., s:e] / max(temperature, 1e-6)
z = z - z.max(axis=-1, keepdims=True) # 数值稳定
p = np.exp(z)
p = p / p.sum(axis=-1, keepdims=True)
out[..., col] = p @ bin_centers(col)
return out
def argmax_decode(logits: np.ndarray, action_dim: int = 18) -> np.ndarray:
"""argmax 解码:每头取最大概率箱中心(确定性,量化到箱宽)"""
l = np.asarray(logits, dtype=np.float32)
out = np.zeros(l.shape[:-1] + (action_dim,), dtype=np.float32)
for col in HEAD_COLS:
if col >= action_dim:
break
s, e = START[col], START[col + 1]
idx = np.argmax(l[..., s:e], axis=-1)
out[..., col] = bin_centers(col)[idx]
return out