第四节:信号预处理——去趋势、去均值、归一化与标准化
各位同学,咱们接着聊。上一节讲了噪声怎么滤,这一节咱们聊聊信号预处理里几个基础但极其重要的操作:去趋势、去均值、归一化、标准化。
说实话,这几个概念在教科书里往往被一笔带过。但我做了这么多年胎压算法,可以负责任地告诉你——预处理做不好,后面再牛的模型也是白搭。就像盖房子,地基没打牢,装修再漂亮也经不起风吹雨打。
4.1 为什么要做预处理?
先问大家一个问题:你拿到一段胎压原始数据,第一件事是什么?
我个人的习惯是——先看一眼波形。不是看细节,而是看整体趋势。比如车辆从冷车启动到热车,胎压会缓慢上升。这个上升趋势,其实是温度变化引起的,跟轮胎漏不漏气没关系。
如果你不把这个趋势去掉,算法就会误判——哎?压力在上升?是不是漏气了?其实不是。这就是去趋势的意义。
再说均值。传感器输出的原始信号,往往带有一个直流偏置。这个偏置可能是传感器本身的零漂,也可能是ADC的参考电压偏差。你不把它去掉,后续计算出来的幅值、能量特征全是错的。
至于归一化和标准化,说白了就是让不同量纲的数据能放在一起比较。胎压单位是kPa,温度单位是℃,车速单位是km/h。你想想看,这三个数值直接扔进模型,谁占主导?肯定是数值大的那个。但实际物理意义呢?不一定。
4.2 去趋势(Detrend)
去趋势,就是去掉信号中缓慢变化的成分。在胎压数据里,最常见的趋势就是温度漂移引起的压力缓慢变化。
我记得有一次做路试,采集了一组数据,用肉眼一看,压力曲线整体向上飘。当时年轻,差点就报故障了。后来仔细一查,是那天太阳暴晒,轮胎温度从20℃升到了50℃,压力自然跟着涨。去掉这个线性趋势后,信号就正常了。
去趋势的方法很简单——线性拟合。把信号拟合成一条直线,然后减去这条直线。
import numpy as np
import matplotlib.pyplot as plt
def detrend_signal(signal):
"""
去趋势:减去线性拟合的直线
"""
n = len(signal)
x = np.arange(n)
# 线性拟合:y = kx + b
k, b = np.polyfit(x, signal, 1)
trend = k * x + b
detrended = signal - trend
return detrended, trend
# 示例:模拟带趋势的胎压信号
t = np.linspace(0, 10, 1000)
trend = 0.05 * t # 缓慢上升趋势
noise = np.random.normal(0, 0.1, 1000)
signal = 100 + trend + noise
clean, trend_line = detrend_signal(signal)
print(f"原始信号均值: {np.mean(signal):.3f}")
print(f"去趋势后均值: {np.mean(clean):.3f}")
4.3 去均值(Remove Mean)
去均值,也叫零均值化。就是把整个信号减去它的平均值,让信号围绕0波动。
为什么要这么做?因为很多信号处理算法,比如FFT、自相关函数,都假设信号是零均值的。如果信号有直流分量,FFT的0Hz处会有一个很大的峰值,把其他频率成分都淹没了。
我曾经遇到过一个问题:用FFT分析胎压信号的频谱,发现低频段能量特别大。排查了半天,原来是传感器零漂导致的直流分量没去掉。去掉均值之后,频谱就干净了。
def remove_mean(signal):
"""
去均值:减去信号的平均值
"""
mean_val = np.mean(signal)
centered = signal - mean_val
return centered, mean_val
# 示例
signal = np.array([100.2, 100.5, 99.8, 100.1, 100.3])
centered, mean_val = remove_mean(signal)
print(f"原始信号: {signal}")
print(f"均值: {mean_val:.2f}")
print(f"去均值后: {centered}")
4.4 归一化(Normalization)
归一化,就是把数据映射到[0,1]或[-1,1]区间。常用的方法有两种:
- Min-Max归一化: x' = (x - min) / (max - min),结果在[0,1]之间
- 绝对值归一化: x' = x / max(|x|),结果在[-1,1]之间
归一化在胎压算法里有什么用?我举两个例子:
第一,多传感器融合。胎压传感器、温度传感器、加速度传感器,量纲完全不同。归一化之后,它们才能放在同一个模型里处理。
第二,神经网络输入。如果你用深度学习做胎压异常检测,输入层的数据必须归一化。不然网络训练的时候,数值大的特征会主导梯度更新,收敛速度慢得让人抓狂。
def min_max_normalize(signal):
"""
Min-Max归一化到[0,1]
"""
min_val = np.min(signal)
max_val = np.max(signal)
if max_val == min_val:
return np.zeros_like(signal)
normalized = (signal - min_val) / (max_val - min_val)
return normalized
# 示例
pressure = np.array([210, 215, 208, 220, 212])
temp = np.array([25, 28, 22, 30, 26])
p_norm = min_max_normalize(pressure)
t_norm = min_max_normalize(temp)
print("归一化后的胎压:", p_norm)
print("归一化后的温度:", t_norm)
# 现在可以放在一起比较了
4.5 标准化(Standardization)
标准化和归一化经常被混为一谈。其实它们不一样。
标准化是把数据变成均值为0、标准差为1的分布。公式是:x' = (x - μ) / σ,其中μ是均值,σ是标准差。
标准化不改变数据的分布形状,只是把数据"拉"到标准正态分布附近。它比归一化更鲁棒——因为归一化受异常值影响很大,一个离群点就能把整个区间拉偏。
我记得有一次做胎压异常检测,数据里有一个传感器故障导致的异常值(压力飙到了500kPa)。如果用Min-Max归一化,正常数据全被压缩到0.1以下,模型直接废了。换成标准化之后,异常值虽然还在,但正常数据的分布没被破坏。
def standardize(signal):
"""
标准化:均值为0,标准差为1
"""
mean = np.mean(signal)
std = np.std(signal)
if std == 0:
return np.zeros_like(signal)
standardized = (signal - mean) / std
return standardized
# 示例:对比归一化和标准化对异常值的敏感度
signal_with_outlier = np.array([100, 102, 101, 500, 99, 103])
norm = min_max_normalize(signal_with_outlier)
std = standardize(signal_with_outlier)
print("原始信号:", signal_with_outlier)
print("归一化后:", np.round(norm, 3))
print("标准化后:", np.round(std, 3))
| 方法 | 公式 | 输出范围 | 对异常值敏感度 | 适用场景 |
|---|---|---|---|---|
| Min-Max归一化 | (x-min)/(max-min) | [0,1] | 高 | 已知边界、图像处理 |
| 标准化 | (x-μ)/σ | 无固定范围 | 低 | 异常检测、机器学习 |
| 去均值 | x-μ | 围绕0 | 中 | 频谱分析、滤波 |
| 去趋势 | x - (kx+b) | 无固定范围 | 低 | 消除漂移、温度补偿 |
4.6 实际应用中的顺序问题
这几个操作,先做哪个后做哪个?我个人的建议是:
- 先去趋势——消除缓慢漂移
- 再去均值——消除直流偏置
- 最后归一化或标准化——根据后续算法选择
为什么先去趋势?因为趋势会影响均值的计算。如果信号有上升趋势,均值会被拉高,去均值之后信号依然有趋势残留。反过来,先去趋势再去均值,结果才干净。
嗯,这里要注意:标准化和归一化是互斥的,选一个就行。不要两个都做,那是画蛇添足。
4.7 小结
好了,这一节的内容就这些。去趋势、去均值、归一化、标准化,这四个操作是信号预处理的基石。你想想看,如果连这些基础都做不好,后面的特征提取、模式识别、异常检测,怎么可能靠谱?
下一节咱们聊聊特征提取——怎么从预处理后的信号里,挖出真正有用的信息。到时候你会发现,预处理做得越干净,特征提取就越轻松。
今天就到这里。有问题随时交流。