电机振动智能分析实战指南:从Python到MCU(基于隔离森林)

本指南面向工业振动监控的初学者,手把手教你用Python完成数据采集、特征工程、异常检测模型训练,并最终将方案部署到STM32等边缘设备。核心算法采用隔离森林(Isolation Forest),兼顾精度与轻量。


1. 采样与窗口设定:让数据"看得清"故障

1.1. 三轴采样与设备频率的关系

电机振动通常采集X(水平)、Y(垂直)、Z(轴向)三轴加速度。窗口长度(即一次分析的时间片段)的设定必须与设备转频挂钩,否则要么漏掉低频故障,要么统计特征不稳。

关键公式

  • 转频 f0=转速 (RPM)60f_0 = \frac{\text{转速 (RPM)}}{60}f0=60转速 (RPM) (Hz)
  • 窗口长度 TwindowT_{\text{window}}Twindow 内应包含至少 KKK 个旋转周期,推荐 K≥10K \ge 10K≥10:
    Twindow≥Kf0T_{\text{window}} \ge \frac{K}{f_0}Twindow≥f0K

举例

  • 电机 1500 RPM → f0=25Hzf_0 = 25\text{Hz}f0=25Hz,周期 0.04s → Twindow≥10×0.04=0.4sT_{\text{window}} \ge 10\times0.04 = 0.4\text{s}Twindow≥10×0.04=0.4s,工程取 1s。
  • 电机 300 RPM → f0=5Hzf_0 = 5\text{Hz}f0=5Hz,周期 0.2s → Twindow≥2sT_{\text{window}} \ge 2\text{s}Twindow≥2s。

1.2 窗口长度的工程权衡

窗口过短 窗口过长
❌ 无法分析低于 1/T1/T1/T 的低频故障 ✅ 可捕捉极低频成分
❌ 峭度、偏度等统计量波动大,易误报 ✅ 特征稳定,抗噪强
✅ 响应快,预警及时 ❌ 预警延迟大
❌ FFT频率分辨率低(Δf=1/T\Delta f = 1/TΔf=1/T) ❌ 计算量大,内存占用高

推荐 :对于工业电机(600~3000 RPM),取 1秒窗口。转速低于600 RPM时,适当延长至2秒。

1.3 Python实现:滑动窗口切分

python 复制代码
import numpy as np

def sliding_window(data, window_size_sec, fs, step_sec=None):
    """
    data: 原始一维振动信号 (numpy array)
    window_size_sec: 窗口长度(秒)
    fs: 采样率 (Hz)
    step_sec: 步进长度(秒),默认等于窗口长度(不重叠)
    """
    win_len = int(window_size_sec * fs)
    step = win_len if step_sec is None else int(step_sec * fs)
    return [data[i:i+win_len] for i in range(0, len(data)-win_len+1, step)]

2. 特征工程:把振动波形变成故障指纹

特征工程是决定隔离森林成败的核心。我们将提取时域+频域共12~16维特征,并给出公式和解释。

2.1 时域特征(对冲击、磨损敏感)

设窗口内有 NNN 个采样点 xix_ixi,均值为 μ\muμ,标准差为 σ\sigmaσ。

特征 公式 物理意义
均方根 (RMS) 1N∑xi2\sqrt{\frac{1}{N}\sum x_i^2}N1∑xi2 振动能量,反映整体磨损、不平衡
峰值 (Peak) max⁡∣xi∣\max \vert x_i \vertmax∣xi∣ 最大瞬时冲击
峰峰值 (P-P) max⁡(xi)−min⁡(xi)\max(x_i) - \min(x_i)max(xi)−min(xi) 振动摆幅
峭度 (Kurtosis) 1N∑(xi−μσ)4\frac{1}{N}\sum\left(\frac{x_i-\mu}{\sigma}\right)^4N1∑(σxi−μ)4 冲击敏感,早期轴承点蚀的黄金指标(正常≈3,>4报警)
偏度 (Skewness) 1N∑(xi−μσ)3\frac{1}{N}\sum\left(\frac{x_i-\mu}{\sigma}\right)^3N1∑(σxi−μ)3 不对称性,反映摩擦、不对中
波形因子 RMS∣均值∣\frac{RMS}{\vert \text{均值} \vert }∣均值∣RMS 检测脉冲,对负载变化不敏感
脉冲因子 Peak∣均值∣\frac{Peak}{\vert \text{均值}\vert }∣均值∣Peak 类似波形因子,但对极端值更敏感

