LingBot-VA 2.0 的动作数据解析,核心动作是先锁定模型输入层对张量形状和数值范围的要求,再设计一段可替换的转换层。直接对着现有轨迹数据硬转,通常会在维度重排或单位换算处出问题。下面的流程把转换拆成三段:先确认模型示例数据,再统一成中间字典,最后转成模型输入张量,并在每一步加校验和测试,让错误在进入模型之前暴露出来。
动作数据转换的关键是先锁定模型输入层的 shape、dtype 与数值范围。建议采用“原始数据→中间字典→模型张量”的三段式转换,配合推理前校验和单元测试,能有效避免形状不匹配与单位错误。此方案适用常见的关节角度和笛卡尔坐标轨迹,不绑定具体坐标系和字段名,需要结合自身数据列名调整。
确认模型官方要求的数据格式
拿到 LingBot-VA 2.0 动作模型后,先不要写转换脚本,先把官方示例数据跑起来,确定模型输入张量的 shape、dtype 和数值范围。通常在项目仓库里会有 sample_input.pth、example_data.npz 或类似的演示文件。用下面的方式直接打印张量及其属性:
import torch
sample = torch.load('sample_input.pth', map_location='cpu')
# 也可能是 npz 或 h5,按实际文件类型加载
# import numpy as np; sample = np.load('example_data.npz')
print('type:', type(sample))
print('shape:', sample.shape)
print('dtype:', sample.dtype)
print(sample)
# 如果 sample 是 dict,则逐键打印
if isinstance(sample, dict):
for k, v in sample.items():
print(k, v.shape if hasattr(v, 'shape') else type(v))
记录三件事:输入张量的维度顺序(比如是“帧数×关节数”还是“关节数×帧数”)、每个通道的物理含义(位置、速度还是角度)、数值是否做过归一化或标准化。这些信息决定了后面转换器里要不要做单位换算和重新排序。
编写原始数据到中间表示的转换器
不要直接把 CSV 或 JSON 的字段映射到模型输入,先转成一个中间字典,这样后续调整列名或增加新数据源时只改一个地方。中间字典建议包含原始时间戳、关节角度或笛卡尔坐标,以及你关心的辅助信息。下面给出读取 CSV 和 JSON 的示例,统一输出成中间字典:
import json
import numpy as np
def csv_to_intermediate(csv_path):
# 使用 pandas 读取,或按需用 csv 模块
import pandas as pd
df = pd.read_csv(csv_path)
return {
'timestamps': df['timestamp'].to_numpy(dtype=np.float64),
'joint_positions': df[[c for c in df.columns if c.startswith('joint_')]].to_numpy(dtype=np.float32),
'cartesian_positions': df[[c for c in df.columns if c.startswith('cart_')]].to_numpy(dtype=np.float32),
}
def json_to_intermediate(json_path):
with open(json_path, 'r') as f:
data = json.load(f)
return {
'timestamps': np.array(data['time'], dtype=np.float64),
'joint_positions': np.array(data['joints'], dtype=np.float32),
'cartesian_positions': np.array(data['pose'], dtype=np.float32),
}
这个中间字典明确标注了数据属于关节空间还是笛卡尔空间,后面转换模型输入时就能按需取用。若是真实数据里没有某一项,就暂时用空数组占位,不要在转换器里写死。
将中间表示转为模型输入张量
在转成模型输入之前,先确认模型示例数据里用的单位是度还是弧度,坐标系是左乘还是右乘,维度顺序是否和模型一致。这里给出一个通用转换函数,把中间字典转成模型要求的张量,并顺带做零均值归一化:
import torch
def intermediate_to_model_input(intermediate, config):
# config 里应写明:use_columns, input_dim, normalize_mean, normalize_std
selected = []
if 'joint' in config['use_columns']:
joint = intermediate['joint_positions']
# 若模型使用弧度,但原始数据是度,则在此转换
if config.get('angle_unit') == 'degree':
joint = joint * (3.14159265 / 180.0)
selected.append(joint)
if 'cartesian' in config['use_columns']:
# 笛卡尔坐标只做平移,不翻转坐标系,除非模型要求
cart = intermediate['cartesian_positions']
selected.append(cart)
arr = np.concatenate(selected, axis=1) # 假设模型是 [seq_len, feature_dim]
# 维度重排:若模型需要 [feature_dim, seq_len],则转置
if config.get('transpose'):
arr = arr.T
# 归一化:使用与训练一致的均值和标准差
arr = (arr - config['normalize_mean']) / config['normalize_std']
return torch.tensor(arr, dtype=torch.float32)
归一化参数必须从模型示例数据统计出来,不能随便选 0 和 1。如果示例数据是多个样本,就统计所有样本的均值和标准差,并保持与模型训练时一致,这样才能保证转换后的数据落入模型期望的分布。
校验转换后的数据是否被模型接受
转换完成后,不要直接送进业务逻辑,先跑一次模型推理,显式捕获异常。模型在遇到形状或 dtype 不匹配时通常会抛 TypeError、RuntimeError,或者内置的 assert 失败。用下面的方式把错误记录下来,方便定位:
def try_inference(model, tensor):
try:
with torch.no_grad():
output = model(tensor)
return output
except AssertionError as e:
raise RuntimeError(f'模型断言失败,输入张量 shape: {tensor.shape}, dtype: {tensor.dtype}, 错误信息: {e}')
except RuntimeError as e:
raise RuntimeError(f'模型推理运行时错误: {e}, 输入 shape: {tensor.shape}')
except Exception as e:
raise RuntimeError(f'未知错误: {e}')
# 使用示例
tensor = intermediate_to_model_input(intermediate, config)
# 先校验维度,再调用推理
if tensor.ndim != expected_dims:
raise ValueError(f'期望 {expected_dims} 维,实际 {tensor.ndim} 维')
try_inference(model, tensor)
这类校验应该作为转换流程的固定步骤,而不是出了问题再查。把模型示例数据也走一遍这个校验函数,如果示例数据都报错,说明模型加载或校验函数本身有误。
用正则化测试保证转换可重复
为了避免改动转换器后引入随机错误,写一个单元测试,固定输入和 mock 模型,比较转换前后的张量是否一致。mock 模型只需要一个简单的 forward 函数,返回输入本身,这样就能检查转换器的输出是否符合预期结构。下面是一个 pytest 示例:
import torch
import pytest
from your_converter import intermediate_to_model_input
class MockModel(torch.nn.Module):
def forward(self, x):
return x
def make_fixed_intermediate():
# 固定一批数据,确保测试可复现
return {
'timestamps': np.arange(10, dtype=np.float64),
'joint_positions': np.tile(np.array([30., 60., 90.], dtype=np.float32), (10, 1)),
'cartesian_positions': np.zeros((10, 3), dtype=np.float32),
}
def test_intermediate_to_model_input():
inter = make_fixed_intermediate()
config = {
'use_columns': ['joint'],
'angle_unit': 'degree',
'transpose': False,
'normalize_mean': np.array([0.0, 0.0, 0.0]),
'normalize_std': np.array([1.0, 1.0, 1.0]),
}
tensor = intermediate_to_model_input(inter, config)
# 形状应与固定输入一致
assert tensor.shape == (10, 3)
# 值应等于角度转弧度后的结果,再除以 std 1
expected = np.array([30., 60., 90.]) * (3.14159265 / 180.0)
assert torch.allclose(tensor[0], torch.tensor(expected, dtype=torch.float32), atol=1e-5)
# mock 模型应能接受该输入
model = MockModel()
output = model(tensor)
assert output.shape == tensor.shape
这个测试可以放在 CI 或本地提交前运行。每次改动数据源或转换逻辑,先跑这个用例,能快速暴露单位换算、维度顺序或 dtype 的问题。若后续增加新的中间字段,也只要在固定输入里补一项,并追加对应的断言。