数据预处理与特征工程:从原始信号到可用特征
大家好,我是你们的部署工程师。今天咱们聊聊数据预处理和特征工程。说实话,这部分工作占了我整个项目周期的60%以上。很多人觉得模型才是核心,但我告诉你——数据搞不好,模型再牛也白搭。
我在现场部署时遇到过太多次了:传感器数据哗哗地来,但直接扔进模型,结果一塌糊涂。为什么?因为原始数据太“脏”了。今天我就把这几年的实战经验掰开揉碎讲给你听。
数据清洗:去噪与去异常值
传感器数据,说白了就是一堆带噪声的测量值。温度传感器可能受电磁干扰,加速度计可能因为震动产生毛刺。这些噪声不处理,模型学到的就是噪声模式,而不是真实物理规律。
去噪:滑动窗口平均
我最常用的去噪方法是滑动窗口平均。简单、高效、在嵌入式上跑得飞快。
// C语言实现:滑动窗口平均去噪
#define WINDOW_SIZE 5
float buffer[WINDOW_SIZE] = {0};
int index = 0;
float denoise(float new_sample) {
buffer[index] = new_sample;
index = (index + 1) % WINDOW_SIZE;
float sum = 0;
for (int i = 0; i < WINDOW_SIZE; i++) {
sum += buffer[i];
}
return sum / WINDOW_SIZE;
}
嗯,这里要注意:窗口大小选多少?我一般选3-7。太小去噪效果差,太大信号延迟大。我在一个振动监测项目里试过,窗口选5效果最好,既滤掉了高频噪声,又保留了冲击信号的上升沿。
去异常值:3σ原则
异常值比噪声更可怕。噪声是随机的,异常值可能是传感器故障、通信丢包导致的。我曾经遇到过,一个压力传感器突然跳变到满量程,差点让整个控制系统误动作。
我的做法是:先算均值和标准差,然后剔除超出3σ范围的点。
// 3σ异常值检测
int is_outlier(float value, float mean, float std) {
float z_score = (value - mean) / std;
return (z_score > 3.0f || z_score < -3.0f);
}
时域特征:均值、方差
清洗完数据,接下来就是提取特征。时域特征是最直观的,说白了就是看信号在时间轴上的统计特性。
均值:信号的直流分量
均值代表信号的“中心位置”。比如加速度计静止时,均值就是重力加速度的分量。我习惯用递推公式算均值,省内存。
float mean = 0;
int count = 0;
float update_mean(float x) {
count++;
mean = mean + (x - mean) / count;
return mean;
}
方差:信号的波动程度
方差告诉你信号抖不抖。在故障诊断里,方差突然变大往往意味着设备出问题了。我做过一个电机轴承故障检测,正常运行时方差很小,一旦轴承磨损,方差直接翻倍。
// 递推计算方差(Welford算法)
float mean = 0, M2 = 0;
int count = 0;
void update_variance(float x) {
count++;
float delta = x - mean;
mean += delta / count;
float delta2 = x - mean;
M2 += delta * delta2;
}
float get_variance() {
return M2 / (count - 1);
}
频域特征:FFT
时域特征只能看到“信号有多大”,看不到“信号由哪些频率组成”。而很多故障特征恰恰藏在频率里。比如齿轮箱的啮合频率、电机的转频,这些在时域里根本看不出来。
FFT实战要点
FFT(快速傅里叶变换)是把时域信号变到频域的工具。但现场部署时,有几个坑你必须知道。
| 参数 | 说明 | 我的建议 |
|---|---|---|
| 采样率 | 决定最高分析频率 | 至少是目标频率的2.56倍 |
| FFT点数 | 决定频率分辨率 | 选2的幂次,如256、512 |
| 窗函数 | 减少频谱泄漏 | 汉宁窗最常用 |
// 简易FFT实现(基2-FFT,仅作演示)
void fft(float* real, float* imag, int n) {
// 位反转排序
for (int i = 1, j = 0; i < n; i++) {
int bit = n >> 1;
for (; j & bit; bit >>= 1)
j ^= bit;
j ^= bit;
if (i < j) {
// 交换real[i]和real[j], imag[i]和imag[j]
}
}
// 蝶形运算
for (int len = 2; len <= n; len <<= 1) {
float w_real = 1.0f, w_imag = 0.0f;
float theta = -2 * M_PI / len;
// ... 完整实现略
}
}
数据归一化与标准化
最后一步,也是很多人容易忽略的一步。不同传感器量纲不一样——温度可能是0-100℃,压力可能是0-10MPa。如果不归一化,模型会认为数值大的特征更重要,这显然是错的。
归一化:缩放到[0,1]
说白了就是把数据压到0和1之间。公式很简单:x' = (x - min) / (max - min)。
// 在线归一化(更新min/max)
float min_val = 1e10, max_val = -1e10;
float normalize(float x) {
if (x < min_val) min_val = x;
if (x > max_val) max_val = x;
return (x - min_val) / (max_val - min_val + 1e-8);
}
标准化:均值为0,方差为1
标准化公式:x' = (x - mean) / std。它不保证数据在某个区间内,但能消除量纲影响。
我个人习惯:如果后续用SVM、神经网络这类对尺度敏感模型,用标准化;如果用决策树、随机森林,其实归一化标准化都行,树模型不关心尺度。
// 标准化(使用预计算的mean和std)
float standardize(float x, float mean, float std) {
return (x - mean) / (std + 1e-8);
}
总结一下
数据预处理这条流水线,我建议你这样走:
- 原始数据 → 滑动窗口去噪 → 3σ去异常值
- 干净数据 → 提取时域特征(均值、方差)
- 时域特征 → FFT提取频域特征(幅值谱、功率谱)
- 所有特征 → 归一化或标准化 → 送入模型
你想想看,这一套流程走下来,数据质量就有了保障。我在现场部署时,这套流程帮我解决过振动分析、温度预测、压力监测等各种问题。说白了,数据预处理做得好,模型部署就成功了一半。
下一章咱们聊聊模型压缩和量化,那又是另一番天地了。