4、数据预处理:去噪、滤波(低通/高通)、归一化、滑动窗口分割等常用技术

TinyML 传感器建模的起点并非模型结构,而是数据质量。传感器采集到的原始信号是物理世界与电路噪声、环境干扰、量化误差的混合体。若将未经处理的原始数据直接送入端侧模型,轻则导致模型收敛缓慢、泛化能力差,重则使分类器学习到噪声的“伪特征”,在真实部署时完全失效。本章围绕去噪、滤波、归一化与滑动窗口分割四项核心技术展开,帮助读者建立一套适用于资源受限设备的传感器数据预处理流水线。

TinyML 传感器数据预处理流水线 原始传感器数据 IMU / 麦克风 / 环境 含噪声、基线漂移 去噪与滤波 • 移动平均 / 中值滤波 • 低通 / 高通 / 带通 • 截止频率选择 • 零相位滤波(离线) 归一化 • Min-Max 缩放 • Z-score 标准化 • 鲁棒缩放(中位数) • 标定参数固化 滑动窗口分割 • 窗口长度 (W) • 步长 (S) • 重叠率 = 1 - S/W • 标签对齐策略 输出:标准化、去噪、带标签的固定长度样本序列 可直接输入 TFLite / CMSIS-NN / 自研推理引擎 关键参数速查 滤波类型 适用场景 典型参数 注意事项 低通滤波 去除高频噪声 截止频率 5-50 Hz 避免过度平滑 高通滤波 去除基线漂移 截止频率 0.1-1 Hz 注意相位失真 滑动窗口 样本分割 W=2s, S=0.5s 重叠率 50-75%

4.1 去噪:从时域到频域的噪声识别

传感器噪声来源多样:热噪声(白噪声)、电源纹波(50Hz/100Hz 及其谐波)、机械振动耦合、量化噪声(ADC 分辨率不足)以及环境电磁干扰。在 TinyML 场景下,去噪的目标不是“完美还原真实信号”,而是在计算资源与延迟约束内,最大化信噪比(SNR),使后续特征提取更稳定。

核心原理:噪声通常表现为高频成分或与信号频带重叠的干扰。去噪的本质是设计一个滤波器,让目标信号频带通过,抑制非目标频带。对于加速度计、陀螺仪等惯性传感器,人体活动信号主要集中在 0.5Hz~20Hz;对于麦克风,语音信号在 300Hz~3.4kHz;对于环境温湿度传感器,变化极为缓慢,低于 1Hz。因此,去噪策略必须依据信号频带与噪声频带的相对关系选择。

判断方法:在开始滤波之前,建议先对原始数据做一次快速频谱分析(如 FFT),观察信号与噪声的频带分布。若噪声均匀分布在整个频带(白噪声),低通滤波即可;若存在明显的 50Hz 工频尖峰,则需要陷波器(Notch Filter);若基线缓慢漂移(如温度变化导致传感器零点偏移),则需高通滤波。

经验提醒:在资源受限的 MCU 上,避免使用高阶 FIR 滤波器(如 128 阶)。一阶 IIR 滤波器(如 y[n] = α·x[n] + (1-α)·y[n-1])仅需 2 次乘法和 1 次加法,即可实现有效的低通滤波,适合 Cortex-M0 级别的内核。α 值由截止频率与采样率决定:α = 1 / (1 + 2π·fc/fs)。

4.2 滤波技术:低通、高通与带通的选择策略

滤波是数据预处理的核心环节。在 TinyML 中,滤波器的选择直接影响模型输入的质量与计算开销。

滤波器类型 传递函数(一阶) 典型应用 MCU 计算量 注意事项
低通 (LPF) y[n] = y[n-1] + α·(x[n] - y[n-1]) 加速度计去高频振动 2 乘 1 加 截止频率需高于信号最高频率
高通 (HPF) y[n] = α·(y[n-1] + x[n] - x[n-1]) 去除呼吸信号中的基线漂移 2 乘 2 加 会衰减低频有效成分
带通 (BPF) LPF + HPF 级联 心电信号 (0.5-40Hz) 4 乘 3 加 需分别设定上下截止频率
中值滤波 排序取中值(窗口 N=3/5) 去除脉冲噪声(尖峰) N·log(N) 比较 窗口不宜过大,否则延迟明显

