Skip to content

数据质量评估指南

本文档提供 L2 数据采集的质量评估方法,帮助你识别和过滤低质量轨迹,提升下游 BC/VLA 训练效果。

核心质量指标

指标定义目标值评估工具
Success Rate成功完成任务的 episode 比例>95% (scripted), >80% (RL), >90% (teleop)h5_inspect.py --stats
Episode 长度每个 episode 的步数均值稳定,方差小h5_inspect.py --stats
Action 平滑度相邻步之间 action 变化率<0.1 (位置), <0.5 (旋转)visualize_episode.py
轨迹一致性同一任务的多条轨迹相似度高(scripted), 中(RL/teleop)人工检查视频
数值异常NaN / Inf / 超出范围0h5_inspect.py --stats

Success Rate 分析

查看整体成功率

bash
# 查看 HDF5 中所有 episode 的 success 标记
uv run h5_inspect data/lift_cube/scripted/{run_id}/episodes.hdf5 --stats

# 输出示例:
# Episode Statistics:
#   Total episodes: 1000
#   Success rate: 98.5% (985/1000)
#   Episode length: mean=127.3, std=8.2, min=98, max=156

三种来源的典型成功率

数据源典型成功率失败原因改进方法
Scripted95-99%状态机逻辑 bug、物理不稳定调试 SM、增加容错逻辑
RL70-90%策略未完全收敛、探索噪声延长训练、降低探索率
Teleop85-95%人为失误、操作不熟练提高操作熟练度、重录失败 demo

失败 Episode 的处理

方案 1:自动过滤(推荐)

L2 采集脚本已内置 EXPORT_SUCCEEDED_ONLY 机制,只写入成功的 episode:

python
# src/sim2lerobot/sources/scripted/collect.py 中已启用
recorder_cfg = ActionStateRecorderManagerCfg(
    export_succeeded_only=True,  # 只导出 success=True 的 episode
)

方案 2:事后过滤

如果需要保留失败 episode 用于分析,可以在 L3 转换时过滤:

bash
uv run convert_lerobot \
    data/lift_cube/scripted/{run_id}/episodes.hdf5 \
    data/lerobot/lift_cube_filtered/ \
    --filter-success

Episode 长度分布

查看长度统计

bash
uv run h5_inspect data/lift_cube/scripted/{run_id}/episodes.hdf5 --stats

# 输出示例:
# Episode length distribution:
#   Mean: 127.3 steps
#   Std: 8.2 steps
#   Min: 98 steps
#   Max: 156 steps
#   Percentiles:
#     p10: 115 steps
#     p50: 126 steps
#     p90: 139 steps

异常长度的识别

过短 episode(<p10):

  • 可能原因:任务提前失败、碰撞导致 reset
  • 处理:检查视频,确认是否为有效轨迹

过长 episode(>p90):

  • 可能原因:策略犹豫、陷入局部最优、人为拖延
  • 处理:检查视频,考虑过滤(可能是低质量轨迹)

三种来源的典型长度分布

数据源典型长度(steps)标准差特点
Scripted120-1405-10非常稳定,方差小
RL100-20020-40方差大,包含探索轨迹
Teleop150-25030-50方差大,受人类操作速度影响

Action 平滑度分析

可视化 Action 时序

bash
# 可视化单个 episode 的 action 变化
uv run visualize_episode \
    data/lift_cube/scripted/{run_id}/episodes.hdf5 \
    --episode 0

# 输出:
# - 3D 轨迹图(EE 位置 + cube 位置)
# - Action 时序图(8 个维度的变化曲线)

计算 Action 变化率

Action 平滑度定义为相邻步之间的变化率:

python
# 伪代码
action_diff = np.diff(actions, axis=0)  # shape: (T-1, 8)
smoothness = np.abs(action_diff).mean(axis=0)  # shape: (8,)

# 典型值:
# - 位置 (xyz): 0.01-0.05 m/step
# - 旋转 (quat): 0.05-0.2 rad/step
# - 夹爪: 0.0-1.0 (离散切换)

三种来源的典型平滑度

数据源位置变化率 (m/step)旋转变化率 (rad/step)特点
Scripted0.01-0.030.05-0.15非常平滑,motion planner 保证
RL0.02-0.080.1-0.3中等平滑,偶有抖动
Teleop0.03-0.10.15-0.4最不平滑,受人类操作影响

识别抖动轨迹

阈值定义

  • 位置变化率 > 0.1 m/step → 可能抖动
  • 旋转变化率 > 0.5 rad/step → 可能抖动

过滤策略

python
# 伪代码:在 l3/convert.py 中添加
def is_smooth(actions, pos_threshold=0.1, rot_threshold=0.5):
    action_diff = np.abs(np.diff(actions, axis=0))
    pos_diff = action_diff[:, :3].max()  # xyz
    rot_diff = action_diff[:, 3:7].max()  # quat
    return pos_diff < pos_threshold and rot_diff < rot_threshold

# 过滤时使用
if not is_smooth(episode_actions):
    print(f"Skipping episode {i} due to high jitter")
    continue

轨迹一致性评估

视觉检查

最直观的方法是观看视频,检查轨迹是否合理:

bash
# 录制视频(每 5 个 episode 录一个)
uv run sim2lerobot scripted collect \
    --num_demos 100 \
    --num_envs 4 \
    --video --video_interval 5

# 查看视频
ls data/lift_cube/scripted/{run_id}/videos/
# episode-0.mp4, episode-5.mp4, episode-10.mp4, ...

检查要点

  • 运动轨迹是否流畅
  • 是否有异常碰撞或穿模
  • 夹爪开合时机是否合理
  • 物体是否稳定抓取