2.2 频域特征(定位具体故障源)

需要对每个窗口做FFT,得到幅值谱 ∣X[k]∣|X[k]|∣X[k]∣,频率分辨率 Δf=fs/N\Delta f = f_s / NΔf=fs/N。

特定频带能量(重点)

公式
E[flow,fhigh]=∑k=klowkhigh∣X[k]∣2E_{[f_{low}, f_{high}]} = \sum_{k=k_{low}}^{k_{high}} |X[k]|^2E[flow,fhigh]=k=klow∑khigh∣X[k]∣2

其中 klow=⌊flow/Δf⌋k_{low} = \lfloor f_{low}/\Delta f \rfloorklow=⌊flow/Δf⌋,khigh=⌈fhigh/Δf⌉k_{high} = \lceil f_{high}/\Delta f \rceilkhigh=⌈fhigh/Δf⌉。

典型频带 (假设当前转频为 f0f_0f0):

  • 1×f01\times f_01×f0:不平衡
  • 2×f02\times f_02×f0:不对中、松动
  • 0.5×f00.5\times f_00.5×f0:机械松动、摩擦
  • 高频段(如 2k~5kHz):轴承早期点蚀
  • 边频带(以齿轮啮合频率/轴承故障频率为中心,宽度 ±2f0f_0f0):调制故障

Python实现

python 复制代码
def band_energy(fft_mag, fs, f_low, f_high):
    """fft_mag: 单边幅值谱(长度 N//2+1)"""
    delta_f = fs / (2 * (len(fft_mag)-1))
    k_low = int(f_low / delta_f)
    k_high = int(f_high / delta_f)
    return np.sum(fft_mag[k_low:k_high+1] ** 2)

全局频域特征

特征 公式 意义
重心频率 fc=∑fkPk∑Pkf_c = \frac{\sum f_k P_k}{\sum P_k}fc=∑Pk∑fkPk 频谱能量中心,反映刚度变化
频率方差 ∑(fk−fc)2Pk∑Pk\frac{\sum (f_k-f_c)^2 P_k}{\sum P_k}∑Pk∑(fk−fc)2Pk 能量分散程度
总能量 ∑Pk\sum P_k∑Pk 全频带振动烈度

(Pk=∣X[k]∣2P_k = |X[k]|^2Pk=∣X[k]∣2 为功率)

2.3 工况特征(必须包含)

  • 转速 (RPM) -- 来自编码器或变频器
  • 负载 (%) -- 电流或扭矩信号

工况特征能让模型区分"转速升高导致振动变大"和"故障导致振动变大"。


3. 特征分布分析:不做"睁眼瞎"

为什么要分析特征分布?

  • 大多数统计模型(如3-sigma控制图)假设数据服从正态分布,而振动特征(如RMS、峭度)往往是对数正态长尾分布。无视分布直接设阈值,误报率极高。
  • 分布形态的漂移(均值、方差、偏度变化)是设备退化的早期信号,比超阈值更灵敏。

3.1 分布分析的具体用途

分析工具 作用
直方图 + 核密度估计 (KDE) 直观查看分布形态(单峰/多峰、偏斜、长尾)
Q-Q图 判断是否服从正态/对数正态分布
偏度、峰度值 量化偏离程度:偏度>1强右偏,峰度>3尖峰
KL散度 (Kullback-Leibler) 量化健康分布与当前分布的差异,用于趋势预警

