4. 数据准备:数据集采集与标注,数据增强策略,转换为TFRecord或二进制格式

数据准备这一步,说白了就是给模型喂饭。饭好不好吃,直接决定了模型能不能干活。我在做嵌入式AI项目时,见过太多因为数据没处理好导致模型在MCU上跑偏的案例。今天咱们就把这块掰开揉碎了讲清楚。

4.1 数据集采集:从哪儿搞数据?

嵌入式AI的数据集采集,跟云端那套不太一样。云端可以海量爬取,咱们MCU端讲究的是「精准打击」。我个人习惯把数据来源分成三类:

数据来源 适用场景 采集成本 注意事项
公开数据集 通用任务(手势识别、关键词唤醒) 低 需确认授权,注意与目标场景的分布差异
传感器现场采集 特定工业场景、设备振动分析 高 需考虑传感器型号、采样率、环境噪声
仿真/合成数据 极端工况、罕见故障模式 中 需验证仿真与真实数据的迁移效果

我记得有一次做电机故障检测,直接用了公开的振动数据集,结果部署到现场后准确率掉了20%。后来发现是公开数据的采样频率是10kHz,而现场传感器只有2kHz。嗯,这里要注意:采集条件必须与最终部署环境一致。

避坑指南:我曾经在采集加速度计数据时,忽略了MCU的ADC量化精度。云端模型用float32训练得好好的,转到MCU的12位ADC后,特征完全变了形。建议采集时直接使用目标硬件的采样参数。

4.2 数据标注:给数据打标签

标注这件事,听起来简单,做起来全是坑。我见过团队花了两周标注了10万张图片,结果发现标签定义不一致——有人把「轻微抖动」标成「正常」,有人标成「异常」。这种数据喂进去,模型不学歪才怪。

我的建议是:

  • 制定标注规范文档:明确每个类别的边界条件,附上正例和反例截图
  • 多人标注+交叉验证:至少两人独立标注,不一致的地方由专家仲裁
  • 标注质量抽检:每批数据抽检5%-10%,错误率超过2%的批次退回重标

对于时序数据(比如麦克风音频、IMU数据),标注更麻烦。你想想看,一段10秒的音频里,只有中间2秒是关键词,前后都是噪声。这时候我习惯用分段标注工具,把长序列切成短片段再标。

4.3 数据增强策略:让数据「变多」

MCU上的数据增强,跟云端不太一样。云端可以搞复杂的随机裁剪、色彩抖动,但MCU资源有限,增强策略必须轻量且有效。说白了,就是花小钱办大事。

我常用的增强策略分三类:

增强类型 具体方法 适用数据 MCU端可行性
几何变换 随机裁剪、翻转、小角度旋转 图像、雷达点云 高(整数运算即可)
噪声注入 高斯噪声、椒盐噪声、量化噪声模拟 音频、振动、图像 高(可离线完成)
时序扰动 时间拉伸、随机偏移、幅度缩放 IMU、ECG、语音 中(需注意边界处理)
我的经验:对于MCU部署,我特别推荐量化噪声模拟。在训练时就把数据量化为8位或16位精度,让模型提前适应MCU上的低精度推理。这样做剪枝和量化时,精度损失会小很多。

为什么会这样?因为模型在训练时没见过量化噪声,部署时突然遇到,表现就会不稳定。提前在数据增强阶段引入,相当于给模型打了一剂「预防针」。

4.4 数据格式转换:TFRecord与二进制格式

数据准备好了,接下来就是格式转换。MCU上跑推理,不可能像PC一样从文件夹里一张张读图片。我们需要把数据打包成连续存储的二进制格式,方便MCU直接读取。

我个人常用两种格式:

4.4.1 TFRecord格式(TensorFlow生态)

如果你用TensorFlow训练模型,TFRecord是首选。它把数据序列化成二进制,读取速度快,还能利用TensorFlow的数据管道。

# 将图像数据写入TFRecord
import tensorflow as tf

def serialize_example(image_bytes, label):
    feature = {
        'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_bytes])),
        'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
    }
    example = tf.train.Example(features=tf.train.Features(feature=feature))
    return example.SerializeToString()

# 写入文件
with tf.io.TFRecordWriter('dataset.tfrecord') as writer:
    for image_path, label in data_pairs:
        image_bytes = open(image_path, 'rb').read()
        example = serialize_example(image_bytes, label)
        writer.write(example)

4.4.2 自定义二进制格式(通用方案)

对于非TensorFlow生态,或者需要极致存储效率的场景,我习惯自己定义二进制格式。结构很简单:

// 二进制文件结构示例
// [文件头] [样本1] [样本2] ... [样本N]

// 文件头(12字节)
typedef struct {
    uint32_t magic;       // 魔数,用于校验文件类型,例如 0x44415441
    uint32_t num_samples; // 样本总数
    uint32_t sample_size; // 每个样本的字节数(固定长度)
} FileHeader;

// 每个样本(固定长度)
typedef struct {
    uint8_t data[SAMPLE_DATA_SIZE]; // 原始数据
    uint8_t label;                  // 标签
} Sample;
关键点:MCU没有文件系统时,需要把二进制数据直接烧录到Flash的固定地址。这时候样本大小必须固定,否则无法随机访问。我曾在项目中因为样本长度不一致,导致MCU读取时地址错位,排查了整整两天。

4.5 数据准备全流程

说了这么多,咱们用一张图把整个流程串起来:

数据准备全流程 数据准备全流程 数据采集 公开/现场/仿真 数据标注 规范+交叉验证 数据增强 几何/噪声/时序 格式转换 TFRecord / 自定义二进制 MCU部署 Flash烧录 / 运行时加载 关键检查点 1. 样本数量是否平衡? 2. 标注一致性是否达标? 3. 增强策略是否可复现?

4.6 实战建议:小数据集起步

最后说点实在的。很多初学者一上来就想搞几万张图片,觉得数据越多越好。但在嵌入式AI项目里,我建议从小数据集开始。

为什么?因为MCU上的模型通常很小(几十KB到几百KB),参数量有限。你拿1000张图片训练一个MobileNetV2,效果可能跟10000张差不多。但数据准备的工作量却差了10倍。

我的做法:先拿500-1000个样本跑通全流程——采集、标注、增强、转换、训练、剪枝、部署。确认流程没问题后,再根据模型表现决定是否需要扩充数据。这样能避免「数据准备了两周,模型跑不起来」的尴尬。

嗯,数据准备这块就讲到这里。记住一句话:垃圾进,垃圾出。数据质量决定了模型的天花板,后面的剪枝和部署只是在这个天花板下做优化。把数据准备好,后面的路就好走了。