第3章:数据采集与预处理——从传感器到干净数据的最后一公里
大家好,我是你们的老朋友。今天咱们聊聊数据采集与预处理。说实话,这一章是整个状态监测系统的地基。地基没打好,后面算法再牛也是白搭。我在项目里见过太多人,上来就调模型,结果数据里全是野点,最后折腾半天才发现是采集环节出了问题。
数据采集与预处理,说白了就是三件事:怎么把信号拿进来、怎么把脏东西去掉、怎么让数据规规矩矩。咱们一个一个说。
核心观点:预处理不是可有可无的步骤,而是决定监测系统成败的关键。80%的故障诊断问题,根源都在数据质量上。
3.1 数据采集卡与PLC通信——硬碰硬的活儿
数据采集卡,说白了就是传感器的翻译官。传感器把物理量变成电信号,采集卡把电信号变成数字。PLC呢,是工业现场的老大哥,它自己也能采集数据,但精度和采样率往往不如专用采集卡。
我个人习惯,振动信号用采集卡,温度压力走PLC。为什么?振动信号频率高,需要高速采样,采集卡更擅长。温度压力变化慢,PLC的模拟量模块足够用了。
3.1.1 采集卡选型要点
- 采样率:至少是信号最高频率的2.56倍。我一般取5-10倍,留点余量。
- 分辨率:16位是底线,24位更好。分辨率不够,小信号就淹没了。
- 通道数:别抠门,多留几个备用通道。我在一个项目里就因为少算了一个通道,后期加传感器时只能换卡,折腾了三天。
3.1.2 PLC通信协议
PLC通信,Modbus TCP是主流。简单、稳定、几乎所有PLC都支持。我给大家一个Python读取PLC数据的示例:
import pyModbusTCP.client as modbus
# 连接PLC
client = modbus.ModbusClient(host="192.168.1.100", port=502)
client.open()
# 读取保持寄存器(温度值)
registers = client.read_holding_registers(0, 10) # 从地址0读10个寄存器
if registers:
temperature = registers[0] / 10.0 # 假设精度是0.1
print(f"当前温度:{temperature}°C")
else:
print("读取失败,检查网络或PLC状态")
client.close()
小技巧:PLC通信一定要加超时重试机制。工业现场网络不稳定,一次失败就报错,那系统就没法用了。我一般设3次重试,间隔1秒。
3.2 数据清洗——去噪与去野点
数据拿到手了,但别急着用。原始数据里什么妖魔鬼怪都有。电磁干扰、传感器松动、接线不良,都会产生噪声和野点。不清洗,后面分析全是错的。
3.2.1 去噪方法
去噪,我常用的就三种:
- 移动平均滤波:简单粗暴,适合慢变信号。窗口大小选3-5点,太大信号会失真。
- 中值滤波:对付脉冲噪声特别好。我在处理振动信号时经常用,能有效去掉尖刺。
- 小波去噪:高级玩法,适合非平稳信号。但参数调起来比较麻烦,新手慎用。
import numpy as np
from scipy.signal import medfilt
# 模拟原始信号(含噪声)
raw_signal = np.sin(np.linspace(0, 10, 1000)) + 0.1 * np.random.randn(1000)
# 中值滤波去噪
window_size = 5
clean_signal = medfilt(raw_signal, kernel_size=window_size)
print(f"原始信号均值:{np.mean(raw_signal):.4f}")
print(f"去噪后均值:{np.mean(clean_signal):.4f}")
3.2.2 去野点——3σ法则
野点,就是明显偏离正常范围的数据点。我判断野点的标准很简单:超过均值±3倍标准差,基本就是野点。为什么是3σ?正态分布下,99.7%的数据都在这个范围内,超出的大概率有问题。
注意:我曾经在一个项目里,用3σ法则去野点,结果把正常的高幅值振动信号也去掉了。后来才发现,那台设备在特定工况下振动就是大。所以,去野点前一定要先了解设备的正常工况范围。
def remove_outliers(data, threshold=3):
mean = np.mean(data)
std = np.std(data)
lower_bound = mean - threshold * std
upper_bound = mean + threshold * std
clean_data = data[(data >= lower_bound) & (data <= upper_bound)]
return clean_data
# 示例
data = np.array([1.2, 1.3, 1.1, 15.7, 1.4, 1.2, 1.3]) # 15.7是野点
clean = remove_outliers(data)
print(f"原始数据:{data}")
print(f"清洗后数据:{clean}")
3.3 缺失值处理——别让数据断档
数据采集过程中,难免会有缺失。网络断了、传感器故障、存储满了,都会导致数据缺失。缺失值不处理,很多算法会报错,或者结果偏差很大。
处理缺失值,我一般分三步走:
- 先判断缺失比例:如果超过30%,建议重新采集。硬补出来的数据不可靠。
- 再选方法:缺失少用插补,缺失多用删除。
- 最后验证:补完的数据要检查合理性,别补出负的温度值。
3.3.1 常用插补方法
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 均值插补 | 数据平稳,波动小 | 简单快速 | 会降低数据方差 |
| 线性插值 | 数据有趋势变化 | 保留趋势信息 | 对突变不敏感 |
| 前向填充 | 传感器短暂掉线 | 保持最近值 | 会引入滞后 |
| KNN插补 | 多维数据,有相关性 | 精度高 | 计算量大 |
import pandas as pd
import numpy as np
# 创建含缺失值的数据
df = pd.DataFrame({
'温度': [25.1, 25.3, np.nan, 25.6, 25.8],
'压力': [101.2, 101.5, 101.8, np.nan, 102.1]
})
# 线性插值
df_interpolated = df.interpolate(method='linear')
print("插值前:")
print(df)
print("\n插值后:")
print(df_interpolated)
我的习惯:对于时间序列数据,我优先用线性插值。它简单,而且能保留数据的变化趋势。如果缺失点前后数据变化剧烈,我会改用样条插值,效果更好。
3.4 数据归一化——让所有特征站在同一起跑线
归一化,说白了就是把不同量纲的数据拉到同一个尺度上。温度是几十度,振动是几个g,压力是几百kPa。不归一化,算法会偏向数值大的特征。
我常用的归一化方法有两种:
- Min-Max归一化:把数据映射到[0,1]区间。适合数据分布比较均匀的情况。
- Z-score标准化:把数据变成均值为0、标准差为1。适合数据有异常值的情况。
from sklearn.preprocessing import MinMaxScaler, StandardScaler
import numpy as np
# 模拟多特征数据
data = np.array([[25.0, 101.0, 0.5],
[30.0, 102.0, 0.8],
[28.0, 100.5, 0.6]])
# Min-Max归一化
minmax_scaler = MinMaxScaler()
data_minmax = minmax_scaler.fit_transform(data)
print("Min-Max归一化结果:")
print(data_minmax)
# Z-score标准化
zscore_scaler = StandardScaler()
data_zscore = zscore_scaler.fit_transform(data)
print("\nZ-score标准化结果:")
print(data_zscore)
避坑指南:我曾经犯过一个错误——先归一化再划分训练集和测试集。结果测试集的信息泄露到了训练集里,模型评估虚高。正确的做法是:先划分数据集,再用训练集的参数去归一化测试集。
3.5 预处理流程总结
好了,咱们把整个流程串起来。一个完整的预处理管线应该是这样的:
- 数据采集:从采集卡或PLC读取原始数据
- 去噪:用中值滤波或小波去噪去掉噪声
- 去野点:用3σ法则或IQR方法剔除异常值
- 缺失值处理:用线性插值或前向填充补全数据
- 归一化:用Min-Max或Z-score统一量纲
每一步都有坑,但只要你按这个流程走,数据质量基本就有保障了。记住,预处理不是一次性工作。设备状态变了、工况变了,预处理参数也要跟着调。我一般每三个月重新评估一次预处理效果,该调就调。
嗯,这一章就到这儿。数据准备好了,下一章咱们就可以开始真正的特征提取了。