3.2 如何用于特征选择?

  • 剔除常数特征:分布退化为单点(方差=0),无信息量。
  • 剔除极端离群分布:某个特征在健康数据中就已呈现多峰或极长尾,说明可能混入了不同工况,需分段处理或剔除。
  • 决定是否做变换:若RMS呈对数正态,取对数后再标准化,模型更稳定。

3.3 Python示例

python 复制代码
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.stats import probplot

# 假设 df 包含所有窗口的RMS特征
sns.histplot(df['rms'], kde=True)
probplot(df['rms'], dist='norm', plot=plt)  # Q-Q图

4. 特征排序:找到"最有价值"的特征

隔离森林本身可以输出特征重要性(基于平均路径深度减少量),但在训练前,我们可以先用过滤式方法快速排序,剔除无效特征。

4.1 常用排序方法

方法 原理 优点 缺点
方差阈值 方差 < 0.01 的特征剔除 极快 只能剔除常数特征
互信息 (Mutual Information) 衡量特征与标签(如果有)的非线性相关性 不依赖模型 需要标签(无监督场景慎用)
基于隔离森林的特征重要性 训练后查看 model.feature_importances_ 与最终模型一致 需先训练一次
主成分分析 (PCA) 载荷 第一主成分的系数绝对值 降维同时排序 线性假设

4.2 推荐流程(无监督场景)

  1. 计算每个特征的方差,剔除方差为0的特征。
  2. 计算特征之间的相关性矩阵(皮尔逊),对于相关系数>0.95的组,保留其中一个(参见第五节)。
  3. 用全部剩余特征训练一个临时隔离森林,输出特征重要性,排序后保留前15~20个。
python 复制代码
from sklearn.ensemble import IsolationForest

iso = IsolationForest(contamination=0.01, random_state=42)
iso.fit(X_train)  # X_train 为标准化后的特征矩阵
importance = iso.feature_importances_  # 注意:scikit-learn 0.22+ 支持
sorted_idx = np.argsort(importance)[::-1]

5. 特征相关性分析(皮尔逊相关系数):去冗余 & 动态监测

5.1 皮尔逊相关系数公式

对于两个特征 X,YX, YX,Y:
r=∑(xi−xˉ)(yi−yˉ)∑(xi−xˉ)2∑(yi−yˉ)2r = \frac{\sum (x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum (x_i - \bar{x})^2 \sum (y_i - \bar{y})^2}}r=∑(xi−xˉ)2∑(yi−yˉ)2 ∑(xi−xˉ)(yi−yˉ)

取值范围 [−1,1][-1, 1][−1,1],绝对值越接近1,线性相关性越强。

5.2 用途一:特征筛选(去冗余)

问题:RMS和峰峰值常常高度相关(r>0.95),同时保留会使隔离森林的随机切分偏向这个冗余方向,降低效率。

做法:设定阈值(如0.95),对相关性矩阵中的高相关组,每组只保留一个特征(保留与目标变量相关性最高或方差最大的)。

python 复制代码
def remove_correlated_features(df, threshold=0.95):
    corr_mat = df.corr().abs()
    upper_tri = corr_mat.where(np.triu(np.ones(corr_mat.shape), k=1).astype(bool))
    to_drop = [column for column in upper_tri.columns if any(upper_tri[column] > threshold)]
    return df.drop(columns=to_drop)

5.3 用途二:动态关系监测(高级异常)

在电机健康状态下,计算两个特征(如转速 vs RMS)的相关系数 rhealthyr_{healthy}rhealthy。在线运行时,滑动窗口实时计算 rcurrentr_{current}rcurrent。如果 ∣rcurrent−rhealthy∣>0.3|r_{current} - r_{healthy}| > 0.3∣rcurrent−rhealthy∣>0.3,说明系统动态特性改变(如联轴器松动、负载突变),即使单个特征正常也应报警。

5.4 用途三:故障溯源

