做信号处理这几年凡是涉及故障诊断、生理信号分析、语音识别这类项目最绕不开的一个环节就是特征提取。传感器采集回来的原始信号本质上是电压随时间变化的波形但模型不会直接理解波形它需要一组能概括信号内在规律的数字。换过很多方案之后我发现小波变换做特征提取是性价比很高的一条路它对非平稳信号天然友好时频局部化能力强通常几行代码就能拿到一组结构清晰、解释性强的特征。这篇文章就把我在项目里用DWT、CWT做特征提取的经验完整梳理出来包括什么场景该用它、小波基和分解层数怎么定、特征怎么从系数里挖出来、怎么接分类器以及那些常规文档里不会写出来的坑。适合刚接触信号处理、或者已经在故障诊断和生理信号分析这条路上踩过坑的朋友建议搭配代码边跑边看一遍不够就两遍。1. 方案设计为什么非平稳信号要选小波变换1.1 傅里叶和短时傅里叶的局限在哪里设备故障诊断是理解小波价值的最佳场景。设备正常运转时信号比较平稳一旦出现轴承剥落、齿轮断齿波形上就会冒出一个个冲击。用快速傅里叶变换FFT处理这种信号把整段序列变成频谱可以知道“有没有某个频率成分”但完全不知道“这个冲击发生在哪个时间点”。这不是单纯的信息丢失而是会直接误导分类结果假设两种完全不同的故障在同一个频带上激起了相近的能量FFT看过去几乎没有区别特征自然分不开。有人会提议用短时傅里叶变换STFT解决时间定位问题。STFT确实是把信号切成片段再对每个片段做FFT得到时频矩阵。但STFT的窗口一旦固定就陷入了分辨率矛盾窗太窄时间分辨高频率分辨差窗太宽频率分辨上来了时间定位又糊了。现实中的非平稳信号低频成分往往持续很久高频成分可能只持续几个毫秒一个固定宽度的窗口根本不可能同时兼顾两种尺度。这个问题不是靠换窗函数就能解决的。正是这个矛盾让小波变换成了非平稳信号分析的主要工具。1.2 小波“多分辨率”的本质是什么小波变换的核心特点是多分辨率分析。用最直白的方式理解小波变换就是把原始信号和一系列“被缩放、被平移过的小波波形”做内积得到一组系数。系数越大说明信号在该时刻、该尺度下的局部波形和小波越像。这有点像模板匹配不过匹配的是一族模板——同一个小波基函数拉长一点去匹配低频成分压扁一点去匹配高频成分。因此小波系数天然具备两个维度尺度维对应频率和时间维对应位置。具体到离散小波变换DWT它实现起来更像一组滤波器组信号先经过低通滤波器得到近似系数再经过高通滤波器得到细节系数每一层都把频带对半拆分。低频的近似系数继续进入下一层高频的细节系数直接留存。这个过程相当于给信号配了一组“焦距随频率变化”的显微镜低频用宽窗看全局高频用窄窗看细节。特征提取时我不会直接使用全部系数而是对系数做统计和能量聚合转成一维特征向量这样既压缩了维度又保留了对分类最有用的信息。1.3 哪些项目真正需要小波特征不是所有信号都值得上小波变换。如果信号本身是稳态的比如工频正弦、平稳高斯噪声直接用FFT提取频谱幅值特征就够了小波反而增加计算量和调参复杂度。小波的优势集中在非平稳、带突变的信号上典型场景包括旋转机械振动信号的轴承、齿轮故障诊断脑电、肌电、心电等生理信号的节律分析和事件检测电力系统的瞬态扰动、谐波和电压暂降分析语音端点检测、说话人识别、地震信号分类图像纹理分析、指纹掌纹识别、医学图像分割判断标准很简单如果信号里存在瞬态冲击、扫频变化或者事件起始点FFT太“整体”STFT太“呆板”这时候小波就是最合适的选择。如果信号本身就是一段平稳周期波就别折腾了跑个FFT出幅值谱又快又稳。2. 参数定得好特征就成功一半2.1 小波基按信号形态选各家族画像小波基的选择没有绝对最优但绝不是随便挑一个就行。我做过不少对比实验一个直观的经验是小波波形与目标信号的局部形态越相似分解后的系数越稀疏能量越集中在少数层特征越好分。反过来基函数选得不对能量会摊平在各层分类器很难捕捉到差异。常用小波基的画像大致如下Haardb1结构最简单阶跃状。适合检测突变点但光滑性差对连续平滑信号的近似能力弱。DaubechiesdbN正交、紧支撑db4、db6在机械故障诊断中非常常见对冲击型信号比较友好。SymletssymN近似对称能轻微克服相位失真适合脑电、心电这类生物信号。CoifletscoifN对称性更好消失矩更高做图像纹理特征时效果不错。Morlet复值小波不满足正交性主要用在连续小波变换里生成时频图对振荡型信号特别敏感。实操上不要拍脑袋选一种就冲到底。我会在一个小数据集上做快速实验分别用db4、db6、sym8、coif4做相同的分解提取同一组特征然后比较分类准确率或者直接看每层能量分布。通常这一步能在半天时间内帮我把备选方案排掉一大半。重要的是这个选择过程要记录到项目文档里因为调基函数这件事非常容易过几个月就忘。2.2 分解层数和采样率的倒推计算法分解层数决定了特征提取的频带分辨率。DWT每一层都把当前频带对半拆分细节系数对应高频半区近似系数继续进入下一层低频半区。假设采样率是Fs奈奎斯特频率是Fs/2则第一层细节D1对应[Fs/4, Fs/2)第二层细节D2对应[Fs/8, Fs/4)依次类推最后一层近似系数A_N对应[0, Fs/(2^(N1)))区间。我一般用“倒推法”定层数先确认关心的故障频率或生理节律落在哪个频带再反推需要分解到第几层。举个例子某振动信号采样率1000Hz轴承故障特征频率大约30Hz我只需要分解到5层因为D5频带是[31.25, 62.5)覆盖了30Hz附近区域。继续往深层分解反而会把能量摊薄特征维度变多却对分类没有实质帮助。如果是脑电alpha节律8到13Hz采样率256Hz时奈奎斯特频率是128HzD3频带是[16, 32)D4频带是[8, 16)取第4层细节就能覆盖alpha频段分解到5层足够。还有一个容易被忽略的点DWT只对低频部分继续分解。如果你关心的特征落在中高频的某个窄带里比如语音共振峰用DWT一层层拆是拆不到目标频带的。这时候应该改用小波包分解WPD它对低频和高频都同时拆分能输出等宽的子带。我之前在一个语音特征项目里一开始用DWT共振峰特征怎么都不稳定换成小波包之后分类准确率直接提升了4到5个点。这个经验值得记住。2.3 信号长度、边界延拓与平移敏感度分解层数每增加1近似系数点数大约减半。我做特征提取时习惯先用len(signal) 2^level这个条件做兜底否则最后一层的近似系数只剩几个点统计量非常不可靠。比如信号只有128个点强行分解到7层最后一层可能只剩1个系数均值、标准差都没有意义。边界效应是另一个大坑。小波滤波器的卷积过程需要借助信号边缘外的数值补法有零填充、对称延拓、周期延拓、常值延拓等。PyWavelets默认用对称延拓多数工程场景下表现不错但振动冲击这类信号如果起始和结束不在同一水平边界处的几个系数会被扭曲。我的做法是截取数据片段时多留一些余量特征统计时把两端若干系数裁掉再计算。还有一个更隐蔽的问题DWT对信号平移非常敏感同一段波形整体右移几个采样点各层系数相位都会变化。如果直接把整段系数展平当特征分类器会学到跟故障无关的“位置”信息。解决方案我在第5部分细讲这里先记住一个原则优先用小波系数的统计量做特征而不是直接用原始系数。3. 特征提取的完整链路与设计细节3.1 预处理先过三关去均值、去趋势、滤干扰这一步常规文档很少细讲但恰恰决定了特征的天花板。原始信号进小波变换之前至少需要处理三件事去均值、去趋势、滤掉无关频段。去均值是为了避免直流分量跑到近似系数里让能量占比失真。很多传感器在模拟输入阶段会叠加一个直流偏置如果不减掉近似系数的能量会被偏置主导细节系数反而显得无足轻重。去趋势是去掉缓慢漂移的基线比如设备升温导致的热漂移、心电信号中的呼吸基线漂移。这些低频漂移如果不处理会污染最深层近似系数的能量。滤掉无关频段比如50Hz工频干扰是为了防止它占据高频细节的能量占比。这三个动作都很简单但少了一个后面的特征都会不稳定。信号分段也是预处理的一部分。如果是滚动分类比如每隔2秒分析一次振动要注意段与段之间的重叠率。我一般用50%重叠分段既保证特征平滑又不会制造太多重复样本导致验证结果虚高。3.2 三层特征体系能量、统计量、熵拿到coeffs pywt.wavedec(signal, wavelet, levelN)之后先别急着写特征代码。我建议先看一眼每层系数的形状确认分解层数没崩再开始特征工程。特征设计的基本盘可以总结成三大类。第一类是能量特征。各层系数的平方和占总能量的比例物理含义非常直观。设备出现故障时原本集中在某个频带的能量会发生迁移。比如正常状态下主要能量在A5轴承故障后D3明显变高这个迁移趋势就是分类器的核心线索。能量占比还有一个优点天然归一化到0到1之间跨样本可比性强。第二类是系数统计特征。对每一层系数计算均值、标准差、绝对均值、峰值、峭度、偏度。这相当于把系数序列压缩成若干标量。需要特别强调峭度它对冲击信号极其敏感早期轴承故障的微弱冲击往往先表现在峭度升高上所以峭度几乎是机械故障诊断的必选特征。第三类是信息熵特征。小波能量熵刻画信号在尺度分布上的“混乱程度”。熵的原理很简洁如果能量全部集中在一个系数层熵值低如果均匀分散在各层熵值高。故障程度越重能量分布通常越分散熵值就越大。我在很多项目里发现熵特征的区分度往往比单一能量特征更好尤其是同类故障在不同严重程度下的分级场景。3.3 特征向量拼接和标准化的正确姿势拼特征向量时排列顺序会影响别人阅读代码的体验但对模型本身影响不大。我习惯的排列是近似层系数统计量放前面然后是各层细节的统计量最后放能量占比和熵类特征。这样当特征数量变化时前面部分的索引相对稳定调试起来方便。标准化是必须做的一步。小波能量占比天然在0到1之间但系数标准差可能跨几个数量级比如振动信号的细节系数标准差可能是0.1而近似系数标准差是500。如果不处理直接喂给SVM小数特征会被大数特征完全淹没。标准化要在训练集上估计均值和方差绝不能把验证集或测试集的数据也一起算进去。这一点属于数据泄漏范畴第5部分我会专门展开。3.4 什么时候改用CWT时频图加CNN如果不想手工拼特征或者信号模式太复杂、统计量表达不清可以考虑连续小波变换CWT直接生成时频图再交给卷积神经网络做端到端分类。这在图像模型非常成熟的今天是一条很自然的路。把一维振动信号通过CWT转成二维时频图喂给一个小型CNN分类轴承故障效果通常不输手工特征方案。但这需要付出代价CWT计算量远大于DWT时频图的尺寸、颜色映射、尺度范围都会影响CNN效果可解释性也随之变差。我的习惯是需求量小、硬件成本敏感、需要向客户解释特征含义时优先用手工特征加DWT如果数据量大、计算资源充足、追求端到端省事再上CWT加CNN。不要一上来就两头都要兼顾成本太高。4. 实操代码从分解到分类4.1 环境依赖和PyWavelets的版本坑Python环境下做小波特征提取绕不开PyWavelets。安装命令是pip install PyWavelets导入名是pywt。配合numpy做统计计算scipy做信号预处理再加一个sklearn接分类器。需要留意的是PyWavelets的不同版本API有些差异老版本pywt.cwt不支持sampling_period参数新版本才支持安装时尽量锁定稳定版本。如果不小心环境里有多个Python环境建议用虚拟环境隔离避免版本冲突导致代码跑着跑着报参数错误。4.2 一维振动信号特征提取核心代码下面这段代码是我平时做轴承振动特征时改改就能用的版本。核心函数完成DWT分解之后从每层系数里提取能量占比、标准差、绝对均值三个视角的特征。import numpy as np import pywt def wavelet_feature_extract(signal, waveletdb4, level5): coeffs pywt.wavedec(signal, wavelet, levellevel) features [] names [] energies [np.sum(np.square(c)) for c in coeffs] total_energy sum(energies) if total_energy 0: total_energy 1e-12 for i, c in enumerate(coeffs): features.append(energies[i] / total_energy) names.append(fenergy_ratio_{i}) for i, c in enumerate(coeffs): features.append(np.std(c)) names.append(fstd_{i}) for i, c in enumerate(coeffs): features.append(np.mean(np.abs(c))) names.append(fmad_{i}) return np.array(features), names调用时直接传入信号片段# 假设signal是采样率1000Hz的振动信号截取1024个点 signal your_loaded_signal[:1024] feat, feat_names wavelet_feature_extract(signal, db4, 5) print(feat.shape) # (18,)为什么特征数是18因为5层分解共产生6个系数数组A5加D5到D1每个数组对应3个特征能量占比、标准差、绝对均值所以是6乘3等于18个特征。如果是4层分解就是5乘3等于15个。想清楚这个逻辑后续扩展特征时不会懵。我在项目中有时还会额外加入峭度特征代码逻辑完全一样只是往features列表里继续append而已。4.3 二维图像小波特征提取代码二维场景下函数换成pywt.wavedec2分解结果是近似系数加三个方向的细节系数水平方向cH、垂直方向cV、对角方向cD。图像纹理识别、医学图像分割里会经常用到def wavelet_image_feature(image, waveletdb2, level2): coeffs pywt.wavedec2(image, wavelet, levellevel) cA coeffs[0] features [np.mean(cA), np.std(cA)] total_energy np.sum(image ** 2) for idx, detail in enumerate(coeffs[1:]): cH, cV, cD detail features.extend([ np.sum(cH ** 2) / total_energy, np.mean(np.abs(cV)), np.sum(cD ** 2) / total_energy ]) return np.array(features)每个细节层的水平、垂直、对角分量反映了图像在不同方向上的高频能量分布。指纹纹理图像里脊线方向的突变会在对角细节系数上留下痕迹这种几何定向特征用普通像素统计是提取不出来的小波的优势在这里体现得很明显。4.4 把特征集接上SVM分类器特征提取只是前半程接分类器才是完整链路。用sklearn跑SVM分类很短小from sklearn.svm import SVC from sklearn.model_selection import cross_val_score from sklearn.preprocessing import StandardScaler # X是特征矩阵每行一个样本y是标签 scaler StandardScaler() X_scaled scaler.fit_transform(X) clf SVC(kernelrbf, C10, gammascale) scores cross_val_score(clf, X_scaled, y, cv5, scoringaccuracy) print(交叉验证准确率:, scores.mean())标准化顺序这里再强调一次必须先对训练数据做fit_transform之后用同一个scaler对验证集和测试集做transform。如果直接对全量数据先归一化再做划分测试集的信息会偷偷泄漏到训练过程中得到的准确率是虚高的。这个错误在公开数据集的社区源码里经常看到但工程上代价非常大。4.5 生成CWT时频图给CNN需要给CNN喂时频图时用下面的方式生成import matplotlib.pyplot as plt scales np.arange(1, 128) coeffs_mat, freqs pywt.cwt(signal, scales, morl, sampling_period1/1000) plt.imshow(np.abs(coeffs_mat), aspectauto, cmapjet) plt.savefig(cwt_image.png, dpi150, bbox_inchestight) plt.close()scales的范围和间距决定了时频图的频带分辨率和计算量。从1到128基本覆盖了中低频到高频如果只关心低频段可以缩到np.arange(1, 32)图会更聚焦训练也更快。保存图片时建议统一尺寸因为后续CNN输入层通常要求固定分辨率尺寸不一致就要重新resize浪费算力。5. 高频踩坑与排查完全记录5.1 小波基不对能量摊平怎么排查我在一个项目里做某类振动信号分类一开始选了Haar小波分类准确率只有七十出头换成db6之后直接跳到85%。原因不难理解Haar小波本身是阶跃状对连续光滑的振动信号近似能力弱分解后的系数不够稀疏能量分散在多层。排查方法也很直观打印各层能量占比如果能量几乎被摊平在各层多半是小波基跟信号形态不匹配。这时候不要急着加特征先换小波基做一次消融实验看能量分布是否开始集中。5.2 分解层数贪多引发的连锁故障层数设太深最深层近似系数只剩几个点统计特征完全不可靠高频细节能量被拆得稀碎特征向量暴涨。我见过有人把512点信号分解到8层特征维度堆到200多最后还得靠PCA硬压回去纯属绕远路。记住频带倒推公式分解只要覆盖到目标频带就行不必贪多。如果真的需要高频率分辨率又不想牺牲时间分辨率就换小波包分解而不是无限加深DWT层数。5.3 CWT慢到跑不动怎么办PyWavelets的CWT实现是纯Python循环数据量大了以后非常慢。我拿几十万点的长信号直接跑CWT等了十几分钟还没结束。解决办法有两个方向一是先降采样把采样率降到刚好覆盖目标频带再跑CWT二是改用FFT加速的CWT实现或者把长信号分段处理每段单独生成时频图。总的来看CWT适合短片段分析不适合直接铺在长时间序列上这一点要在项目设计阶段就想清楚。5.4 数据泄漏两个最隐蔽的来源归一化泄漏是最常见的一种。先对全量数据做标准化再划分训练集和测试集会让测试集的信息通过均值方差进入训练过程交叉验证分数往往高得离谱。第二个隐蔽来源是分段重叠率过高。如果相邻样本大量重叠训练集和验证集中会出现几乎相同的样本模型相当于已经见过了验证集的一部分数据。我一般用50%重叠做特征平滑但最终评估模型效果时会单独用一段完全不重叠的测试集重新算一次数字才敢写进报告。5.5 先想清楚物理意义再堆特征特征工程做到最后容易陷入一个误区能加的特征全部加上靠模型自己挑。这是一个性价比极低的路线。我在一个项目里把特征加到80多维分类效果不仅没变好训练时间还翻了三倍。后来把特征按物理来源分组每组用随机森林的特征重要度排了一遍删掉一半准确率反而稳住了。小波特征最大的优势之一是每一个特征都有明确物理含义能量占比就是能量迁移峭度就是冲击强度。利用这些含义去筛选特征比盲目堆维度高效得多。最后再分享一个小经验做小波特征提取这些年我最大的体会是小波变换本身并不难难的是每一步都要想清楚“为什么”。为什么选这个基函数、为什么拆这么多层、为什么提取的是能量而不是别的统计量这些决策背后其实是信号本身的物理特性。把这些问题想透了小波方法就是一台特别好用的显微镜想不透它就只是变成了又一个调参黑盒。如果你读完准备动手复现建议先拿一段自己手头的真实信号跑一遍用18维特征做一次简单分类然后回头看每层能量分布再去调整基函数和层数。这个过程走一遍之后你对小波变换的理解会比看十篇教程都管用。