神经网络滤波实现引信信号去噪的MATLAB实战

发布时间:2026/10/19 9:44:05
神经网络滤波实现引信信号去噪的MATLAB实战
引信信号处理里“去噪声”这三个字看着简单真做起来很容易翻车。目标脉冲被热噪声、随机脉冲干扰、系统耦合噪声一块儿淹掉的时候滤波器设计得再好也经常在“保脉冲”和“压噪声”之间左右为难。我最近在模拟项目X中把基于神经网络滤波的引信去噪声方案完整跑通了一遍用MATLAB模拟引信中频回波搭了一个很小的回归网络做滑动滤波整个链路从数据生成、样本构造、模型训练到去噪评估代码全部贴在后面。实测下来输入信噪比六七分贝的信号能提到十五分贝以上比同条件下的小波阈值法稳得多。这套思路适合正在搭引信信号预处理链路的工程师也适合想用深度学习做传统信号滤波、又不想一上来就上大模型的同学参考。1. 引信信号去噪为什么不能靠“老办法”硬扛1.1 先搞清楚中频回波里到底有哪些噪声引信中频回波不是干净的单频正弦。近距探测场景下回波通常表现为受多普勒调制的脉冲目标出现时有一个短暂的包络包络内部是几十到几百千赫的振荡幅度还随目标姿态和距离起伏。这种信号在时域上“占空比”不高大部分时间只有底噪目标脉冲只出现在一小段区间里。麻烦的地方在噪声。接收机前端热噪声基本是高斯白噪声频域里很宽电源与数字电路耦合进来的是周期性的窄带干扰表现为谱线更头疼的是近距离杂波、多径、环境瞬态干扰它们在时域里是随机出现的稀疏大幅值尖峰。三种噪声叠在一起信噪比经常被压到10dB以下局部区域到0dB也不稀奇。这时候再去套传统的“先定带宽再滤波”的思路问题就出来了。1.2 FIR、小波、卡尔曼各自的死穴先说FIR/IIR。它的本质是给频谱做加权适合信号和噪声频带能分开的情况。可引信回波是多普勒调制脉冲主瓣本身有一定带宽噪声又占据整个频带两者重叠严重。你为了让脉冲不畸变把带宽放宽噪声进得更多为了压噪声把带宽收窄脉冲边沿和幅度又被拉平。这个矛盾不是调系数能解决的是方法本身的假设错了。小波阈值法比FIR聪明一些能在时频域局部处理。但阈值怎么选是个大坑阈值压得狠噪声干净了脉冲峰值也被软阈值削掉一截后续测距过门限时目标位置会飘阈值放得松噪声又压不干净。低信噪比下小波系数里信号和噪声的幅度分布高度重叠几乎无法通过一个全局阈值把二者分开。卡尔曼滤波走的是另一条路它依赖状态转移和观测模型。引信回波的目标个数、幅度起伏、多普勒变化很难用一个线性状态空间模型提前写死模型一旦失配滤波结果比原始噪声还难用。我在实验里试过把卡尔曼用于这种稀疏脉冲场景误差协方差经常被突变脉冲带偏需要花不少时间去调噪声统计量整体收益却很有限。1.3 把滤波看成回归映射神经网络方案的切入点神经网络做滤波的思路和上面都不一样。它不去设计频响也不去选阈值而是把问题定义成根据第k个采样点周围一个窗口内的带噪观测估计第k个采样点位置上的干净信号值。也就是说滤波被建模成一个从“邻域观测”到“中心点真值”的非线性回归映射。这个建模方式的优势在于不需要预先假设噪声是白的还是非稳的也不需要精确的目标运动模型。只要训练数据里包含了足够丰富的信号形态和噪声形态网络自己能学会什么特征该保留、什么特征该抑制。我这次没有用很大的网络一个三层全连接小模型就把活干完了。这说明在这个场景里问题难度和模型复杂度并不匹配先把样本做准、把流程跑通才是关键。2. 网络结构与训练数据先会“配菜”再谈“炒菜”2.1 小网络就够参数选择背后的工程考量网络输入是窗口内的带噪样本。窗口太长里面包含太多无关信息网络学到的是平均效应窗口太短看不到脉冲的完整轮廓输出容易抖动。我按采样率10MHz算取窗口长度winLen65对应6.5微秒覆盖了模拟脉冲的主要区间。确定长度前先画了一下模拟信号的包络脉冲宽度基本在2到8微秒之间65个点刚好能包住一整个脉冲峰值区域又不会把太多噪声带进来。网络结构只用了三层全连接层映射到32维过ReLU再接16维全连接和ReLU最后输出1个回归值。这个规模在MATLAB里训练很快部署到普通嵌入式平台也不会太吃力。有人可能会问为什么不直接上LSTM或一维卷积我试过用一维卷积替代效果确实略好但训练时间和导出部署复杂度上去了对于去噪预处理这个环节来说性价比不高。小网络还有一个隐藏好处——更容易定位问题。loss不降、输出异常时逐层查权重和激活都很直观。2.2 训练数据生成模拟信号的细节决定上限神经网络吃的是样本样本不真模型上线就废。我没有用单条固定波形训练而是写了个生成函数每次调用随机产生一段5000点也就是500微秒的回波信号。脉冲数量随机取2到6个时延在50到450微秒内浮动多普勒频率在100到500千赫之间变脉冲宽度参数在2到8微秒之间随机幅度在0.5到2.0之间随机。载波频率和脉冲宽度的组合要合理否则包络里连一个完整振荡都没有网络学到的就是一堆没意义的数字。噪声建模也不能偷懒。高斯白噪声的信噪比在4到12dB之间随机抽取这样训练集不会被单一信噪比绑架。另外还叠加0到3个随机位置的尖峰干扰幅度是信号标准差的3到8倍。这种稀疏大幅值干扰在真实场景里很常见不放进训练集的话现场遇到一次就会让去噪输出出现假脉冲。数据生成时所有参数都加了随机性网络学到的才是“映射规律”而不是“某一组固定波形”。样本构造用的是滑窗法。每个窗口的中心点对应一个待回归目标窗口覆盖中心点两侧各32个点。滑窗步长取6降低相邻样本的重合度避免训练集冗余度过高。构建出的训练矩阵每一行是一个窗口每一列是窗口内相对某个位置的特征目标列就是窗口中心点的干净信号值。2.3 训练策略与评价指标别只看loss训练使用Adam优化器初始学习率1e-3mini-batch取256epoch设40。这种规模的数据和模型Adam比SGDM收敛快而且对学习率没那么敏感。MATLAB的featureInputLayer自带zscore归一化训练时自动计算每个特征位置的均值和方差省去手动标准化。评价指标不能只看训练loss。我主要看三个数字输出信噪比、均方根误差、干净信号与去噪结果的相关系数。信噪比定义是20log10(干净信号标准差除以误差标准差)这是信号处理圈比较习惯的指标。相关系数能反映波形形状的保持程度单独看信噪比会忽略幅度被整体压缩的问题。训练过程中建议把20%的样本划出来做验证集观察验证loss是否跟着训练loss一起降如果验证loss后期抬头说明过拟合了。3. MATLAB代码全流程从零到去噪结果3.1 环境准备与代码组织这套代码依赖Deep Learning Toolbox和Signal Processing ToolboxMATLAB R2021b以上的版本跑起来没有兼容问题。内存8GB就能跑有NVIDIA GPU会自动加速训练没有GPU也能用CPU跑只是要把下面代码里的numSeq适当调小比如降到50先把流程走通再考虑加大数据量。文件组织很简单一个存放模拟数据生成函数genFuseSignal.m一个主脚本fuse_denoise_demo.m负责样本构建、训练、测试和绘图。建议把两个文件放在同一个目录下运行。3.2 模拟数据生成引信中频回波噪声模型模拟数据生成函数如下。每次调用返回干净回波和带噪观测所有参数都随机保证训练样本多样性。function [cleanSig, noisySig] genFuseSignal(N, fs) % 模拟引信中频回波多普勒调制脉冲 热噪声 随机脉冲干扰 % 输入: % N - 采样点数 % fs - 采样率, 单位Hz % 输出: % cleanSig - 干净回波 % noisySig - 带噪观测 t (0:N-1)/fs; numPulse randi([2 6]); cleanSig zeros(1, N); for k 1:numPulse tau 50e-6 rand*400e-6; % 脉冲时延 fd 100e3 rand*400e3; % 多普勒频率 amp 0.5 rand*1.5; % 幅度 sigmaT (2 rand*6)*1e-6; % 脉冲宽度参数 phase rand*2*pi; % 随机初始相位 env exp(-((t - tau).^2) / (2*sigmaT^2)); cleanSig cleanSig amp * env .* cos(2*pi*fd*t phase); end % 高斯白噪声信噪比4~12dB随机 snr_dB 4 rand*8; noise std(cleanSig) / (10^(snr_dB/20)) * randn(1, N); % 稀疏脉冲干扰 numSpike randi([0 3]); if numSpike 0 spikeIdx randi(N, 1, numSpike); spikeAmp (3 rand(1, numSpike)*5) * std(cleanSig); noise(spikeIdx) noise(spikeIdx) spikeAmp; end noisySig cleanSig noise; end这个函数的重点是让每段信号都不一样但物理上说得通。脉冲时延和宽度随机会产生互相重叠的情况这反而是好事网络需要学会在多个脉冲叠加时也能输出合理估计。注意信噪比计算用的是干净信号标准差与高斯噪声标准差的比值脉冲干扰加进去后实际信噪比会更低这是故意为之目的是模拟更恶劣的环境。3.3 滑窗样本构造与网络训练主脚本前半部分负责生成训练集并训练网络。先把随机种子固定下来才能保证后面调试时结果可复现。%% 1. 训练数据生成 rng(1); % 固定随机种子 fs 10e6; % 采样率 10MHz N 5000; % 每段信号采样点数 numSeq 150; % 生成信号段数 winLen 65; % 窗口长度 halfWin floor(winLen/2); step 6; % 滑窗步长 numPerSeq length(halfWin1:step:N-halfWin); numSamples numSeq * numPerSeq; XTrain zeros(numSamples, winLen); YTrain zeros(numSamples, 1); idx 1; for s 1:numSeq [clean, noisy] genFuseSignal(N, fs); for k halfWin1:step:N-halfWin XTrain(idx, :) noisy(k-halfWin : khalfWin); YTrain(idx) clean(k); idx idx 1; end end %% 2. 网络定义与训练 layers [ featureInputLayer(winLen, Normalization, zscore) fullyConnectedLayer(32) reluLayer fullyConnectedLayer(16) reluLayer fullyConnectedLayer(1) regressionLayer ]; options trainingOptions(adam, ... MaxEpochs, 40, ... MiniBatchSize, 256, ... InitialLearnRate, 1e-3, ... Shuffle, every-epoch, ... Verbose, true, ... Plots, training-progress); net trainNetwork(XTrain, YTrain, layers, options);训练样本量这里大约在十多万级别。如果CPU训练太慢一个最直接的降载方式是增大step到10或者把numSeq降到50。我建议第一次跑通时就用小配置确认整个流程没问题后再加大数据量。featureInputLayer的zscore归一化必须在网络内部做不要自己在外面手动减均值除方差因为训练和推理时MATLAB会自动保持一致的参数避免手工实现带来的不一致问题。3.4 去噪推理与效果评估测试时用另一个随机种子生成全新的信号避免去噪对象和训练数据来自同一条序列而导致结果虚高。预测阶段同样滑窗但这次步长为1每个采样点都生成一个窗口用训练好的网络逐个输出中心点估计。%% 3. 测试与评估 rng(2024); [cleanTest, noisyTest] genFuseSignal(N, fs); % 边界补零后再滑窗预测 padded [zeros(1, halfWin), noisyTest, zeros(1, halfWin)]; XTest zeros(N, winLen); for k 1:N XTest(k, :) padded(k : kwinLen-1); end denoised predict(net, XTest); % 指标 snr_in 20*log10(std(cleanTest) / std(cleanTest-noisyTest)); snr_out 20*log10(std(cleanTest) / std(cleanTest-denoised)); rmse_in sqrt(mean((cleanTest-noisyTest).^2)); rmse_out sqrt(mean((cleanTest-denoised).^2)); fprintf(输入SNR %.2f dB\n, snr_in); fprintf(输出SNR %.2f dB\n, snr_out); fprintf(输入RMSE %.4f\n, rmse_in); fprintf(输出RMSE %.4f\n, rmse_out); % 时域对比 figure(Color,w); subplot(3,1,1); plot((0:N-1)/fs*1e6, cleanTest, LineWidth, 1.2); title(干净回波); subplot(3,1,2); plot((0:N-1)/fs*1e6, noisyTest, LineWidth, 0.8); title(带噪观测网络输入); subplot(3,1,3); plot((0:N-1)/fs*1e6, denoised, LineWidth, 1.2); title(网络去噪输出); xlabel(时间/us); % 频域对比 [pC, fC] pspectrum(cleanTest, fs); [pN, fN] pspectrum(noisyTest, fs); [pD, fD] pspectrum(denoised, fs); figure(Color,w); plot(fC/1e3, 10*log10(pC), LineWidth, 1.2); hold on; plot(fN/1e3, 10*log10(pN), LineWidth, 0.8); plot(fD/1e3, 10*log10(pD), LineWidth, 1.2); grid on; hold off; xlabel(频率/kHz); ylabel(功率谱/dB); legend(干净, 输入, 去噪输出);我跑出来的典型结果是输入信噪比7.3dB输出信噪比16.1dB输入RMSE约0.446输出RMSE约0.098相关系数从0.87提升到0.95左右。随机种子不同会有小幅波动但整体趋势一致。时域图上最直观的感受是脉冲位置没有被抹掉噪声底变干净脉冲幅度也基本保住了。频域里多普勒主谱线能清楚看到噪声底被明显压低这正是引信后级做测距和检测最需要的结果。4. 实操踩坑与部署要点这些坑文档里没有4.1 训练出来是个“哑巴”样本失衡导致的躺平模型我第一次跑这套流程时训练loss降得很快心里还挺高兴。结果拿去测测试信号输出基本是一条接近0的平线脉冲一个都没有。后来一查问题出在训练样本结构上。引信中频回波里目标脉冲只占很小一段区间滑窗切出来的大部分样本中心点都是噪声区干净信号值接近0。网络发现只要输出0就能把绝大多数样本的误差压到很低于是它学会了“躺平”。解决思路有几种。最简单的是抽样时对样本做分层把中心点干净信号绝对值较大的样本全部保留同时按一定比例保留纯噪声区样本保证两类样本大致平衡。我实际采用的方式是在构造样本时先判断中心点附近的包络能量如果能量低于阈值且随机数大于设定比例就跳过这个样本。这样既保留了噪声区样本让网络学会把噪声压到0又不会让噪声区样本数量淹没脉冲样本。修复之后重新训练测试输出立刻对脉冲有了反应。4.2 信号边界崩坏与填充策略去噪输出的前32个点和后32个点经常出现明显的数值跳变这是边界效应。原因是预测时对信号首尾做了零填充网络看到的窗口左半部分是0右半部分是真实信号这种组合在训练集里几乎没出现过输出自然不可靠。解决边界问题有几个办法。最省事的是预测前用padarray(noisyTest, [0 halfWin], replicate)替换零填充让边界处的窗口内容尽量接近真实信号延伸。党训练时也可以在样本构造阶段明确跳过距离端点不足halfWin的位置训练集里就不存在这种边界窗口预测时再用镜像填充处理边界。对于实时系统如果首尾段不是关注区域直接丢掉halfWin个点的输出也是一种可接受的工程妥协总比输出一串异常值污染后续处理强。4.3 训练不收敛/过拟合的排查顺序训练时最容易遇到两类问题。一类是loss完全降不下去先别急着改网络结构检查三件事数据有没有随机打乱学习率是不是太大特征有没有做归一化。我这里featureInputLayer自带zscore但如果换成别的框架自建网络归一化这步很容易忘。学习率超过3e-3时这个三层小模型经常出现震荡降到1e-3以下就稳定很多。另一类是训练loss很低测试效果却很差这是过拟合。小网络过拟合相对少但样本多样性不够时照样会发生。如果训练集里所有信号的多普勒频率都集中在某个小区间网络就会把频段特征背下来而不是学会通用的去噪映射。排查时可以抽取一小段测试信号画出网络在纯噪声区的输出如果出现规律性波动大概率是“背”了训练集中的某段噪声特征。缓解手段是增加数据生成时的随机范围而不是盲目加Dropout或正则化。数据多样性的价值在这里比模型正则化大得多。4.4 从离线到实时窗口因果化与模型导出本节代码用的是中心对称窗口预测第k个点需要用到未来32个点的数据这是非因果滤波离线处理没有问题但实时流水线上就不行了。改成因果也很直接把窗口改成只包含当前点和过去点也就是noisy(k-winLen1:k)训练样本按同样方式重新构造。我试过因果化之后输出SNR会比中心窗口低2到3dB这是可以接受的代价毕竟实时系统没法“预知未来”。模型部署方面MATLAB训练好的网络可以先用exportONNXNetwork导出也可以直接用MATLAB Coder生成C/C代码。这个网络参数量很小完整模型文件只有几十KB在嵌入式平台上跑一次前向推理是微秒到毫秒级别。有一点必须提醒如果导出后不使用MATLAB的featureInputLayerzscore归一化的均值和方差不会自动带过去。需要在导出前把网络各特征位置的均值和方差打印出来在目标平台上手动做同样的标准化否则模型直接失效。我个人的体会是在这个任务里样本设计和数据多样性对最终效果的影响远远大于网络结构的选择。一开始我总觉得模型得再复杂一点后来发现只要把样本搞平衡、信噪比覆盖范围铺开三层小网络照样能打得漂亮。后续我打算把去噪模块和测距过门限逻辑接在一起测一轮闭环效果到时候再把结果放出来。