ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

参数优化多尺度排列熵:原理、麻雀搜索算法实现与故障诊断应用

参数优化多尺度排列熵:原理、麻雀搜索算法实现与故障诊断应用 简介面向信号处理与故障诊断研究者这份资源聚焦多尺度排列熵算法MPE的参数优化难题通过遗传算法与粒子群算法对时间序列长度、嵌入维数、延迟时间及尺度因子进行自动寻优。资源包为RAR压缩格式共25个文件以Matlab源码.m为主辅以结果图像.bmp与实验数据.mat整体约6.03MB其中m文件涵盖GA-MPE与PSO-MPE完整框架bmp图像直观展示参数影响与优化效果mat数据供离线分析。已有4331人浏览学习适合正在研究熵特征提取参数敏感性的硕博生与工程师。资源不仅提供两种智能算法的实现代码与对比实验还包含参数对MPE影响的绘图程序便于观察不同参数组合下的熵值变化规律利用偏度构造目标函数这一设计可帮助读者快速掌握基于群体智能优化熵参数的方法论并迁移至其他特征参数优化场景。1. 拿到“参数优化多尺度排列熵”这个包先别急着跑很多论文复现包、课程资料.rar 和毕业设计源码里都挂着“参数优化多尺度排列熵”这个名字但你换一份数据再跑一遍就会发现默认参数跑出来的特征图经常分不开。真正决定多尺度排列熵MPE特征质量的不是熵值算法本身而是嵌入维度 m 和尺度因子 s 怎么组合。参数优化就是在告诉你不要用论文里的“万能默认值”而要针对你自己的信号长度、采样率和工况类别自动找一组让特征最容易分类的 m、s、τ 组合。这个方向主要用在旋转机械故障诊断、脑电疲劳分析、滚动轴承振动信号处理这类场景适合要复现论文、搭诊断流程或者做参数敏感性分析的人。本文从原理讲到最小可运行实现再列出我实际调参过程中翻过的车最后给一套验证优化效果的办法。2. 先把多尺度排列熵的原理与五个必设参数讲透2.1 从排列熵到多尺度粗粒化让短时噪声不再带偏特征排列熵Permutation Entropy, PE是 Bandt 与 Pompe 在 2002 年提出的复杂度指标。它不对信号做幅值归一化而是把长度为 N 的时间序列按嵌入维度 m 和时间延迟 τ 重构为 N - (m-1)τ 个窗口对每个窗口内 m 个采样点按大小排序得到一个序模式permutation pattern然后统计所有序模式出现的概率分布最后计算 Shannon 熵并除以 log(m!) 做归一化。它的好处是抗噪声、计算快坏处是只刻画了局部序关系丢失了不同时间尺度上的结构信息。多尺度排列熵就是在 PE 前面加了一层粗粒化coarse-graining。对于尺度因子 s先把原始序列按长度 s 切成互不重叠的窗口每个窗口内取平均得到一个长度为 N/s 的粗粒化序列再对这个序列计算排列熵。s 个尺度就得到 s 个熵值构成一条“多尺度熵曲线”。这套操作的意义在于单一尺度的 PE 容易被采样噪声和局部波动带偏而粗粒化后相当于做了两次平滑——一次是均值滤波一次是序模式统计能把真正的动力学复杂度从短时扰动中分离出来。这里有一个常见误用有人把每个尺度上的 PE 当成独立参数来调实际上粗粒化改变了序列长度和模式分布m 和 s 是耦合在一起的。比如 s20 时每段粗粒化序列只有 N/20 个点m6 需要 720 种序模式样本长度不够时统计就会出现严重偏差。这正是“参数优化”存在的价值不是调单点而是调整个 (m, τ, s) 组合。2.2 嵌入维度 m、时延 τ、尺度因子 s、样本长度 N参数选择逻辑参数常见范围我的经验取值影响方式m 嵌入维度375m 越大模式数按 m! 增长熵分辨率越高但需要更多数据点τ 时间延迟13固定 1对熵值影响远小于 m 和 s欠采样信号可放宽到 2s 尺度因子820按 N 定N 越大 s 可取越大s 太小漏掉长周期特征太大导致每尺度样本不足N 样本长度≥10242048 或 4096粗粒化后每段至少要有足够多窗口做模式统计优化策略网格或启发式麻雀搜索/PSO目标函数决定选哪组 (m, s)嵌入维度 m 是最敏感的。m3 时只有 3!6 种序模式区分度太低m6 时 720 种模式但要求每个粗粒化序列长度至少超过模式数的数倍才有统计意义。m7 时 5040 种模式常见故障信号长度很难支撑计算量也暴涨所以在优化参数时 m 的上界我会收紧到 7优先在 46 之间搜索。时间延迟 τ 多数情况下取 1 就够因为 MPE 的尺度化处理本身已经包含了时间结构信息。只有当信号采样率特别高比如 50kHz 以上且故障特征频率较低时τ2 或 3 才会带来明显改善。我一般固定 τ1把优化维度控制在 m 和 s 上这样搜索空间小、收敛快、结果也稳定。样本长度 N 是很多人忽略的隐性参数。假设 N1024s20 时每个尺度只剩 51 个点m5 需要 120 种模式勉强够用但熵值方差很大。我常用的组合是N2048、s1015N4096 时 s 可以放到 20。如果信号本身是稳态工况下的振动数据尽量取 4096粗粒化后的统计稳定性明显更好。2.3 判断“特征好不好”的两个量化指标参数优化不能靠眼睛看熵值曲线是否漂亮必须用量化指标。最常用的是类内类间距离比distance ratio。假设提取出的特征矩阵是 F每行是一个样本的 s 维熵值先计算每个类别的中心点然后定义类内距离 within 所有样本到本类中心点的欧氏距离平方和类间距离 between 各类中心点之间的欧氏距离平方和距离比 J within / (between ε)J 越小越好表示同类样本紧致、不同工况之间分得开。这个指标的优点是计算快、不依赖分类器适合放进优化算法的迭代循环里。第二个指标是分类准确率用 KNN 或 SVM 做 K 折交叉验证最终任务导向但计算成本高。实际做参数优化时我会先用距离比做快速寻优得到最优参数后再用交叉验证复核一次两个指标都确认过才采信。3. 参数优化不等于网格搜索选算法与目标函数3.1 网格搜索的最少组合数与两个放弃理由最简单的参数优化就是网格搜索。m 在 [3,7] 取 5 个值s 在 [5,20] 取 16 个值τ 固定 1一共 80 组参数。每组参数对全部样本算一遍 MPE 特征矩阵80 次计算在数据量不大时完全能接受。但网格搜索有两个硬伤一是 m 和 s 对熵值的影响不是单调的网格的固定步长很容易跳过低谷二是它本质是离线标定一旦来了新工况数据或换了采样率所有网格点都要重算性价比很低。它适合做“先摸底”的粗筛不适合作为最终方案。另一个放弃理由是网格搜索的输出只有“哪组参数最好”没有给你任何关于参数敏感性的信息。而启发式优化算法在迭代过程中会告诉你目标函数的变化曲线、最优参数周围的平坦程度这些信息在写论文分析时非常有用。所以我一般做法是先用 5×10 的粗网格跑一遍确定 m 和 s 的大致范围再用优化算法在缩小的边界内精搜。3.2 麻雀搜索算法寻优流程为什么它适合低维参数麻雀搜索算法SSA是 2020 年前后提出的群智能优化算法结构上把种群分成发现者、加入者和警戒者三类发现者负责在优质区域附近局部开发加入者向当前全局最优位置靠拢警戒者随机扰动防止早熟收敛。它相比粒子群的好处是超参数少PSO 要调惯性权重、个体学习因子和社会学习因子而 SSA 核心只需要设种群大小和迭代次数安全阈值、发现者比例这些用常值就能跑出不错的结果。对于“参数优化多尺度排列熵”这个具体问题决策变量只有 m整数和 s整数维度低但目标函数是非平滑的——因为 m 取整后模式数跳变s 取整后粗粒化长度跳变。这类问题用梯度类方法完全没法做用贝叶斯优化又对整数变量的处理比较别扭。SSA 这类无梯度群智能算法反而合适种群 12 个个体、迭代 30 次每次迭代算 12 次目标函数总共 300 多次 MPE 特征矩阵计算几分钟内就能收敛。你也可以用 PSO 或遗传算法但 SSA 的收敛速度在低维问题上通常更快因为发现者机制保证了最优解附近始终有局部搜索而加入者机制保证了种群不会整体偏离。我自己的经验是d2 时SSA 通常 10 次迭代就能找到稳定区域再往后只是小幅震荡。3.3 目标函数用距离准则还是分类准确率按数据量来定目标函数的设计直接决定优化效果。如果总样本数少于 50 条我强烈建议用距离比 J 做目标函数不要用 KNN 交叉验证准确率。理由是小样本下 K 折划分的方差很大同一个 (m, s) 在不同随机种子下可能得到从 0.75 到 0.95 的准确率优化算法会追着噪声跑。距离比 J 不涉及随机划分只要类别标签固定结果完全确定。它和 KNN 准确率的相关性在故障诊断类数据上很高因为故障特征天然就是“同工况聚集、异工况远离”的结构。如果数据量超过每类 50 条或者信号信噪比很低比如肌电、脑电这时候距离比可能过于理想化就用类内紧致和类间分离不够了我改成用分类器交叉验证的负准确率作为目标函数KNN 的 n_neighbors 固定为 3避免 SVM 引入核函数和 C 参数的额外不确定性。4. 参数优化多尺度排列熵的最小实现SSA 从 MPE 到最优参数4.1 先从单条信号写出 MPE 计算函数这一节给出可以直接运行的 Python 实现依赖只有 numpy。第一步先实现排列熵和基于粗粒化的多尺度排列熵。import numpy as np from math import factorial def permutation_entropy(signal, m3, tau1): 计算单条时间序列的排列熵 n len(signal) win_num n - (m - 1) * tau if win_num 0: return 0.0 patterns {} # 每个窗口内按元素大小排序取序号元组作为序模式 for i in range(win_num): window signal[i : i m * tau : tau] order tuple(np.argsort(window)) patterns[order] patterns.get(order, 0) 1 # 模式概率分布计算 Shannon 熵 probs np.array(list(patterns.values()), dtypefloat) / win_num pe -np.sum(probs * np.log(probs)) # 除以 log(m!) 归一化到 [0, 1] return pe / np.log(factorial(m)) def multiscale_permutation_entropy(signal, m3, tau1, s10): 多尺度排列熵对原始信号做粗粒化后逐尺度计算 PE n len(signal) mpe [] for scale in range(1, s 1): # 不重叠窗口均值保留整数倍长度 seg_num n // scale if seg_num max(factorial(m), 10): mpe.append(0.0) continue coarse np.mean(signal[: seg_num * scale].reshape(seg_num, scale), axis1) mpe.append(permutation_entropy(coarse, m, tau)) return np.array(mpe)排列熵的核心逻辑是把连续信号变成符号序列窗口内的 m 个点按数值排序数值大小关系完全由序模式刻画幅度信息被丢弃。这样做的好处是天然抗幅值漂移坏处是对噪声持续时间敏感但粗粒化步骤已经做了均值平滑。注意np.argsort(window)返回的是从小到大的索引顺序相同数值的情况不影响整体统计。粗粒化部分有个保护条件seg_num max(factorial(m), 10)时直接记 0.0。这个保护是必须的因为当 scale 很大时粗粒化序列长度不足模式数不够熵值会偏高或出现 NaN。返回值 0.0 从“复杂度不可辨识”的角度是合理的但如果你发现优化算法总往大 scale 跑大概率是这个保护在起作用说明 N 不够而不是参数真的最优。4.2 用类内类间距离构造目标函数有了单条信号的 MPE接下来要构造“一组 (m, s) 参数好不好”的评价函数。输入是一批样本和它们的工况标签输出是这个参数组合下全部样本特征矩阵的类内类间距离比。def mpe_feature_matrix(X, m3, tau1, s10): 把一批样本全部转成 s 维 MPE 特征矩阵每行一个样本 feats [] for i in range(X.shape[0]): feats.append(multiscale_permutation_entropy(X[i], m, tau, s)) return np.vstack(feats) def distance_ratio(params, X_train, y_train): 目标函数类内距离 / 类间距离值越小越好 m int(round(params[0])) m max(3, min(m, 7)) s int(round(params[1])) s max(2, min(s, 30)) tau 1 feats mpe_feature_matrix(X_train, m, tau, s) classes np.unique(y_train) within 0.0 centers {} # 先算每个类的中心点再累加类内距离 for c in classes: center feats[y_train c].mean(axis0) centers[c] center diffs feats[y_train c] - center within np.sum(diffs ** 2) # 类间距离各类中心点两两距离平方和 between 0.0 for i, c1 in enumerate(classes): for c2 in classes[i 1:]: between np.sum((centers[c1] - centers[c2]) ** 2) return within / (between 1e-12)注意两点。第一params是连续值但 m 和 s 必须是整数这里用int(round())取整并且做了边界裁剪。这是在群智能优化里处理整数变量的标准做法算法在连续空间里探索评估目标函数时映射到整数空间。第二between加了一个 1e-12 的极小值防止除零这在类数只有 1 或特征矩阵全相等时会触发正常两类以上数据不会走到这一步。距离比是一个标量越小表示同类聚集、异类分开后续 SSA 就是去最小化这个标量。4.3 麻雀搜索主循环与最终输出目标函数就绪后SSA 的主循环并不复杂。下面是一个简化但可运行的实现保留了发现者、加入者、警戒者三种典型个体分工。def ss_optimize(X_train, y_train, m_range(3, 7), s_range(3, 20), pop12, iters30, seed2024): np.random.seed(seed) lb np.array([m_range[0], s_range[0]], dtypefloat) ub np.array([m_range[1], s_range[1]], dtypefloat) # 初始化种群 positions np.random.uniform(lb, ub, (pop, 2)) fitness np.array([distance_ratio(p, X_train, y_train) for p in positions]) gbest_idx np.argmin(fitness) gbest_pos positions[gbest_idx].copy() gbest_score fitness[gbest_idx] ST, PD, SD 0.8, 0.7, 0.2 history [] for t in range(iters): order np.argsort(fitness) ranked positions[order] new_pos positions.copy() # 发现者排名靠前的个体在小范围内精细搜索 for i in range(int(pop * PD)): if np.random.rand() ST: factor np.exp(-i / (pop * (t 1.0))) new_pos[order[i]] np.clip(ranked[i] * factor, lb, ub) else: new_pos[order[i]] np.clip( ranked[i] np.random.randn(2) * (ub - lb) * 0.05, lb, ub) # 加入者向全局最优靠拢随机决定逼近还是越过最优点 for i in range(int(pop * PD), pop): if i pop - 1: new_pos[order[i]] np.clip( gbest_pos np.random.randn(2) * (ub - lb) * 0.1, lb, ub) else: direction np.random.choice([-1.0, 1.0], size2) delta np.abs(gbest_pos - ranked[i]) * np.random.rand() new_pos[order[i]] np.clip(gbest_pos direction * delta, lb, ub) # 警戒者少量个体扰动防止早熟 guard_idx np.random.choice(pop, sizeint(pop * SD), replaceFalse) for idx in guard_idx: if np.random.rand() 0.5: new_pos[idx] np.clip( gbest_pos np.random.randn(2) * (ub - lb) * 0.1, lb, ub) else: new_pos[idx] np.clip( new_pos[idx] np.random.randn(2) * (ub - lb) * 0.02, lb, ub) positions np.clip(new_pos, lb, ub) fitness np.array([distance_ratio(p, X_train, y_train) for p in positions]) best_idx np.argmin(fitness) if fitness[best_idx] gbest_score: gbest_score fitness[best_idx] gbest_pos positions[best_idx].copy() best_m int(round(gbest_pos[0])) best_s int(round(gbest_pos[1])) history.append((best_m, best_s, gbest_score)) print(fiter {t 1:02d}: m{best_m:d}, s{best_s:d}, ratio{gbest_score:.4f}) opt_m int(round(gbest_pos[0])) opt_s int(round(gbest_pos[1])) return opt_m, opt_s, gbest_score, history # 用法示例 # opt_m, opt_s, score, hist ss_optimize(X_train, y_train) # print(opt_m, opt_s, score)这段代码里的粒子位置始终保持为浮点数直到评估前在distance_ratio里取整这样警戒者和加入者的扰动步长不会因为取整而失真。发现者的指数衰减因子exp(-i / (pop * (t1)))让排名靠前的个体逐步收窄搜索范围符合收敛需求。加入者里有 50% 概率越过全局最优点这种“过度冲一下”的策略能帮助种群跳出局部最优实际效果比我一开始用的纯靠拢式更新好很多。种群大小 pop12、迭代 iters30 是两变量问题的经验值。如果你发现 history 里最后 5 次迭代的目标值还在持续下降把 iters 加到 50如果第 5 次迭代以后就完全不动可以减小到 20。目标函数是确定性计算没有随机数据划分所以同一随机种子下的输出完全可复现这比用分类准确率做目标函数要省心得多。5. 参数优化多尺度排列熵的 5 个避坑与排查记录5.1 尺度因子太大特征值全部塌缩到 0现象MPE 曲线的后半段s20 以上全是 0SSA 优化时评分表现出异常的“大 s 更优”因为距离比里 0 向量让某些类的中心点重合。原因粗粒化后的序列长度 N/s 小于模式数 m!。比如 N1024、s20 时每段只有 51 个点m6 时 720 种模式的统计根本撑不起来程序受保护逻辑影响把熵值记成 0等于告诉优化算法“这个尺度没有信息量”。解决先检查最小可用尺度。经验判断式是 s_max 不超过 N/(m!×2)。N4096 时 s 可以放宽到 30N2048 时建议 s 不超过 20N1024 时不要超过 10。如果业务上确实需要大尺度先增加采样点数或做滑窗拼接而不是硬调 s。5.2 m7 时熵值出现 NaN模式的统计量不够用现象m 取 7 时部分样本的 PE 计算结果为 NaN整个特征矩阵报废优化算法报错或评分全是 nan。原因m7 对应 5040 种序模式粗粒化后序列点位数少于模式数patterns字典里很多模式只出现一次概率分布极度稀疏np.log(probs)对 0 概率不报错但熵值本身对观测长度极其敏感数值波动会放大到异常范围。解决把 m 的搜索上界设为 6。如果必须用 m7把 N 提到 10000 点以上并且给permutation_entropy加一个保护if win_num factorial(m) * 5: return 0.0。这样至少不会让 NaN 污染整个优化过程。另外检查一下你的原始信号里有没有重复过零或常量段常量段会大量产生同一个序模式让熵值被低估。5.3 每次运行最优参数都不一样随机种子与目标波动现象同一个数据集、同一个优化代码两次运行得到的 (m, s) 不同有时候 m4 和 m5 交替出现。原因SSA 种群初始化是随机的这在低维整数参数空间里很容易收敛到不同的“山谷”。另外一个隐藏来源是如果目标函数里带了 KNN 交叉验证K 折划分的随机性会让目标值自身有噪声优化算法会把噪声当作真实信号去追逐。解决目标函数固定用距离比不用交叉验证在ss_optimize入口显式设置np.random.seed(2024)。如果多次换种子仍然得到不同的 m说明这个数据集的 MPE 特征对 m 不敏感属于平坦最优区域取 m5 作为保守默认值即可不必纠结。5.4 RAR 包里的数据读出来是乱码或报错路径、编码与格式三连坑现象从课程资料.rar 解压出的 MATLAB 数据文件scipy.io.loadmat报文件不存在或者变量名乱码偶尔还会遇到“忘记解压密码”之外更头疼的问题——文件在但读不出来。原因三个原因叠在一起。第一Windows 下中文路径和空格会被某些旧版 scipy 误判第二MATLAB 7.3 及以上版本默认用 HDF5 格式保存 .matloadmat直接读不了第三部分压缩包里的文件是 latin1 编码保存的变量名loadmat默认 utf-8 就会变成乱码。解决路径环节统一用os.chdir切到数据目录避免拼接长路径格式检测上先用h5py读取文件头判断是否 HDF5是的话改用h5py.File(path, r)读取变量名乱码时给loadmat加encodinglatin1。读取环节加一个 try 结构把 scipy 和 h5py 两条路径都尝试一遍能省掉大量排查时间。import scipy.io as sio import h5py, os def load_mat_safe(path): 兼容 MATLAB 7.3 之前的 .mat 和 HDF5 格式 try: data sio.loadmat(path, encodinglatin1) # 去掉 __header__ 等辅助字段 return {k: v for k, v in data.items() if not k.startswith(__)} except NotImplementedError: with h5py.File(path, r) as f: return {k: np.array(f[k]) for k in f.keys()} except FileNotFoundError: print(路径不对检查文件名和中文目录) return None5.5 优化参数在分类任务上没提升目标函数和评价指标脱节现象SSA 给出的最优参数 (m4, s14) 在距离比上明显优于默认 (m5, s10)但用 KNN 做 5 折交叉验证时准确率几乎一样甚至偶尔更低。原因距离比度量的是欧氏空间里的类间分离度而 KNN 的决策边界是局部的。某些特征分布下两个类的中心点距离远但边界处样本重叠距离比好看、分类准确率却不理想。另一个常见原因是特征维数太高时 KNN 受无关尺度影响s 从 10 加到 20 引入了低区分度的尾部尺度反而稀释了有效特征。解决在优化结束后强制做一次双指标复核用evaluate_params同时计算距离比和 KNN 准确率两者都提升才采纳如果只有距离比提升考虑对特征矩阵做标准化或只保留前 s 个尺度中区分度最高的 80% 维度。如果准确率差距在 1% 以内直接判定为“该数据集对参数不敏感”用默认参数是更稳的选择。6. 验证参数优化效果的两个技巧交叉验证与特征可视化拿到opt_m和opt_s之后不要直接拿去写论文先做一次严格的对比验证。我习惯写一个小函数把默认参数和优化参数在同一个评价流程里跑一遍from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import cross_val_score def evaluate_params(X, y, m, s): feats mpe_feature_matrix(X, m, 1, s) knn KNeighborsClassifier(n_neighbors3) acc cross_val_score(knn, feats, y, cv5).mean() ratio distance_ratio(np.array([m, s]), X, y) return feats, acc, ratio default_acc evaluate_params(X_train, y_train, 5, 10)[1] opt_acc evaluate_params(X_train, y_train, opt_m, opt_s)[1] print(fdefault m5 s10: acc{default_acc:.3f}) print(foptimized m{opt_m} s{opt_s}: acc{opt_acc:.3f})第二个验证技巧是特征可视化。用 t-SNE 把 s 维熵特征降到 2 维散点图观察不同工况样本是否形成明显簇。注意 t-SNE 的perplexity必须小于样本数比如总样本 40 条时就设 30否则会报错。如果 t-SNE 图上优化参数比默认参数明显更紧凑这个优化就有说服力如果两种参数的可视化结果差不多说明该数据上参数优化空间本就不大。from sklearn.manifold import TSNE feats_opt, _, _ evaluate_params(X_train, y_train, opt_m, opt_s) tsne TSNE(n_components2, perplexitymin(30, len(y_train)-1), random_state42, initpca) vis tsne.fit_transform(feats_opt)我自己的习惯是每次优化都把 history 里的 (m, s, ratio) 存成 numpy 数组迭代过程画成收敛曲线存档。这样既能看到参数是否真的收敛也能在审稿人问“为什么选这个 m”时直接拿出曲线。参数优化这个方向最怕的就是把优化算法当黑匣子跑完就拿结果。把随机种子固定、目标函数写清楚、收敛过程打出来这套流程才算真正闭环。希望这篇笔记能帮你少踩几个我踩过的坑。本文还有配套的精品资源点击获取
返回列表