操作流程:首先确定采样率 fs(如 IMU 常用 50Hz~200Hz)。然后根据信号物理特性确定截止频率 fc。例如,人体步态识别中,步行频率约 1~2.5Hz,跑步约 2.5~4Hz,因此低通截止频率设为 10Hz 即可保留主要能量并滤除高频抖动。若使用 IIR 滤波,需注意相位失真问题——IIR 滤波器在截止频率附近会产生非线性相位偏移,可能导致波形畸变。对于需要保持波形形态的应用(如心电 R 波检测),建议使用零相位滤波(如 scipy.signal.filtfilt)在离线阶段处理,或使用 FIR 滤波器保证线性相位。

风险提示:过度滤波会抹掉有效特征。例如,在跌倒检测中,跌倒瞬间的冲击信号包含丰富的高频成分(>20Hz),若低通截止频率设置过低(如 5Hz),会严重衰减冲击峰值,导致漏检。建议在滤波前后对比关键特征(如峰值、方差、过零率)的变化,确保信号形态未被破坏。

4.3 归一化:让特征尺度一致

不同传感器通道的量纲和数值范围差异巨大。例如,加速度计输出范围为 ±2g(约 ±19.6 m/s²),而陀螺仪输出范围为 ±250°/s,磁力计则可能输出 ±4800mGauss。若直接拼接这些特征输入模型,数值范围大的通道会主导梯度更新,导致模型偏向某些传感器,降低泛化能力。

核心原理:归一化将各通道数据映射到统一尺度(如 [0,1] 或均值为 0、方差为 1),使模型能够平等对待每个特征维度。两种最常用的方法:

  • Min-Max 归一化:x' = (x - x_min) / (x_max - x_min),将数据线性映射到 [0,1]。适用于已知数据边界且无极端离群值的场景。缺点是对离群值敏感,一个异常尖峰会压缩正常数据的分布范围。
  • Z-score 标准化:x' = (x - μ) / σ,使数据服从均值为 0、标准差为 1 的分布。适用于数据近似高斯分布或存在离群值的场景。在 TinyML 中,μ 和 σ 需在训练集上预先计算并固化为常量,部署时直接代入。

操作流程:在训练集上计算每个通道的 min/max 或 μ/σ,保存为 C 语言头文件中的常量数组。推理时,对每个新样本执行相同的线性变换。注意:归一化参数只能从训练集统计,不能使用测试集或实时数据的统计值,否则会引入数据泄漏(Data Leakage),导致评估结果过于乐观。

实战场景:某智能手环项目使用 3 轴加速度计 + 3 轴陀螺仪进行活动识别。原始数据中,加速度计 Z 轴范围 [-1.2g, 1.8g],陀螺仪 Z 轴范围 [-320°/s, 280°/s]。若不归一化,模型权重更新主要由陀螺仪主导。采用 Z-score 标准化后,各通道均值为 0、方差为 1,模型收敛速度提升约 40%,准确率从 82.3% 提升至 87.1%。关键操作:在 PC 端用 Python 计算 6 个通道的 μ 和 σ,生成 norm_params.h 头文件,MCU 端仅需 6 次减法、6 次乘法即可完成归一化。

4.4 滑动窗口分割:构建时间序列样本

传感器数据是连续的时间序列流,而分类模型通常需要固定长度的输入。滑动窗口分割将无限长的数据流切分为有限长度的样本片段,每个片段对应一个分类标签。

核心参数:窗口长度(W)与步长(S)。窗口长度决定了单个样本包含的时间跨度,需覆盖至少 1~2 个完整活动周期。例如,步态周期约 1~1.2 秒,窗口长度设为 2 秒(100Hz 采样率下为 200 个采样点)可确保包含完整的步态周期。步长决定了相邻窗口的重叠率(Overlap = 1 - S/W)。重叠率越高,样本数量越多,模型训练更充分,但推理时计算量也越大。

标签对齐策略:窗口的标签通常取窗口中心时刻的标签(适用于实时分类),或取窗口内多数投票标签(适用于离线标注)。对于动作切换的过渡段,建议丢弃或单独标注,避免标签噪声。

