
信号系统这门课当年学的时候卷积、傅里叶变换、采样定理一套下来最直观的感受就是把一个信号拆开看它由什么组成。后来转去做神经网络相关的信号处理项目越做越觉得这两件事是同一套东西的两张皮——卷积层干的活本质就是一组可学习的滤波器反向传播调的是这些滤波器的系数而你在频域里看到的通带、阻带、混叠一样会在训练曲线上咬你一口。这篇东西想聊的就是信号系统之神经网络这个交叉点上真正会卡住人的地方怎么把采集端的物理约束翻译成网络结构怎么手算感受野和参数量怎么切数据集才不给自己埋雷以及上线之后为什么好好的模型会突然掉点。内容适合有基本信号处理概念、正准备拿神经网络做振动、语音、生理电、通信这类时序信号任务的人也适合纯做模型、对传感器那一端不太熟的朋友补一补上游的常识。下面全部是踩过坑之后整理出来的东西能抄的代码和参数我尽量给全。1. 先给神经网络换个坐标它本质上是个可学习的信号系统很多人第一次把神经网络和信号系统放在一起看会觉得是硬凑。其实不是。你在线性系统里学的那套输入输出关系、卷积、频率响应、稳定性判据几乎可以一一映射到网络的前向与反向过程上。这个视角的价值不在于好听而在于它能让你在动手之前就判断出哪些结构根本不可能work省掉大量盲目试错的时间。1.1 从冲激响应到卷积核这层逻辑其实一模一样线性时不变系统的输出写成离散形式就是 y[n] Σ h[k]·x[n-k]h 是冲激响应它完整描述了系统对任意输入的反应。你再看一层一维卷积y[n] σ( Σ w[k]·x[n-k] b )除了那个非线性激活 σ 和偏置 b剩下的一模一样。差别只有一处传统做法里 h 是你按指标设计出来的比如要一个截止 2kHz 的巴特沃斯低通你算阶数、查表、双线性变换而在网络里w 是让梯度去调的。这一层认识带来的第一个实操结论是输出通道数不是随手拍的它对应你希望这一层提取多少种不同的频率选择性。传统滤波器组里你要设计 8 个带通就并 8 个滤波器卷积层设 out_channels16相当于让网络自己去找 16 种它认为有用的频率响应组合。所以当你发现某层输出通道从 64 砍到 16 之后指标几乎没掉通常说明你给的数据里可分辨的频率结构本来就不多多出来的通道在学噪声。第二个结论来自卷积定理时域卷积等于频域相乘。一个卷积核在频域上就是一条频率响应曲线。这也解释了为什么浅层小核的 CNN 提取出来的特征普遍偏平滑——随机初始化的一小组 3 点核其频率响应在低频段增益大、高频段衰减快行为上接近低通。这不是玄学是初始化分布本身决定的。1.2 采样率决定了你的网络能看到什么深度补不回来采样定理这条红线我在项目评审里见过太多次被忽略。fs ≥ 2·f_max 是硬约束一旦违反高频分量会折叠到低频而且这个折叠是不可逆的——你在频谱上根本分不清那个 1.2kHz 的峰是真实存在的还是 3.6kHz 的东西折下来的fs4kHz 时。模型再深、参数再多也没法从被污染的数据里把真相抠出来。举个具体的手头某产线的振动采集卡固定 12800 Hz 采样那它的可用带宽就是 6400 Hz。如果你关心的某个故障特征频率落在 7kHz 附近这张卡就废了换模型没用得换硬件。这个判断要在项目第一天做完不是训完模型发现效果差才回头查。第二层陷阱藏在网络内部下采样等价于降低采样率。池化层和 stride2 的卷积都是在减少时间轴上的采样点。如果在下采样之前没有做抗混叠低通高频能量就会折叠进低频模型学到的是一堆假特征。经典 CNN 里 stride2 的卷积并没有内置低通这一点和信号处理教材里的标准下采样链路不一样。我自己的习惯是要么在 stride2 前面显式加一个可分离的低通比如 3 点或 5 点的平滑核要么干脆用空洞卷积把感受野撑大、全程不做下采样。后者在振动信号这种长度几万点的输入上特别省心。1.3 系统的稳定性和训练的稳定性是同一件事的两面控制里判断离散系统稳定看极点是否都在单位圆内训练里判断能不能收敛看更新步长是否让损失单调下降。这两件事的数学结构高度相似。学习率给太大损失曲线开始等幅振荡甚至发散这就是一个增益过大的反馈系统在自激梯度爆炸对应系统发散梯度裁剪相当于在回路里加一个饱和限幅器把过大的误差信号削掉。我习惯用这套类比去给非信号背景的同事解释几个训练技巧为什么有效训练现象系统侧对应常规处理手段损失等幅震荡不下降环路增益过大临界稳定学习率减半或加 warmup损失突然变 NaN系统发散梯度裁剪、降低 lr、检查 log(0)前期收敛后期平台增益不足响应迟钝换自适应优化器、加余弦退火不同 batch 间波动大输入动态范围过大批归一化等价于自动增益控制深层网络训不动信号逐级衰减残差连接等价于加前馈旁路批归一化这个类比我觉得特别贴切它统计每个通道在一个 batch 上的均值方差并做归一化本质上就是一个自动增益控制AGC把输入电平拉回一个固定范围让后级电路工作在线性区。理解了这一点你也就明白为什么推理时必须用滑动统计量而不是当前 batch 的统计量——AGC 的增益不能随当前输入跳变否则输出会跟着抖。2. 从传感器到张量输入链路的每一环都在决定上限模型结构决定的是你能不能逼近那个映射而输入链路决定的是那个映射还在不在数据里。我做过的不下十个信号类项目里收益最大的改动几乎都发生在前处理阶段而不是换骨干网络。2.1 采集端那些改不动但必须接受的硬约束动手建模之前有四个参数必须先问清楚问不清楚后面全是白干。采样率与抗混叠滤波。除了上面说的带宽限制还要确认采集卡前端有没有模拟抗混叠滤波器。便宜的采集卡经常省掉这一级导致高于 fs/2 的能量直接折叠。这种情况你在频谱上看不出异常但模型的泛化会莫名其妙地差。量化位数与有效位数。理论量化信噪比约 6.02N 1.76 dB12 位对应约 74 dB16 位约 98 dB。但这是理想值实际的有效位数ENOB往往低 1 到 2 位。如果你的目标信号幅值只占满量程的 5%那实际能用的动态范围还要再掉 26 dB。这直接决定了微弱的早期故障特征到底在不在数据里。传感器带宽与谐振点。加速度计的谐振频率通常标称在几十 kHz工作频段要远离它。曾经有个项目因为把关注频段设到了谐振点附近模型学到的全是安装共振换一台设备立刻失效。时钟同步与丢包。多通道采集时通道间的时间对齐误差会直接破坏空间特征。如果链路有丢包又没有打时间戳帧对齐就会错位而这种错位在数据里看起来跟正常波动没什么区别极难排查。我在接手一个旧数据集时就吃过这个亏最后是靠检查帧间隔的直方图才发现有 0.3% 的帧长度不对。2.2 预处理为什么我坚持先做分帧、去直流和归一化去直流。传感器偏置、放大器失调都会带来一个很大的直流分量。它本身不携带判别信息却会吃掉动态范围还会让后续的频域分析在 0 Hz 处出现一个巨峰掩盖真正的低频成分。做法很简单减均值或者上一个截止 1~2 Hz 的高通。去工频干扰。50Hz 及其谐波在很多场景下是全场最强的分量。如果不处理模型十有八九会去学它——因为它在不同类别之间确实可能存在细微差异比如负载不同导致干扰幅度不同于是模型学了一个跟故障毫无关系的捷径。用陷波器或者谱减法都行我一般用二阶 IIR 陷波Q 值取 30 左右带宽够窄不会伤到邻近频段。分帧加窗。做时频分析必须加窗汉宁窗是最常用的折中。这里的关键参数是窗长 N 和帧移 H它们分别决定频率分辨率和时间分辨率两者此消彼长fs 12800 # 采样率 N 512 # 窗长 hop 160 # 帧移 print(频率分辨率 Δf , fs / N, Hz) # 25.0 Hz print(时间分辨率 Δt , hop / fs * 1000, ms) # 12.5 ms print(单帧时长 , N / fs * 1000, ms) # 40.0 ms25Hz 的频率分辨率意味着间隔小于 25Hz 的两个谱峰你是分不开的。如果故障特征频率和它的边带间隔只有 10Hz就得把 N 加大到 1024 甚至 2048代价是时间分辨率变粗短时冲击会被抹平。这个权衡没有标准答案得看你的物理对象冲击型故障要时间精度稳态调制型故障要频率精度。归一化这里有个大坑。逐样本做 z-score 是很多人的默认操作但如果你的任务跟幅值本身有关比如故障严重程度分级、磨损量回归逐样本标准化会把整体幅值大小这个最有判别力的信息直接抹平等于把标签给删了。正确做法是用训练集全局统计量做标准化或者把 RMS、峰值这类幅值特征单独保留一个通道送进去。我用逐样本归一化毁掉过一次严重度回归任务loss 卡在某个值死活下不去查了两天才反应过来。2.3 特征形态取舍原始波形、手工特征还是时频图这三条路线各有适用面别迷信端到端一定更好。输入形态优势短板适用场景原始波形 一维卷积保留全部相位与瞬态信息端到端需要很长感受野才能看到周期性参数量大冲击、瞬态、短时事件检测手工特征RMS、峭度、峰值因子、包络谱可解释、样本需求少、计算极省依赖领域知识容易漏掉未预期的模式样本稀缺、需要给出判据依据时频图 二维卷积兼顾时间与频率迁移学习方便分辨率受不确定性原理限制参数量大非平稳信号、变工况学习式前端可训练滤波器组自动适配频段需要更多数据收敛慢数据充足、频段未知峭度这个特征值得单独说一句它对冲击型故障极其敏感正常信号接近高斯分布峭度约为 3一旦出现周期性冲击峭度会明显抬升。但峭度对单个野值也敏感所以通常会配合中值滤波或者分位数截断一起用。包络解调希尔伯特变换取包络再做谱分析在滚动体早期故障上依然是性价比最高的方法之一我现在的常规做法是把包络谱作为额外通道和原始波形一起喂进网络等于把领域知识以特征的形式注入收敛速度和最终指标都比纯端到端好一截。3. 模型选型与参数核算别靠感觉堆层数到了选结构这一步最常见的错误是别人用 ResNet 效果好我也上 ResNet。信号类任务的结构选择应该由物理尺度倒推而不是由论文排行榜倒推。3.1 一维卷积感受野和参数量的手算过程感受野receptive field指的是一层输出上的一个点能看到输入的多少个原始采样点。这个数字如果小于你信号中关键周期结构的长度网络在物理上就不可能学到那个周期性。递推公式很好记每经过一层感受野增加 (k-1)·d·S其中 k 是核长d 是空洞率S 是这一层之前所有 stride 的乘积。def receptive_field(layers): rf, s_prod 1, 1 for k, s, d in layers: rf (k - 1) * d * s_prod s_prod * s return rf # 四层 3 点卷积stride1无空洞 print(receptive_field([(3,1,1)]*4)) # 9 # 第一层换成 15 点大核 print(receptive_field([(15,1,1)] [(3,1,1)]*3)) # 27 # 用空洞卷积撑感受野3,1,2 / 3,1,4 / 3,1,8 print(receptive_field([(3,1,2),(3,1,4),(3,1,8)])) # 43现在算一个真实的例子。fs12800转频 30Hz一个机械旋转周期对应 12800/30 ≈ 427 个采样点。要让网络稳定地看到 2 到 3 个周期的周期性感受野至少得在 1000 点量级。四层 3 点核的感受野只有 9 点它看到的是 0.7 毫秒的信号片段连一个完整的冲击都包不住。这就是为什么浅层小核 CNN 在振动信号上经常输给手工特征 浅层分类器——不是网络不行是它根本没看到该看的东西。要撑到 1000 点量级三条路第一层用大核比如 64 点堆很多层或者用空洞卷积按 2 的幂次递增。空洞卷积的性价比最高三层就能到 43五层到 171七层到 683参数量还很小。参数量按 (k · C_in 1) · C_out 算。第一层 k15、输入 1 通道、输出 16 通道(15×11)×16 256 个参数。第二层 k7、16 进 32 出(7×161)×32 3616。第三层 k5、32 进 64 出(5×321)×64 10304。三层加起来不到一万五端侧部署毫无压力。计算量MACs按 L_out × k × C_in × C_out 估。假设输入 128000 点第一层 stride1、k15、1→16MACs ≈ 128000×15×1×16 30.7M。这个量级在普通 MCU 上跑一次要几百毫秒实时性要求高的场合得靠降采样或者减少通道。3.2 时频图配二维卷积的适用边界时频图的好处是把频率随时间怎么变显式画出来了二维卷积在原图上的局部性正好对应时频平面上的局部能量团这个归纳偏置是天然契合的。代价是分辨率的固有限制以及内存开销。接着上面的参数算10 秒信号、fs12800、N512、hop160得到 800 帧 × 257 个频点。单样本 float32 占 800×257×4 ≈ 822KBbatch 32 就是 26MB显存完全可控。但如果信号是 60 秒帧数涨到 4800单样本接近 5MBbatch 就上不去了。这时候要么降采样后再做时频分析要么用更长的 hop。还有两个必做的小处理。一是转 dB原始幅度谱的动态范围经常跨四五个数量级直接送进网络会让梯度被大值主导取 20log10 之后压到 60~80dB 区间就温和多了。二是统一尺寸不同时长的样本要裁或者补到同一帧数补齐的部分用极小值比如 -100dB填充并且单独给一个掩码通道别用 0 填——在 dB 域里 0 是个很大的值会污染边界。小波变换CWT在冲击型信号上往往比 STFT 更合适因为它的窗长随频率自适应低频用长窗高频用短窗。但尺度怎么选是个麻烦事选少了覆盖不全选多了图很大。我的经验是按你关心的特征频率范围反推尺度序列别用默认的。3.3 长序列建模循环结构与注意力机制的取舍信号动辄几万点序列建模的选择直接决定可行性。循环结构LSTM/GRU理论上能记很久实际受梯度衰减影响有效记忆长度往往只有几十到上百步。它还有个硬伤是不能并行训练慢。在采样率 12800 的数据上即使先降采样 64 倍10 秒也是 2000 步训起来会很吃力。我现在基本不用纯 RNN 处理原始信号只在特征序列比如每帧一个特征向量上用。时间卷积网络TCN本质是空洞因果卷积堆叠是我更喜欢的方案感受野随层数指数增长可以并行梯度路径短。上面算过七层空洞卷积感受野能到 683 点堆到十层就是几千点覆盖几个旋转周期毫无问题。注意力机制的问题在复杂度——标准自注意力的计算量是 O(L²)。L128000 的时候这个数字没有任何实际意义。可行的做法是先做 patch embedding把 256 点合成一个 token序列长度降到 500再上注意力。这种分块 注意力的结构在长信号上效果不错但要注意块与块之间的边界信息会丢需要在分块时保留重叠。4. 完整跑通一遍振动信号四分类的实操记录下面这条链路是我这两年的标准模板换个信号类型基本只需要改前处理。任务设定四类状态正常、内圈、外圈、滚动体采样率 12800单条记录 10 秒一共 240 条记录。4.1 划分数据集先切分组再切窗口这一步是整个项目里最容易埋雷的地方我把话说重一点随机切窗口的划分方式几乎一定会给你一个虚高的准确率。原因很直白。窗长 2048、hop 512 意味着相邻两个窗口共享 1536 个采样点重叠率 75%。如果把这些窗口随机打乱分到训练集和测试集测试集里的样本跟训练集里的样本有 3/4 的内容是完全一样的模型只要记住片段就能答对。这种情况下测出来的 99% 没有任何意义。正确的顺序是先按物理来源分组再在组内切窗口。分组的维度可以是采集批次、设备编号、工况、时间段看你的目标是什么。如果目标是同一台设备上的状态判别按时间段分组前 70% 时间训练、后 30% 测试是最贴近上线的如果目标是跨设备泛化那必须按设备编号分组一台设备的数据只能整块出现在一边。# 分组划分的骨架逻辑 groups {} # key: 记录ID, value: 该记录的窗口列表 for rec in all_records: groups[rec.id] sliding_window(rec.signal, win2048, hop512) # 按记录ID划分而不是按窗口划分 train_ids, test_ids split_by_group(list(groups.keys()), ratio0.7, seed42) train_set [w for i in train_ids for w in groups[i]] test_set [w for i in test_ids for w in groups[i]]另外提醒一句数据增强。加噪、时间平移、幅值缩放这些增强本身没问题但如果同一段原始信号的原始版和增强版分别落在训练集和测试集那还是泄漏。增强只能在训练集内部做。4.2 训练配置与损失曲线判读配置上我没有太多花活Adam初始学习率 1e-3余弦退火到 1e-5batch 64权重衰减 1e-4早停耐心 15 个 epoch。损失用带类别权重的交叉熵因为四类样本量通常是 6:2:1:1 这种分布。类别权重按样本数的倒数归一化或者直接用 focal loss 把易分样本的权重压下去。损失曲线的判读我总结成四句话非常好用训练降、验证也降最后一起平正常收敛看最终指标就行。训练降、验证不降反升过拟合或者存在泄漏导致验证集分布和训练集不一致先查泄漏再谈正则。训练也不降八成是标签、输入范围或者学习率的问题跟模型结构没关系别急着加层。两条曲线剧烈锯齿学习率太大或 batch 太小先降 lr 再加 batch。还有一个常被忽略的检查拿一小批数据比如 8 个样本去故意过拟合。一个正常的网络应该能在几百步内把这一小批的 loss 压到接近 0。如果压不下去说明前向或反向链路有 bug这时候调任何超参都是浪费时间。这个检查我每次都做五分钟能省掉半天。4.3 评估与混淆矩阵准确率不是终点工业场景里四类的误判代价完全不对称。把故障判成正常漏报可能意味着停机损失甚至安全事故把正常判成故障误报只是多一次人工复核。所以整体准确率这个指标基本没有决策价值得看每一类的召回率。真实 \ 预测正常内圈外圈滚动体该类召回正常581010.967内圈217100.850外圈011900.950滚动体31060.600看这张表整体准确率是 (5817196)/60 0.967很漂亮。但滚动体这一类召回只有 0.610 个样本里错了 4 个而且三个被判成了正常——这是最危险的错误方向。真正该做的是给滚动体类加权、补样本或者单独分析这一类错在哪。滚动体故障的特征频率通常最弱信号里被其他分量淹没往往需要靠包络谱才能提出来这时候加一个包络谱通道的效果比调网络结构明显得多。评估阶段还要固定一个黄金测试集任何改动都要在这上面回归一遍。别每次换模型就换测试集那样你永远不知道自己是不是真的进步了。5. 排查实录那些看起来收敛了其实没学到东西的情况5.1 准确率虚高的五个常见来源现象根因排查方法首轮就 95%窗口重叠泄漏检查划分是否按记录分组算一下训练测试的样本相关性验证比训练还高训练时做了强增强验证没有确认增强只在训练分支生效换随机种子指标跳变 10 个点测试集太小或类别极不平衡用分层抽样报告多次运行的均值方差某些类别永远全对该类的采集条件与其它类不同不同设备、不同日期按条件分组重做划分上线后立刻掉点训练特征里混入了未来信息检查每个特征的计算窗口是否只用了当前及历史数据最后一条我要展开说一下。有些项目会用整段信号统计量做归一化比如用一整条 10 秒记录的均值方差去标准化这条记录。离线看没问题但上线时你手上只有最近 1 秒的数据根本算不出整段统计量只能用历史统计量近似分布立刻就偏了。任何用到未来数据的特征在线下都会显得很好在线上都会崩。5.2 不收敛与数值异常的定位顺序遇到 loss 不动或者直接 NaN按这个顺序查从便宜到贵标签对不对。把标签随机打乱重新训如果指标还在 90% 以上说明你的数据里有泄漏模型根本没在看标签。输入范围。打印每个通道的 min/max/mean/std。出现 inf、nan或者某个通道量级是其它通道的 1000 倍先修这个。log 和除法的边界。对数谱里出现 -inf 基本都是幅度为 0 导致的加个 1e-10 的 eps 就好。归一化时分母为 0 同理。学习率。从 1e-4 开始试别一上来就 1e-2。半精度溢出。用 fp16 训练时损失缩放没配好会在几十步后突然 NaN换个稳定的损失缩放策略或者先用 fp32 跑通。我遇到最隐蔽的一次是窗函数导致的。当时用了一个自定义的窗两端衰减到 0结果某些帧的能量算出来接近 0转 dB 之后是 -inf前向传播里出现了 nan但因为用了梯度裁剪nan 没有立刻扩散而是过了几百步才把整个模型污染掉。这种问题光看 loss 曲线很难发现得加一个每步检查参数是否有限的断言。5.3 上线之后掉点训练与推理链路不一致离线 96%、线上 78% 这种事九成九是链路不一致而不是模型不行。常见的差异点重采样实现不同。离线用某个库做 16k 到 12.8k 的重采样线上用另一个库两者的滤波器不同频响在过渡带差好几个 dB。归一化统计量没固化。训练时算的均值方差没存进配置文件推理时用了当前 batch 的统计量。窗函数和帧移不一致。离线 hop160线上为了省算力改成 320时频图的尺寸就变了网络输入对不上。通道顺序或单位不一致。训练时加速度单位是 g线上传的是 m/s²差 9.8 倍虽然归一化后可能看不出来但和幅值相关的特征全废了。量化误差。int8 量化后某些层的输出分布被截断尤其是激活值动态范围大的层掉点会很明显。我的应对办法是建一个黄金样本回归集固定 100 条样本及其期望输出把这 100 条样本的原始数据、前处理中间结果每一级的统计量、最终 logits 全部存档。每次改动代码或者换推理引擎跑一遍这个集合任何一级的输出偏差超过阈值就报警。这套东西搭起来大概一天之后每次发版能省掉无数次线上怎么又掉了的扯皮。6. 落地侧的几件小事延迟、内存与可维护性6.1 推理预算怎么估先算 MACs再按目标硬件的实际算力打折。经验值是普通 ARM 核上每秒能跑 1 到 3 亿次乘加带 SIMD 或者 NPU 的能到十倍以上。举个小模型的例子L_in 12800 # 1 秒数据 layers [(15, 4, 1, 16), # (k, stride, C_in, C_out) (7, 2, 16, 32), (5, 2, 32, 64)] L L_in total 0 for k, s, cin, cout in layers: L L // s macs L * k * cin * cout total macs print(f输出长度 {L}, 本层 MACs {macs/1e6:.2f}M) print(f合计 {total/1e6:.2f}M MACs)这个规模大概几十 M 的 MACs1 秒的数据在普通 ARM 核上跑几十毫秒实时性没问题。但如果输入改成 10 秒、且第一层不做降采样MACs 直接涨一个数量级就得重新考虑了。降采样要放在网络最前面越早越省。int8 量化通常能带来 2 到 4 倍的加速和 4 倍的内存节省代价是百分之零点几到几个点的精度损失。做量化一定要用真实分布的校准集别用随机数否则激活值的截断范围完全不对。6.2 版本与数据资产的管理习惯这个东西听起来不性感但它决定了一个模型能不能维护两年。我的做法是把三样东西绑定在一起打版本号数据版本哪批采集、哪次清洗、特征版本前处理参数fs、N、hop、滤波器系数、模型版本结构、权重、量化方式。任何一次上线这三个版本号必须能对上。曾经有一次线上指标莫名其妙掉了两个点查了两天才发现是有人更新了陷波器的 Q 值特征版本变了但没通知建模那边。另外建议把前处理参数全部写进配置文件而不是代码里并且给每个参数写清楚物理含义和取值范围。信号的参数之间耦合很紧fs 变了 N 就得跟着变写成硬编码迟早出事。我个人在实际操作里的体会是信号类项目里模型的贡献大概占三成剩下七成在数据质量、前处理设计和评估方式上。见过太多人一上来就调网络结构把 ResNet 换成 Transformer 再换回来指标在原地打转而真正的问题是第一层感受野只有 9 个点、或者测试集跟训练集共享了 75% 的采样点。先把这两件事查清楚再谈模型效率会高得多。