当隔离森林报告异常后,将当前窗口的特征与历史故障特征库(每个故障类型对应一组典型特征)做皮尔逊相关,相关性最高的故障类型即为诊断结果。


6. 隔离森林模型训练:边缘上的哨兵

6.1 数据量要求

场景 建议数据量(训练集)
最小可用 200~500 条
工程推荐 1000~5000 条
高稳定性 5000+ 条

换算:1秒窗口,30分钟健康数据 = 1800条,完全够用。

6.2 训练步骤(Python)

python 复制代码
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
import numpy as np

# 1. 加载健康数据(无故障)的特征矩阵 X_healthy (n_samples, n_features)
# 假设已经完成特征工程,得到 X_healthy

# 2. 标准化(必须!否则大数值特征主导切分)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_healthy)

# 3. 训练隔离森林
model = IsolationForest(
    n_estimators=100,        # 树的数量,50~100足够
    max_samples='auto',      # 每棵树采样数,默认256
    contamination=0.01,      # 预期异常比例(健康数据中一般<1%)
    random_state=42,
    bootstrap=False
)
model.fit(X_scaled)

# 4. 保存模型和标准化器(用于后续部署)
import joblib
joblib.dump(model, 'iso_model.pkl')
joblib.dump(scaler, 'scaler.pkl')

6.3 参数调优建议

  • n_estimators:越大越稳定,但模型也越大。边缘设备用50,云端用100。
  • contamination:设一个略高的值(如0.05)以降低漏报,后续用阈值调节。
  • max_samples:数据量<10000时保持默认"auto"(=256);数据量大时可适当增大。

6.4 模型评估(无监督)

没有标签时,可通过以下方式间接评估:

  • 重构误差:将正常验证集输入,看异常分数分布是否集中。
  • 注入人工异常:在健康信号中加入冲击、噪声,看模型能否检出。
  • 时序一致性:连续窗口的异常分数不应剧烈跳变,否则模型不稳定。

7. 从Python到MCU:轻量化部署要点

7.1 模型压缩

  • 树数量减至 n_estimators=30~50
  • 最大深度 max_depth=8~10(限制树的生长)
  • 模型大小可控制在 10~25 KB(STM32完全可承受)

7.2 特征计算优化

  • FFT使用CMSIS-DSP库(ARM官方优化)
  • 频带能量预计算索引,避免重复计算边界
  • 时域特征采用递推算法(如RMS滑动更新)降低计算量

7.3 C++推理示例(伪代码)

cpp 复制代码
#include "isolation_forest.h"  // 假设已有模型加载库

float features[12];            // 实时计算的特征
float anomaly_score = iso_forest_predict(features);  // 返回异常分数(负值越大越异常)
if (anomaly_score < -0.3) {    // 阈值需根据Python调优确定
    trigger_alert();
}

8. 完整工作流程图

采集原始振动信号
滑动窗口分片
时域特征提取
FFT + 频域特征提取
合并特征向量
特征分布分析与标准化
相关性分析去冗余
训练隔离森林模型
保存模型 + 标准化器
Python验证
转换为C++模型
部署到MCU实时推理


9. 常见问题与避坑指南

问题 解决方案
特征超过50维,模型效果变差 用相关性分析 + 特征重要性筛选到15维以内
训练数据只有几百条 使用滑动窗口重叠(步长0.5秒)扩展样本,或做数据增强(加噪声、缩放)
转速变化大,频带能量不靠谱 动态更新频带边界:根据实时转速重新计算1x, 2x等频带的频率范围
MCU内存不足 限制树的数量和深度,将float64转为float32,甚至量化到int16
误报率高 1)降低contamination;2)提高异常分数阈值;3)增加峭度等确认逻辑(连续3个窗口报警才上报)

最后一句:先从Python跑通流程,用你自己的电机数据验证,再移植到MCU。不要一上来就追求复杂模型,隔离森林+15个精心挑选的特征,已经能解决80%的工业异常检测问题。