轨迹相似度(高级)

对于 scripted 数据,多条轨迹应该高度相似(因为是确定性策略)。可以用 DTW(Dynamic Time Warping)计算相似度:

python
# 伪代码
from scipy.spatial.distance import euclidean
from fastdtw import fastdtw

# 比较两条轨迹的 EE 位置
traj1 = episode1["observations/ee_pos"]  # shape: (T1, 3)
traj2 = episode2["observations/ee_pos"]  # shape: (T2, 3)

distance, path = fastdtw(traj1, traj2, dist=euclidean)
similarity = 1.0 / (1.0 + distance)  # 归一化到 [0, 1]

# 典型值:
# - Scripted: similarity > 0.95
# - RL: similarity > 0.7
# - Teleop: similarity > 0.6

数值异常检测

检查 NaN / Inf

bash
uv run h5_inspect data/lift_cube/scripted/{run_id}/episodes.hdf5 --stats

# 输出示例:
# Observations:
#   ee_pos: shape=(3,), dtype=float32, range=[-0.5, 0.5], NaN=0, Inf=0
#   ee_quat: shape=(4,), dtype=float32, range=[-1.0, 1.0], NaN=0, Inf=0
# Actions:
#   actions: shape=(8,), dtype=float32, range=[-1.0, 1.0], NaN=0, Inf=0

如果发现 NaN / Inf

  1. 检查仿真是否稳定(物理爆炸、碰撞穿模)
  2. 检查 IK 求解是否失败(无解时可能返回 NaN)
  3. 检查传感器读数是否异常

检查数值范围

Action 范围检查

  • IK-abs 位置:应该在工作空间内(如 [-0.5, 0.5] m)
  • IK-abs 旋转:quat 应该归一化(norm(quat) ≈ 1.0
  • 夹爪:应该在 [0, 1] 或 [-1, 1]

Observation 范围检查

  • EE 位置:应该在合理范围内
  • 物体位置:应该在桌面上方(z > 0)
  • 关节角度:应该在关节限位内

三种来源的质量对比

综合评分

维度ScriptedRLTeleop
Success Rate⭐⭐⭐⭐⭐ (98%)⭐⭐⭐⭐ (85%)⭐⭐⭐⭐ (90%)
轨迹平滑度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
轨迹一致性⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
多样性⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
数据量⭐⭐⭐⭐⭐ (10⁵+)⭐⭐⭐⭐ (10³-10⁴)⭐⭐ (10²-10³)

混合训练策略

根据 GR00T 论文[1],混合三种来源可以取长补短:

  1. Scripted 提供量:大规模数据覆盖基础技能
  2. RL 提供 recovery:包含失败恢复轨迹,提升鲁棒性
  3. Teleop 提供质量:人类意图最清晰,作为"黄金标准"

推荐配比

  • Scripted: 70-80%(10,000-50,000 demos)
  • RL: 10-20%(1,000-5,000 demos)
  • Teleop: 5-10%(500-2,000 demos)

训练时对 teleop 数据上采样(重复 2-3 次),提高其权重。

质量过滤流程

推荐流程

1. L2 采集时自动过滤失败 episode
   ↓ (EXPORT_SUCCEEDED_ONLY=True)
2. 用 h5_inspect.py --stats 查看整体统计

3. 用 visualize_episode.py 抽查异常 episode

4. L3 转换时应用数值过滤(NaN/Inf/范围检查)

5. (可选)应用平滑度过滤(action 变化率阈值)

6. 生成最终 LeRobot dataset

过滤参数建议

过滤器阈值适用场景
Success filtersuccess == True所有来源
Length filterp10 < length < p90RL(过滤异常长/短轨迹)
Smoothness filterpos_diff < 0.1, rot_diff < 0.5RL / Teleop(过滤抖动)
NaN/Inf filterno NaN/Inf所有来源
Range filteractions in valid range所有来源

质量监控工具

已实现工具

工具功能用法
h5_inspect.py --stats数值统计 + episode 长度分布uv run h5_inspect <hdf5> --stats
visualize_episode.py3D 轨迹 + action 时序图uv run visualize_episode <hdf5> --episode <i>
视频录制视觉检查--video --video_interval N

待实现工具(扩展点)

工具功能工程量
quality_report.py生成 HTML 质量报告(统计 + 图表)~1 天
filter_dataset.py批量过滤低质量 episode~半天
compare_sources.py对比三种来源的质量指标~半天

常见质量问题

问题 1:RL 成功率低(<70%)

可能原因

  • 训练不充分(迭代次数不够)
  • 奖励函数设计不合理
  • 探索噪声过大

解决方案

  1. 延长训练时间(600 → 1000 iters)
  2. 调整奖励权重(增加 success bonus)
  3. 降低探索率(在 rollout 时用 deterministic policy)

问题 2:Teleop 轨迹抖动严重

可能原因

  • 操作不熟练
  • 控制频率过高(30 Hz 对人类太快)
  • 键盘映射不直观

解决方案

  1. 降低控制频率(--step_hz 20
  2. 增加操作练习时间
  3. 考虑使用 SpaceMouse(更直观)

问题 3:Scripted 轨迹过于单调

可能原因

  • 状态机是确定性的
  • 缺少域随机化

解决方案

  1. 在 SM 中加入随机化(目标位置、抓取姿态)
  2. 启用 IsaacLab 的域随机化(光照、摩擦、质量)
  3. 用 mimic 扩增数据(轻微扰动轨迹)

相关文档


  1. NVIDIA GR00T 技术报告(假设存在,实际需要替换为真实引用) ↩︎