// 滑动窗口分割伪代码(C 语言风格)
#define WINDOW_SIZE 200   // 2秒 @ 100Hz
#define STEP_SIZE 50      // 0.5秒步长,重叠率 75%
#define NUM_CHANNELS 6    // 3轴加速度 + 3轴陀螺仪

float buffer[WINDOW_SIZE][NUM_CHANNELS];
int buffer_index = 0;

void process_sample(float* sample) {
    // 将新样本写入环形缓冲区
    memcpy(buffer[buffer_index], sample, NUM_CHANNELS * sizeof(float));
    buffer_index = (buffer_index + 1) % WINDOW_SIZE;
    
    // 每积累 STEP_SIZE 个新样本,触发一次推理
    static int step_counter = 0;
    if (++step_counter >= STEP_SIZE) {
        step_counter = 0;
        // 从缓冲区按顺序提取窗口数据(注意环形缓冲区的索引映射)
        float window[WINDOW_SIZE][NUM_CHANNELS];
        for (int i = 0; i < WINDOW_SIZE; i++) {
            int idx = (buffer_index + i) % WINDOW_SIZE;
            memcpy(window[i], buffer[idx], NUM_CHANNELS * sizeof(float));
        }
        // 调用模型推理函数
        run_inference(window);
    }
}

案例拆解:某工业设备预测性维护项目,使用振动传感器(采样率 20kHz)监测轴承故障。原始数据包含正常、内圈故障、外圈故障、滚动体故障四种状态。初始方案采用 W=1024 点(约 51ms),S=512 点,重叠率 50%。模型准确率仅 91.2%。分析发现:轴承故障特征频率(BPFO/BPFI)在 100~400Hz 之间,1024 点窗口仅包含 2~5 个故障周期,特征不充分。调整后:W=4096 点(约 205ms),S=512 点,重叠率 87.5%。准确率提升至 97.8%。但代价是推理延迟从 25ms 增至 100ms,且内存占用从 4KB 增至 16KB。最终通过降低采样率至 10kHz(W=2048 点)平衡了精度与资源。

常见误区:① 窗口越长不一定越好——过长的窗口会引入无关数据,且增加延迟;② 重叠率过高(>90%)会导致训练样本高度相关,造成过拟合;③ 忘记在窗口分割前进行滤波——若先分割再滤波,窗口边界会产生截断效应,导致滤波后的首尾数据失真;④ 归一化参数在窗口内重复计算——应在整个数据集上一次性计算,而非每个窗口单独计算。

4.5 预处理流水线的工程落地与验证

在 MCU 上实现预处理流水线时,需考虑内存、延迟与功耗的平衡。建议采用“分阶段处理”架构:传感器中断服务程序(ISR)中仅做数据搬运和轻量级去噪(如移动平均),主循环中完成归一化与窗口分割,推理任务由独立任务或硬件加速器(如 CMSIS-DSP)执行。

验证方法:在 PC 端使用 Python 完成预处理后,将处理后的数据导出为 C 数组,在 MCU 上运行相同的预处理代码,对比输出是否一致(允许浮点误差 < 1e-5)。同时,记录每个阶段的耗时与内存峰值,确保满足实时性要求。

阶段 内存占用(典型值) 延迟(@100Hz 采样) 优化建议
原始数据缓冲 W × C × 4 字节 0ms(DMA 搬运) 使用环形缓冲区,避免 memcpy
滤波 滤波器状态变量(每通道 2-4 字节) < 0.1ms 定点数替代浮点,使用 CMSIS-DSP
归一化 参数表(每通道 8 字节) < 0.05ms 预计算 1/σ,用乘法替代除法
窗口分割 窗口数据副本(W × C × 4 字节) < 0.2ms 使用指针索引,避免数据拷贝

总结复盘:数据预处理不是“一刀切”的固定流程,而是需要根据传感器类型、目标信号频带、MCU 资源约束进行定制。核心原则是:先分析信号与噪声的频带分布,再选择滤波策略;先确定窗口长度覆盖完整活动周期,再调整步长平衡样本量与延迟;归一化参数必须在训练集上统计并固化。建议在项目初期建立一套自动化的预处理评估脚本,对比不同参数组合下的模型准确率、内存占用与推理延迟,以数据驱动的方式确定最优配置。