附:FFT泄漏问题分析与解决方案(工程实战篇)

在电机振动分析中,FFT是提取频域特征的核心工具。但初学者常遇到一个"隐形杀手"------频谱泄漏。它会让本该尖锐的谱线变得"拖尾"、能量扩散到相邻频点,导致频带能量计算严重失真,最终让隔离森林模型学到错误特征。

什么是FFT泄漏?

FFT泄漏 是指:当信号的频率不是FFT频率分辨率 Δf=fs/N\Delta f = f_s / NΔf=fs/N的整数倍时,信号能量会"扩散"到相邻的多个频率分量上,导致频谱变宽、幅值降低、相位失真。

产生原因(直观理解)

FFT 默认对时域信号进行周期性延拓 。如果你的采样窗口内不是整数个信号周期,延拓后的波形在连接处会出现突变(不连续)。这个突变等效于一个高频脉冲,其频谱会布满整个频域,从而污染真实的谱线。

数学表达

设信号 x(t)=Acos⁡(2πf0t)x(t) = A \cos(2\pi f_0 t)x(t)=Acos(2πf0t),采样后做 N 点 FFT。若 f0=m⋅Δff_0 = m \cdot \Delta ff0=m⋅Δf(m 为整数),则谱线正好落在第 m 根谱线上,无泄漏。

若 f0=(m+δ)⋅Δff_0 = (m + \delta) \cdot \Delta ff0=(m+δ)⋅Δf(0<δ<10<δ<10<δ<1),则能量会泄漏到第 m、m+1 及更远的谱线上。δ 越接近 0.5,泄漏越严重。

对电机振动分析的影响

  • 1×转频频带能量:计算值偏小,可能误判为"故障减弱"。
  • 边频带能量:微弱的调制边带被泄漏淹没,无法识别轴承/齿轮早期故障。
  • 重心频率:向低频方向漂移。

解决方案(恒速电机场景)

方案一:加窗(最通用,强烈推荐)

原理:在时域对信号乘以一个两端平滑衰减到零的窗函数(如汉宁窗),强制消除边界突变。加窗后,泄漏的能量被约束在窗函数主瓣附近的有限范围内,旁瓣衰减极快。

数学本质:加窗相当于原信号的频谱与窗函数频谱做卷积。窗函数频域的主瓣宽度决定了频率分辨率,旁瓣高度决定了泄漏程度。汉宁窗的旁瓣衰减可达 -31 dB,比矩形窗(-13 dB)好得多。

对振动分析的适配

  • 时域特征(RMS、峭度、偏度等)必须在加窗前从原始信号计算。
  • 频域特征(频带能量、重心频率等)使用加窗后信号的FFT。
  • 频带能量不进行能量修正(因为后续模型只关心相对变化)。

方案二:整周期采样(恒速电机可选)

原理 :动态调整窗口长度 Twindow=K/f0T_{\text{window}} = K / f_0Twindow=K/f0(K 为整数个周期),使窗口内恰好包含整数个旋转周期,信号天然连续,无需加窗。

优点 :无泄漏,频率分辨率最高。
缺点:需要精确测速;窗口长度变化,不利于特征标准化;实现稍复杂。

结论 :对于恒速且转速非常稳定的电机,整周期采样是优雅的。但加窗法更鲁棒(不依赖精确测速,且能抑制噪声),建议作为首选。

Python 代码示例(加汉宁窗 + 原理注释)

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

# ========== 参数设置 ==========
fs = 1024          # 采样率 (Hz)
N = 1024           # FFT点数,也是窗口长度
t = np.arange(N) / fs

# 模拟恒速电机:转频 24.5 Hz(不是 Δf=1Hz 的整数倍,会产生泄漏)
f0 = 24.5
signal = np.sin(2 * np.pi * f0 * t)

