前言
本文将深入 RDA 的代码实现,详细解析 idle 检测的算法细节,包括 bimodal gap detection 的具体实现、MAD 回退机制,以及 action discontinuity 的 spike 检测方法。
核心问题
给定一个机器人 episode 的 action 序列,如何区分"idle 帧"和"active 帧"?
输入:action[T, D],T 是帧数,D 是 action 维度
输出:每帧的 motion 值、idle 阈值、idle_ratio
算法一:Bimodal Gap Detection
第一步:计算 motion
import numpy as np
# action: shape (T, D)
delta = np.diff(action, axis=0) # 一阶差分,shape (T-1, D)
motion = np.linalg.norm(delta, axis=1) # L2 范数,shape (T-1,)
motion[i] 表示第 i 帧到第 i+1 帧的 action 变化幅度。
第二步:构建 histogram
num_bins = 30
max_motion = float(np.max(motion))
hist, bin_edges = np.histogram(motion, bins=num_bins, range=(0.0, max_motion))
将 motion 值分布到 30 个等宽 bin 中。
第三步:寻找双峰谷底
核心逻辑:从左到右扫描 histogram,找到"低值簇"(idle frames)和"高值簇"(active frames)之间的谷底。
total = motion.size
low_cluster_count = 0
threshold_candidate = None
in_valley = False
valley_min = float("inf")
min_low_cluster_fraction = 0.1 # 低值簇至少占 10%
for i in range(len(hist)):
frac = hist[i] / total
low_cluster_count += hist[i]
low_frac = low_cluster_count / total
# 条件1:低值簇已经积累足够,且当前 bin 是"谷底"(频率 < 1%)
if low_frac >= min_low_cluster_fraction and frac < 0.01:
in_valley = True
if hist[i] < valley_min:
valley_min = hist[i]
threshold_candidate = float(bin_edges[i + 1])
# 条件2:已经在谷底,且当前 bin 频率回升(> 2%),说明进入高值簇
elif in_valley and frac > 0.02:
if threshold_candidate is not None and threshold_candidate > 0:
return threshold_candidate # 找到阈值
in_valley = False
valley_min = float("inf")
return None # 未找到明显的双峰结构
关键点解释
-
min_low_cluster_fraction = 0.1:低值簇至少占 10% 的帧。这是为了避免把极少数噪声帧误判为 idle 簇。 -
frac < 0.01:当前 bin 的频率 < 1%,认为是"谷底"。这个阈值是经验值,适用于大多数 manipulation task。 -
frac > 0.02:谷底之后,频率回升到 2% 以上,认为进入高值簇。 -
返回值 :谷底的右边界
bin_edges[i + 1],即阈值为motion < threshold的帧被判定为 idle。
适用场景
- ✅ 典型的 pick-and-place、insertion task,动静分明
- ✅ action 分布有明显的双峰结构
- ❌ 全程缓慢操作,无明显动静分界
算法二:MAD 回退机制
当 bimodal detection 失败时(找不到双峰结构),退回到基于 MAD(Median Absolute Deviation)的保守估计。
def _mad(values):
"""计算 MAD"""
median = np.median(values)
return np.median(np.abs(values - median))
mad = _mad(motion)
threshold = 3.0 * mad # 3 倍 MAD 作为阈值
为什么用 MAD 而不是标准差?
- MAD 是鲁棒统计量,不受极端值影响。如果数据中有少量大幅运动(比如遥操作中的快速移动),标准差会被拉高,导致阈值过大;MAD 则不会。
- 3 × MAD 假设:如果 motion 分布近似正态,3 × MAD 约等于 2.7σ,覆盖约 99% 的数据。低于这个值的帧被认为是"异常小"的运动。
适用场景
- ✅ 数据分布单峰,无明显双峰结构
- ✅ 作为 bimodal detection 的兜底方案
- ❌ 数据中有大量异常值时,MAD 可能过于保守
算法三:Action Discontinuity 检测
除了 idle 检测,RDA 还检测 action 序列中的突变点(spikes)。这些突变可能表示:
- 遥操作中的控制模式切换
- 数据采集中的丢帧或插值问题
- 控制器的高频振荡
算法:基于 MAD 的 z-score
# 计算二阶差分(jerk proxy)
delta2 = np.diff(delta, axis=0) # shape (T-2, D)
delta2_norm = np.linalg.norm(delta2, axis=1) # shape (T-2,)
# 计算 MAD-based z-score
median = np.median(delta2_norm)
mad = _mad(delta2_norm)
z_scores = 0.6745 * (delta2_norm - median) / mad
# 检测 spike:|z| > 5.0
spike_mask = np.abs(z_scores) > 5.0
spike_indices = np.where(spike_mask)[0]
spike_count = len(spike_indices)
关键点
-
为什么用二阶差分? 一阶差分(velocity)反映运动速度,二阶差分(acceleration/jerk)反映运动的"突变程度"。spike 通常表现为二阶差分的异常大值。
-
为什么用 MAD 而不是标准差? 同上,MAD 对异常值鲁棒。如果数据中有少量真实 spike,标准差会被拉高,导致 z-score 被压缩;MAD 则不会。
-
阈值 5.0:z-score > 5 表示该帧的二阶差分偏离中位数超过 5 倍 MAD。这是一个保守阈值,目的是捕捉明显的突变,避免误报。
-
系数 0.6745 :这是正态分布下 MAD 与标准差的换算系数。
0.6745 * (x - median) / MAD等价于标准 z-score,但对异常值更鲁棒。
完整流程
def compute_idle_ratio(action):
"""
输入: action array, shape (T, D)
输出: dict with idle_ratio, threshold, method
"""
# 1. 计算 motion
delta = np.diff(action, axis=0)
motion = np.linalg.norm(delta, axis=1)
# 2. 尝试 bimodal detection
threshold = _find_bimodal_threshold(motion)
method = "bimodal_gap"
# 3. 如果失败,退回 MAD
if threshold is None:
mad = _mad(motion)
threshold = 3.0 * mad
method = "mad"
# 4. 计算 idle ratio
idle_mask = motion < threshold
idle_ratio = idle_mask.sum() / len(motion)
return {
"idle_ratio": idle_ratio,
"threshold": threshold,
"method": method
}
在 G1 数据集上的实测
我们用 RDA v0.9.12 审计了 G1_WBT_Brainco_Pickup_Pillow 数据集(300 episodes,177,811 帧)。
结果:
- 所有 300 个 episode 都使用 bimodal gap detection 成功找到阈值,没有触发 MAD 回退
- idle_ratio 中位数 65.6% ,有效运动仅 34.4%
- action discontinuity:299/300 episodes 检测到 spike,总计 3340 个突变点
解读
-
idle_ratio 偏高(65.6%) :这个任务是"拾取枕头并放置到沙发上",包含大量等待、保持姿态的时间,符合 task 特性。
-
spike 普遍存在:几乎所有 episode 都有 action discontinuity。结合 G1 是遥操作采集的数据,这可能与操作过程中的控制模式切换(比如从连续控制切换到抓取命令)有关。
-
bimodal detection 全部成功:说明这个数据集的动静分布确实存在双峰结构,idle 检测的阈值选择是合理的。
总结
表格
| 算法 | 适用场景 | 局限性 |
|---|---|---|
| Bimodal Gap Detection | 动静分明的 manipulation task | 全程缓慢操作时失效 |
| MAD 回退 | 单峰分布,作为兜底方案 | 可能过于保守 |
| MAD-based z-score(spike) | 检测 action 突变点 | 对高频噪声敏感 |
RDA 的 idle 检测设计是逐 episode 自适应的,阈值基于数据本身的分布计算,不是固定的全局常量。这种设计在大多数 manipulation task 上表现良好,但对于特殊任务(全程缓慢操作),用户需要意识到 idle_ratio 可能偏高,并结合其他指标综合判断。
参考
- RDA GitHub: GitHub - liesliy/rda: Robot Data Audit (RDA) - Quality auditing + optimization recommendations for robot manipulation datasets (LeRobot format) · GitHub
- PyPI: Client Challenge
- 相关 Issue: RDA Audit Data Quality Report for lerobot/svla_so101_pickplace · Issue #4650 · huggingface/lerobot · GitHub