数据清洗基础:格式统一化、缺失值处理、异常值检测与剔除、时间戳对齐
数据清洗,说白了就是给原始数据「洗澡」。我见过太多团队,模型训不出来,第一反应是改网络结构、调超参数。其实呢?八成是数据本身有问题。VLA模型对数据质量极其敏感——你喂进去的是垃圾,吐出来的必然是垃圾。
这一节,我们聚焦四个最基础也最关键的清洗步骤。每个步骤我都踩过坑,咱们一个一个说。
1. 数据格式统一化
VLA数据通常来自不同传感器、不同采集设备。格式五花八门——有的存成JSON,有的存成HDF5,有的干脆是CSV。我接手过一个项目,视觉数据是RGB图像,但有的样本是BGR通道顺序,有的又是RGBA带透明通道。模型训练到一半loss突然飙升,查了两天才发现是通道顺序没统一。
统一化的核心原则:
- 图像数据:统一尺寸(如224x224)、通道顺序(RGB)、数据类型(uint8或float32)、归一化范围([0,1]或[-1,1])
- 文本数据:统一编码(UTF-8)、统一分词器、统一特殊token处理
- 动作/控制数据:统一坐标系(如机器人基座坐标系)、统一单位(弧度vs角度)、统一采样频率
- 元数据:统一字段命名、统一时间格式(ISO 8601)、统一缺失值标记
我的经验:建议在数据采集阶段就制定一份「数据格式规范文档」。别等到清洗时再统一,那会累死。我曾经因为没做这一步,花了两周写转换脚本,结果还漏掉了一批数据。
# 图像格式统一化示例
import cv2
import numpy as np
def unify_image_format(img, target_size=(224, 224)):
# 确保通道顺序为RGB
if img.shape[-1] == 4: # RGBA -> RGB
img = cv2.cvtColor(img, cv2.COLOR_RGBA2RGB)
elif len(img.shape) == 2: # 灰度 -> RGB
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
# 统一尺寸
img = cv2.resize(img, target_size, interpolation=cv2.INTER_LINEAR)
# 归一化到[0,1]
img = img.astype(np.float32) / 255.0
return img
2. 缺失值处理
VLA数据里缺失值太常见了。传感器掉线、网络丢包、标注遗漏……原因五花八门。处理缺失值,我一般分三步走:
- 检测:统计每个字段的缺失比例。缺失超过80%的字段,直接扔掉。
- 分析:判断缺失是随机还是系统性的。比如某个传感器只在特定光照条件下掉线,那就是系统性缺失,需要排查硬件问题。
- 填充或删除:根据场景选择策略。
常用的填充策略:
| 数据类型 | 推荐策略 | 适用场景 |
|---|---|---|
| 连续数值(如关节角度) | 线性插值、前向填充 | 时序数据,缺失间隔小 |
| 离散类别(如物体标签) | 众数填充、模型预测 | 缺失比例低(<5%) |
| 图像/点云 | 丢弃该样本 | 缺失比例低,或使用相邻帧替代 |
| 动作指令 | 丢弃该轨迹段 | 连续缺失超过3帧 |
避坑指南:我曾经用均值填充关节角度数据,结果模型学出来的动作全是「平均动作」,毫无多样性。后来改用线性插值,效果好了很多。记住:VLA模型需要的是真实分布,不是统计平均值。
# 时序数据缺失值插值示例
import pandas as pd
def interpolate_missing_values(df, column, method='linear'):
# 只对连续缺失不超过5帧的数据进行插值
df[column] = df[column].interpolate(method=method, limit=5)
# 剩余缺失值用前向填充
df[column] = df[column].ffill()
return df
3. 异常值检测与剔除
异常值在VLA数据里特别隐蔽。比如机械臂突然抖动一下,采集到的关节速度瞬间飙到1000度/秒——这明显是传感器噪声,不是真实动作。如果不剔除,模型会学到这种「抖动模式」,部署时机器人就真的抖起来了。
我常用的检测方法:
- 统计方法:Z-score(适合正态分布)、IQR(四分位距,适合非正态分布)
- 物理约束:关节角度不能超过机械限位、速度不能超过电机额定值、加速度不能超过安全阈值
- 时序一致性:相邻帧之间的变化量不能超过物理极限
- 模型辅助:用训练好的编码器重建数据,重建误差大的就是异常
注意:别一刀切。有些「异常值」其实是真实场景中的罕见事件,比如机器人抓取时物体滑落。这种数据反而对模型泛化有帮助。我一般会先标记异常,人工审核后再决定是否剔除。
# 基于IQR的异常值检测
def detect_outliers_iqr(data, column, multiplier=1.5):
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - multiplier * IQR
upper_bound = Q3 + multiplier * IQR
outliers = data[(data[column] < lower_bound) | (data[column] > upper_bound)]
return outliers.index.tolist()
4. 时间戳对齐
VLA模型的核心是多模态对齐——视觉、语言、动作必须在时间上严格同步。我见过最离谱的情况:视觉帧率30fps,动作指令频率100Hz,语言指令是异步触发的。三个数据流的时间戳各自为政,模型根本学不会「看到杯子时伸手去抓」这种因果关系。
对齐策略:
- 统一时间基准:所有传感器使用同一个时钟源(如NTP同步)。这是最理想的方案。
- 最近邻匹配:以最低频率的数据流为基准,其他数据流取最近的时间戳。适合帧率差异不大的场景。
- 线性插值:对高频数据流进行插值,对齐到低频数据流的时间点。适合连续信号(如关节角度)。
- 事件触发对齐:以关键事件(如「开始抓取」指令)为锚点,对齐前后一段时间的数据。
我的习惯:在数据采集时,我会给每个样本打一个全局时间戳(Unix时间戳,精确到微秒)。清洗时先按全局时间戳排序,再检查相邻帧的时间差是否稳定。如果发现时间差突然变大,说明有丢帧,需要标记或补帧。
# 时间戳对齐示例:将高频动作数据对齐到低频视觉数据
def align_timestamps(visual_ts, action_ts, action_data):
"""
visual_ts: 视觉帧的时间戳列表
action_ts: 动作数据的时间戳列表
action_data: 动作数据数组
"""
aligned_actions = []
for v_ts in visual_ts:
# 找到最接近的动作时间戳
idx = np.argmin(np.abs(action_ts - v_ts))
aligned_actions.append(action_data[idx])
return np.array(aligned_actions)
知识体系总览
下面这张图概括了数据清洗的四个核心步骤及其关系。你可以把它当作清洗流程的检查清单。
数据清洗这件事,说起来简单,做起来全是细节。我刚开始带团队时,总觉得清洗是「体力活」,让实习生去做就行。后来发现,清洗策略直接影响模型性能——同样的网络结构,清洗前后的数据训出来的模型,成功率能差30%以上。
所以我的建议是:把清洗当作模型训练的一部分来对待。每次清洗完,都做一次可视化检查——看看插值后的曲线是否平滑,看看对齐后的多模态数据是否同步。眼睛看一遍,比跑十个指标都管用。
最后一个小技巧:清洗流程最好写成可复用的Pipeline。每次有新数据进来,跑一遍同样的流程,保证一致性。我自己的项目里,清洗代码和训练代码是放在同一个仓库里的,版本一起管理。