Files
blackbird/actpred/predict.py
T
qing 73b72d6889 fix(actpred): 修复归一化统计不一致等 5 项问题并增强可复现性
- train/val 归一化统计量改为仅用训练集计算并落盘复用,修复 val loss 不可信
- predict.py 统计文件路径改用 os.path 处理,修复跨平台与无分隔符路径问题
- dataset.py jpg/png 混合时统一按文件名排序,保证时间顺序
- 新增 --seed 全局随机种子(含 DataLoader generator),训练可复现
- 新增 --max-grad-norm 梯度裁剪(默认 1.0),防 GRU 梯度爆炸
- get_episode_count 与数据集目录过滤条件对齐,避免无效目录导致索引错位
- 空验证集返回 inf 并保底保存最终权重,避免伪造 best 模型
- 清理 export_onnx.py 重复分支死代码
2026-08-08 14:00:49 +00:00

97 lines
3.8 KiB
Python

import argparse
import json
import os
import numpy as np
import torch
from PIL import Image
from torchvision import transforms
from config import Config, ModelConfig, DataConfig
from model import ActionPredictor
class ActionPredictorInference:
def __init__(self, checkpoint_path: str, device: str = None):
checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=True)
cfg_dict = checkpoint["config"]
self._data_cfg = DataConfig(**cfg_dict["data"])
self._model_cfg = ModelConfig(**cfg_dict["model"])
if device is None:
device = "cuda" if torch.cuda.is_available() else "cpu"
self._device = torch.device(device)
self._model = ActionPredictor(self._model_cfg, self._data_cfg).to(self._device)
self._model.load_state_dict(checkpoint["model_state_dict"])
self._model.eval()
self._transform = transforms.Compose([
transforms.Resize((self._data_cfg.image_size, self._data_cfg.image_size)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
self._action_mean = None
self._action_std = None
stats_path = os.path.join(os.path.dirname(os.path.abspath(checkpoint_path)),
"action_stats.npy")
try:
stats = np.load(stats_path)
self._action_mean = stats[0]
self._action_std = stats[1]
except (FileNotFoundError, ValueError):
pass
@torch.no_grad()
def predict(self, image: np.ndarray, action_history: np.ndarray) -> np.ndarray:
img = Image.fromarray(image) if isinstance(image, np.ndarray) else image
img_tensor = self._transform(img).unsqueeze(0).to(self._device)
history = torch.tensor(action_history, dtype=torch.float32)
if self._action_mean is not None:
history = (history - self._action_mean) / self._action_std
history = history.unsqueeze(0).to(self._device)
pred = self._model(img_tensor, history).squeeze(0).cpu().numpy()
if self._action_mean is not None:
pred = pred * self._action_std + self._action_mean
return pred
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(",")]
assert len(values) == action_dim, \
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")
parser.add_argument("--image", required=True, help="image file path")
parser.add_argument("--action-history", required=True,
help="colon-separated action vectors, e.g. '45,0,45,0:45,10,45,10'")
parser.add_argument("--action-dim", type=int, default=None,
help="override action dim (auto-detected from checkpoint if omitted)")
parser.add_argument("--output-format", choices=["json", "raw"], default="json")
parser.add_argument("--device", type=str, default=None)
args = parser.parse_args()
predictor = ActionPredictorInference(args.model, args.device)
action_dim = args.action_dim or predictor._data_cfg.action_dim
image = Image.open(args.image).convert("RGB")
history = parse_action_history(args.action_history, action_dim)
predicted = predictor.predict(np.array(image), history)
if args.output_format == "json":
print(json.dumps({"predicted_action": predicted.tolist()}, indent=2))
else:
print(" ".join(f"{v:.4f}" for v in predicted))
if __name__ == "__main__":
main()