# ========== 加窗处理 ==========
# 汉宁窗定义: w[n] = 0.5 - 0.5*cos(2πn/(N-1))
# 作用:让信号两端平滑到零,消除边界突变,抑制频谱泄漏
window = np.hanning(N)          # 直接调用 numpy 生成
signal_win = signal * window    # 时域相乘

# ========== 做 FFT ==========
# rfft 返回单边频谱(实信号)
X_no_window = np.fft.rfft(signal)       # 不加窗的频谱(复数)
X_window = np.fft.rfft(signal_win)      # 加窗后的频谱

# 转换为幅值谱(dB表示,便于观察泄漏的旁瓣)
mag_no = 20 * np.log10(np.abs(X_no_window) + 1e-10)
mag_win = 20 * np.log10(np.abs(X_window) + 1e-10)

freqs = np.fft.rfftfreq(N, 1/fs)        # 对应的频率轴

# ========== 绘图对比 ==========
plt.figure(figsize=(10, 4))
plt.plot(freqs, mag_no, label='不加窗(矩形窗)', alpha=0.7)
plt.plot(freqs, mag_win, label='加汉宁窗', alpha=0.7)
plt.xlim(20, 30)                        # 聚焦在转频附近
plt.axvline(f0, color='red', linestyle='--', label=f'真实频率 {f0} Hz')
plt.xlabel('频率 (Hz)')
plt.ylabel('幅值 (dB)')
plt.title('FFT泄漏抑制效果对比(恒速电机 24.5Hz)')
plt.legend()
plt.grid(True)
plt.show()

# ========== 结果解释 ==========
# 不加窗时:能量向两侧扩散,24Hz和25Hz处都有明显幅值,真实峰值被压低。
# 加窗后:能量集中,主瓣变宽但旁瓣快速衰减,24.5Hz处出现清晰峰值。
# 对于频带能量特征(如23~27Hz),加窗后的累加值更能反映真实振动能量。

恒速电机下的实施建议

  1. 固定窗口长度:1秒(或包含10个以上周期)。即使转频不是Δf的整数倍,加窗也能保证频带能量特征稳定。
  2. 预处理顺序:原始信号 → 计算时域特征 → 加汉宁窗 → FFT → 计算频域特征。
  3. 不必刻意追求整周期采样:除非你的转速测量非常精确(误差<0.1%),否则加窗法更简单可靠。
  4. 验证泄漏是否被抑制:用上面的代码跑一下你的实际电机数据,观察转频附近频谱是否出现明显的"单峰"而非"平顶"。
相关推荐
咚咚王者5 小时前
人工智能之知识蒸馏 第七章 知识蒸馏在边缘计算与移动端的实践应用
人工智能·边缘计算
youyudehexie8 小时前
云原生与边缘计算融合驱动下一代互联网架构创新探索实践
云原生·架构·边缘计算
pingao1413788 小时前
物联网+边缘计算:新一代一体化水雨情监测系统架构解析
物联网·系统架构·边缘计算
鲁邦通物联网1 天前
储能系统北美合规架构:基于FCC规范的边缘计算网关数采实践
边缘计算·数据采集·工业数据采集·边缘网关·边缘计算网关·物联网网关·5g数采
小芝麻咿呀1 天前
边缘计算网关-EG8200Mini导轨版
java·人工智能·边缘计算
ZLG_zhiyuan3 天前
高性能边缘计算网关EPCM3568A-LI:小身材,大能量
人工智能·边缘计算
搜佛说4 天前
sfsEdgeStore,工业物联网边缘计算的“瘦身”革命
人工智能·物联网·边缘计算
嵌入式小企鹅5 天前
国芯抗量子MCU突破、太空算力元年开启、AI编程工具密集发布
学习·ai·边缘计算·算力·risc-v·芯片·半导体
鲁邦通物联网6 天前
储能BMS数据语境化采集架构解析与边缘计算网关选型推荐
边缘计算·数据采集·工业数据采集·边缘网关·边缘计算网关·物联网网关·5g数采