特征工程基础:让传感器数据真正“说话”
说实话,很多做剩余寿命预测的朋友,一上来就急着调模型。我刚开始也是这样,觉得特征工程嘛,不就是标准化一下、算几个统计量?结果呢?模型效果差得离谱。后来我才明白——特征工程做不好,再牛的算法也是白搭。
这一章,咱们就聊聊传感器数据的“预处理三件套”:标准化/归一化、滑动窗口特征提取、特征相关性分析。这些都是我实际项目中反复踩坑、又反复验证过的经验。
1. 标准化 vs 归一化:别搞混了
先问个问题:你拿到一组传感器数据,温度是80-120℃,振动是0.1-5.0 m/s²,压力是0.8-1.2 MPa。这三个量纲完全不同,直接扔进模型会怎样?
嗯,模型会“偏心”——它会更关注数值大的特征,比如温度。这显然不合理。
标准化(Z-score)和归一化(Min-Max)就是来解决这个问题的。我个人的习惯是:
- 归一化:把数据缩放到[0,1]区间。适合数据分布比较均匀、没有极端值的情况。公式很简单:
X_norm = (X - X_min) / (X_max - X_min) - 标准化:把数据变成均值为0、标准差为1的分布。适合数据有异常值、或者后续要用PCA等算法时。公式:
X_std = (X - μ) / σ
我的经验之谈:在工业设备健康监测中,传感器数据经常会有突发性尖峰(比如瞬间冲击)。这时候用归一化,最大值会被这个尖峰“拉偏”,导致正常数据被压缩得很厉害。我建议优先用标准化,它对异常值更鲁棒。
来看一段Python代码,很实用:
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np
# 模拟传感器数据:温度、振动、压力
sensor_data = np.array([[85.0, 0.5, 0.9],
[92.0, 1.2, 1.0],
[78.0, 0.3, 0.8],
[105.0, 4.8, 1.1]]) # 注意振动有个尖峰
# 标准化
scaler_std = StandardScaler()
data_std = scaler_std.fit_transform(sensor_data)
# 归一化
scaler_mm = MinMaxScaler()
data_mm = scaler_mm.fit_transform(sensor_data)
print("标准化后:\n", data_std)
print("归一化后:\n", data_mm)
运行一下你会发现,那个4.8的振动尖峰在归一化后,把其他振动值都压到了0附近。而标准化后,分布就合理多了。
小技巧:如果你不确定用哪个,可以两个都试试,然后看哪个让模型收敛更快、效果更好。工业场景下,我80%的情况都用标准化。
2. 滑动窗口特征提取:从“点”到“段”的跃迁
单个时间点的传感器读数,信息量太有限了。你想想看,一个振动值突然升高,可能是故障前兆,也可能是传感器被碰了一下。怎么办?
我的做法是:用滑动窗口把连续一段时间的特征“打包”起来。这样就能捕捉到趋势和变化。
常用的窗口特征有:
- 均值:反映这段时间的“平均水平”
- 方差:反映数据的“波动程度”——波动越大,往往意味着异常
- 斜率:反映数据的“变化趋势”——是上升还是下降?
我在项目中遇到过这样一个案例:一个轴承的振动信号,均值一直很平稳,但方差在缓慢增大。如果只看均值,你会觉得设备没问题。但方差暴露了真相——轴承正在劣化。这就是窗口特征的价值。
代码实现也不复杂:
import numpy as np
import pandas as pd
def extract_window_features(data, window_size=10):
"""
滑动窗口特征提取
data: 一维传感器数据
window_size: 窗口大小
"""
features = []
for i in range(len(data) - window_size + 1):
window = data[i:i+window_size]
# 均值
mean_val = np.mean(window)
# 方差
var_val = np.var(window)
# 斜率(用线性回归的系数近似)
x = np.arange(window_size)
slope = np.polyfit(x, window, 1)[0]
features.append([mean_val, var_val, slope])
return np.array(features)
# 模拟一段振动数据
vibration = np.sin(np.linspace(0, 10, 100)) + np.random.normal(0, 0.1, 100)
# 在第50个点后加入劣化趋势
vibration[50:] += 0.02 * np.arange(50)
window_feats = extract_window_features(vibration, window_size=10)
print("窗口特征形状:", window_feats.shape)
print("前5个窗口的特征:\n", window_feats[:5])
注意:窗口大小怎么选?太小了,特征不稳定;太大了,会丢失局部变化。我一般从设备物理特性出发——比如轴承的旋转周期是0.1秒,那窗口至少覆盖2-3个周期。实在拿不准,就多试几个窗口大小,用交叉验证来选。
3. 特征相关性分析:别让“冗余特征”拖后腿
特征提取完了,你可能得到几十个甚至上百个特征。但问题来了——很多特征之间是高度相关的。比如温度和热膨胀系数,几乎线性相关。把这些都扔进模型,不仅增加计算量,还会导致过拟合。
我常用的方法是皮尔逊相关系数。它的值在-1到1之间:
- 接近1:强正相关(一个增大,另一个也增大)
- 接近-1:强负相关(一个增大,另一个减小)
- 接近0:基本不相关
来看一个实际例子:
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
# 模拟特征数据
np.random.seed(42)
n_samples = 100
features = pd.DataFrame({
'温度': np.random.normal(85, 5, n_samples),
'振动均值': np.random.normal(1.2, 0.3, n_samples),
'振动方差': np.random.normal(0.5, 0.1, n_samples),
'压力': np.random.normal(1.0, 0.1, n_samples),
'转速': np.random.normal(3000, 100, n_samples),
})
# 人为制造相关性:温度和压力正相关
features['压力'] = features['温度'] * 0.01 + np.random.normal(0, 0.05, n_samples)
# 计算相关系数矩阵
corr_matrix = features.corr()
print("相关系数矩阵:\n", corr_matrix)
# 找出高度相关的特征对(|相关系数| > 0.8)
high_corr_pairs = []
for i in range(len(corr_matrix.columns)):
for j in range(i+1, len(corr_matrix.columns)):
if abs(corr_matrix.iloc[i, j]) > 0.8:
high_corr_pairs.append((corr_matrix.columns[i],
corr_matrix.columns[j],
corr_matrix.iloc[i, j]))
print("\n高度相关的特征对:")
for pair in high_corr_pairs:
print(f"{pair[0]} 与 {pair[1]}: {pair[2]:.3f}")
运行后你会发现,温度和压力的相关系数可能高达0.9以上。这时候,我建议只保留其中一个,或者用PCA降维把它们合并。
避坑指南:我曾经犯过一个错误——把所有相关性大于0.8的特征都删掉了。结果模型效果反而变差了。为什么?因为有些特征虽然相关,但它们各自携带了不同的“信息维度”。比如温度和压力相关,但温度变化快、压力变化慢,两者结合能反映不同的物理过程。所以,相关性分析是参考,不是铁律。删不删,还得结合业务理解。
4. 知识体系总览
说了这么多,咱们用一张图来梳理一下本章的核心逻辑:
这张图清晰地展示了特征工程的三个核心步骤,以及它们之间的反馈关系。记住:这不是一个单向流水线,而是一个迭代优化的过程。你做完相关性分析后,可能发现某些窗口特征提取得不好,需要回去调整窗口大小——这很正常。
5. 写在最后
特征工程这件事,说白了就是“让数据自己说话”。标准化是让数据“说同一种语言”,滑动窗口是让数据“说完整的故事”,相关性分析是让数据“不说废话”。
我见过太多人把时间花在调模型参数上,却忽略了特征工程。结果呢?模型精度死活上不去。其实很多时候,好的特征工程比好的模型更重要。你想想看,如果输入的都是垃圾,再牛的算法也输出不了黄金。
下一章,咱们会把这些特征工程技巧应用到实际的剩余寿命预测案例中。到时候你会发现,前面这些“基础工作”有多值钱。
公众号:蓝海资料掘金营,微信deep3321