数据预处理:野点剔除、去趋势、滤波与重采样实战技巧
数据预处理,说白了就是给原始数据“洗澡”。
我做了十几年控制系统,见过太多因为数据没洗干净就往上怼辨识算法,结果模型一塌糊涂的案例。你想想看,传感器采集回来的信号,里面什么都有——噪声、漂移、偶尔的跳变,甚至还有工频干扰。直接拿这些脏数据去辨识,模型能准才怪。
这一章,我就把数据预处理的四个核心步骤掰开揉碎了讲。全是实战经验,没有废话。
1. 野点剔除:先把明显离谱的点干掉
野点,也叫离群点。就是那些明显不符合物理规律的数据点。
我在项目里遇到过最夸张的一次,是某温度传感器突然跳到了2000°C,而实际工艺温度才150°C。后来查出来是现场接线松动,瞬间接触不良导致的。这种数据如果不剔除,辨识出来的模型会直接跑偏。
1.1 常用的野点检测方法
- 3σ准则:假设数据服从正态分布,超出均值±3倍标准差的数据视为野点。简单粗暴,适合大多数场景。
- 中位数偏差法:比3σ更鲁棒,适合数据本身就有波动的情况。我习惯用这个方法处理振动信号。
- 局部异常因子:基于密度的检测方法,适合数据分布不均匀的情况。不过计算量稍大,实时系统慎用。
实战经验:我个人习惯先用3σ粗筛一遍,再用中位数偏差法精筛。两层过滤下来,基本能干掉99%的野点。
1.2 Python代码示例
import numpy as np
from scipy import stats
def remove_outliers_3sigma(data, threshold=3):
"""基于3σ准则剔除野点"""
mean = np.mean(data)
std = np.std(data)
z_scores = np.abs((data - mean) / std)
return data[z_scores < threshold]
def remove_outliers_mad(data, threshold=3.5):
"""基于中位数偏差法剔除野点"""
median = np.median(data)
mad = np.median(np.abs(data - median))
modified_z_scores = 0.6745 * (data - median) / mad
return data[np.abs(modified_z_scores) < threshold]
注意:千万不要在野点剔除后直接补零或补均值。我曾经吃过这个亏——补零会导致频谱出现虚假频率成分,补均值会平滑掉真实变化。正确的做法是用前后有效数据的线性插值或样条插值来填补。
2. 去趋势:把漂移从数据里剥离出来
趋势项,就是数据中缓慢变化的成分。比如传感器温漂、放大器零点漂移、环境温度变化等。
为什么要去趋势?因为大多数辨识算法假设数据是平稳的。如果数据有明显的上升或下降趋势,辨识出来的模型会包含这个趋势,而不是真实的动态特性。
2.1 去趋势的两种主流方法
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 多项式拟合去趋势 | 趋势比较平滑、缓慢变化 | 简单直观,容易实现 | 阶次选择不当会过拟合 |
| 高通滤波去趋势 | 趋势频率已知或可估计 | 不依赖趋势形状 | 可能滤掉低频有用信号 |
我个人更推荐多项式拟合去趋势。原因很简单——你通常能判断趋势是线性的还是二次的。比如温度漂移,多数情况下是线性或缓慢的二次曲线。
2.2 去趋势的坑
避坑指南:我曾经在处理一个压力信号时,用了5阶多项式去拟合趋势。结果拟合出来的曲线把真实的过程波动也吃掉了。后来改成2阶多项式,效果反而更好。记住:去趋势的目的是去掉缓慢漂移,不是拟合数据本身。阶次越低越好,一般1-3阶就够了。
3. 滤波:把噪声和有用信号分开
滤波是数据预处理的重头戏。说白了,就是让有用的频率成分通过,把没用的滤掉。
但这里有个核心矛盾:滤波太狠,会丢失高频动态信息;滤波太轻,噪声又会影响辨识精度。怎么平衡?我总结了三条原则:
- 先分析频谱:用FFT看看噪声集中在哪个频段,有用信号在哪个频段。
- 选择合适滤波器:噪声频率高于信号,用低通滤波;噪声频率低于信号,用高通滤波;两者混叠,用带通或带阻。
- 注意相位延迟:因果滤波器会引入相位延迟,对实时系统影响很大。离线处理时可以用零相位滤波(filtfilt)。
3.1 常用滤波器对比
| 滤波器类型 | 特点 | 推荐场景 |
|---|---|---|
| 移动平均滤波 | 简单、计算量小、有延迟 | 实时系统、嵌入式平台 |
| 巴特沃斯低通滤波 | 通带平坦、过渡带较宽 | 离线分析、精度要求高 |
| 切比雪夫滤波 | 过渡带陡峭、通带有纹波 | 需要锐截止的场景 |
| 卡尔曼滤波 | 自适应、可处理非平稳噪声 | 动态系统、有模型先验 |
3.2 零相位滤波实战
from scipy.signal import butter, filtfilt
def zero_phase_lowpass(data, cutoff, fs, order=4):
"""零相位低通滤波,不引入相位延迟"""
nyquist = 0.5 * fs
normal_cutoff = cutoff / nyquist
b, a = butter(order, normal_cutoff, btype='low', analog=False)
# 关键:用filtfilt代替lfilter
filtered_data = filtfilt(b, a, data)
return filtered_data
实战经验:我处理伺服电机位置信号时,一直用filtfilt做零相位滤波。效果比普通滤波好太多——既滤掉了高频抖动,又保留了位置变化的真实时序。不过要注意,filtfilt不能用于实时系统,因为它需要整段数据。
4. 重采样:统一时间基准
重采样,就是把不同采样率的数据统一到同一个时间基准上。
为什么需要重采样?因为实际系统中,不同传感器的采样率往往不一样。比如温度传感器采样率1Hz,压力传感器采样率10Hz,位置传感器采样率100Hz。如果不做重采样,多变量辨识根本没法做。
4.1 重采样的两种策略
- 降采样:把高采样率的数据降到低采样率。优点是计算量小,缺点是会丢失高频信息。降采样前一定要先做抗混叠滤波。
- 升采样:把低采样率的数据插值到高采样率。优点是保留所有信息,缺点是插值会引入人为误差。
我个人习惯的做法是:把所有数据统一到最低采样率。虽然会丢失一些高频信息,但至少保证了数据的真实性。升采样插出来的数据,说白了是“猜”出来的,用在辨识里容易出问题。
4.2 重采样代码示例
import numpy as np
from scipy import signal
def resample_to_target(data, original_fs, target_fs):
"""将数据重采样到目标采样率"""
# 计算重采样比例
resample_ratio = target_fs / original_fs
# 重采样
resampled_data = signal.resample(data, int(len(data) * resample_ratio))
return resampled_data
注意:重采样前一定要检查数据中是否有NaN或Inf。我遇到过最坑的一次,是某个传感器偶尔输出NaN,重采样时直接报错。排查了半天才发现是数据质量问题。所以,重采样前先做数据清洗,这是铁律。
5. 知识体系总览
下面这张图,是我自己总结的数据预处理全流程。每次做项目前,我都会对照这张图走一遍,确保没有遗漏。
这张图把整个流程串起来了。你想想看,从原始数据到干净可用的数据,每一步都有讲究。跳过任何一步,都可能让后面的辨识工作白费。
我的习惯:每次拿到新数据,我都会先画一张这样的流程图,贴在工位上。然后按步骤走一遍,每完成一步就在图上打个勾。这样既不会漏步骤,也方便复盘。
数据预处理这件事,说白了就是“磨刀不误砍柴工”。你花在预处理上的时间,会在后面的模型辨识和精度提升中加倍回报给你。别嫌麻烦,这是每个控制工程师的必修课。