feat(actpred): 固定范围归一化 + 逐列加权损失,附 pybot*actpred 审计修复
新增 action_norm.py 固定物理范围 min-max 归一化,无 stats 文件零状态部署;train.py 引入 _weighted_mse 逐列加权(舵机 1.0/spk 0.2/保留位 0.0)。审计修复: P0-1 pybot 读 normalize_mode, R2-1 NaN 阻断 best, P2-a 后端大小写不敏感, P1-3 utf-8-sig BOM, R1-1 data_dir 覆盖, R2-7 _run.sh 报错, R2-3 空 val 存 best.pt, P2-a2 误报警, R1-2 合并 parse_action_history, P2-b 合并 numpy import。评估修正 oob 物理界+error_growth 按 episode 中位数。推理部署全链支持 fixed_range。
This commit is contained in:
1 parent
adfa61aa3c
commit
85bf68f505
19 files changed
+874
-167
No files matched your search
+17
-17
@@ -8,7 +8,9 @@ from torchvision import transforms
|
||||
|
||||
from config import parse_checkpoint_config
|
||||
from imu_norm import normalize_imu
|
||||
from action_norm import normalize_action, denormalize_action
|
||||
from model import ActionPredictor
|
||||
from utils import parse_action_history
|
||||
|
||||
|
||||
class ActionPredictorInference:
|
||||
@@ -48,7 +50,11 @@ class ActionPredictorInference:
|
||||
|
||||
self._action_mean = None
|
||||
self._action_std = None
|
||||
if self._data_cfg.normalize_actions:
|
||||
# fixed_range:固定物理范围 min-max(免 stats 文件);旧 checkpoint 无
|
||||
# normalize_mode 键,parse_checkpoint_config 默认 "zscore",行为不变
|
||||
self._fixed_range = (self._data_cfg.normalize_actions
|
||||
and self._data_cfg.normalize_mode == "fixed_range")
|
||||
if self._data_cfg.normalize_actions and not self._fixed_range:
|
||||
# 优先使用 checkpoint config 记录的 stats 路径(相对路径按 checkpoint 目录解析),
|
||||
# 旧 checkpoint 无该字段时回退到 checkpoint 同目录的 action_stats.npy
|
||||
stats_path = self._data_cfg.action_stats_path
|
||||
@@ -94,7 +100,10 @@ class ActionPredictorInference:
|
||||
frames.append(self._transform(img))
|
||||
img_tensor = torch.stack(frames).unsqueeze(0).to(self._device) # (1, F, 3, S, S)
|
||||
history = torch.tensor(action_history, dtype=torch.float32)
|
||||
if self._action_mean is not None:
|
||||
if self._fixed_range:
|
||||
history = torch.from_numpy(
|
||||
normalize_action(action_history, self.action_dim))
|
||||
elif self._action_mean is not None:
|
||||
history = (history - self._action_mean) / self._action_std
|
||||
history = history.unsqueeze(0).to(self._device)
|
||||
imu_tensor = None
|
||||
@@ -106,27 +115,18 @@ class ActionPredictorInference:
|
||||
if self._predict_delta:
|
||||
# 残差:Δ̂ 加回 a_{t-1}(history 末步,同为归一化空间)→ 绝对动作
|
||||
pred = history[0, -1] + pred
|
||||
if self._action_mean is not None:
|
||||
pred = pred * self._action_std + self._action_mean
|
||||
result = pred.cpu().numpy()
|
||||
if self._fixed_range:
|
||||
result = denormalize_action(pred.cpu().numpy(), self.action_dim)
|
||||
else:
|
||||
if self._action_mean is not None:
|
||||
pred = pred * self._action_std + self._action_mean
|
||||
result = pred.cpu().numpy()
|
||||
if not np.all(np.isfinite(result)):
|
||||
print("WARNING: predict output contains NaN/Inf, replacing with zeros")
|
||||
result = np.nan_to_num(result, nan=0.0, posinf=0.0, neginf=0.0)
|
||||
return result
|
||||
|
||||
|
||||
def parse_action_history(s: str, action_dim: int) -> np.ndarray:
|
||||
steps = s.split(":")
|
||||
history = []
|
||||
for step in steps:
|
||||
values = [float(v) for v in step.split(",")]
|
||||
if len(values) != action_dim:
|
||||
raise ValueError(
|
||||
f"Expected {action_dim} values per step, got {len(values)}")
|
||||
history.append(values)
|
||||
return np.array(history, dtype=np.float32)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="actpred inference")
|
||||
parser.add_argument("--model", required=True, help="checkpoint path")
|
||||
|
||||
Reference in new issue
Block a user