ARTICLE DETAIL

资讯详情

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

状态估计实战:EKF、BP神经网络与粒子滤波的Matlab实现对比

状态估计实战:EKF、BP神经网络与粒子滤波的Matlab实现对比 做状态估计的同学多少都遇到过这种场景手里只有一串带噪声的观测数据——GPS轨迹、传感器读数、雷达量测都行——系统模型名义上知道是什么但真跑起来总有对不上的地方。尤其在处理非线性系统时扩展卡尔曼滤波EKF是默认选项可泰勒展开那一下的线性化误差总让人心里没底想用神经网络救场又有人跟你说BP在这个框架里能补模型误差再往后粒子滤波PF好像什么非线性非高斯都能扛。到底选哪个怎么把三者揉到一起这篇内容就是我实际在Matlab里把这些算法全部跑通后的完整记录包括BP、EKFBP、PF三种方案在同一个非线性系统上的轨迹估计实现、结果对比和踩坑细节。适合正在做状态估计、组合导航、目标跟踪或者滤波器方向研究的同学参考。下面按这个顺序展开先把状态估计这个问题的本质说清楚再讲BP神经网络在框架里的定位然后重点拆解“用EKF训练BP网络”的实现细节接着落到粒子滤波轨迹估计的Matlab实现最后上一组对比实验数据。1. 状态估计问题的本质从带噪声的观测中反推系统的真实状态1.1 贝叶斯视角状态估计到底在算什么说通俗点状态估计想知道的是在已知一堆观测数据Z1、Z2、……、Zk的情况下系统内部变量Xk的后验分布是什么。这里的“系统”可以是一架无人机的经纬度、一辆电池车的SOC、一个雷达目标的方位和速度。后验分布写成p(Xk | Z1:k)贝叶斯滤波做的就是想办法维护这个分布并且在新的观测到来时把它更新到最新时刻。整个过程只有两步第一步叫预测用状态方程把上一时刻的后验推到现在第二步叫更新拿着新观测对预测结果做修正。不管用哪种滤波本质上都在做这两件事区别只是“怎么表达和逼近那个后验分布”。EKF选择用一个高斯分布去近似PF选择用上千个加权粒子去逼近BP的思路则完全岔开了——它想直接从数据里学出映射关系这个后验分布它不管。1.2 KF、EKF、PF在一条轴上的定位如果把“后验分布能表达得多复杂”当作一把尺子卡尔曼滤波是最朴素的一头它假设状态与观测都是线性、噪声都是高斯后验分布就长成一个高斯分布均值方差两个数就够扩展卡尔曼滤波放宽到非线性系统但手段还是局部线性化也就是在估计点附近做泰勒展开后验依然用一个高斯去近似粒子滤波则站在尺子最复杂的一头它不再假设任何分布形状直接用一大把粒子去离散逼近后验理论上对强非线性、非高斯系统都能处理。三种方法在工程里怎么选我整理成下面这个表方法对系统的要求分布假设单步计算量主要劣势KF线性方程高斯低O(n^3)非线性系统直接失效EKF方程可导高斯低O(n^3)强非线性下线性化误差明显PF可采样即可任意较高O(Np·n)粒子退化、算力敏感BP滤波有训练数据看怎么配中到高数据覆盖和训练质量决定上限1.3 神经网络在状态估计框架里的切入点很多人误以为神经网络出现在这个框架里就是要替代滤波器直接输出状态其实不然。工程上更常见的做法是机理模型部分搞不清、或者根本无法写出解析表达式但你能采到状态-观测数据——这时候神经网络可以充当“黑箱环节”比如拟合未知的状态转移函数或者观测函数然后外接EKF或PF完成递推还有一种做法更激进直接拿滤波算法去训练网络本身这就是后面要重点讲的“扩展卡尔曼滤波算法的神经网络训练”把网络的全部权重当成滤波器的状态来估计。2. BP神经网络在状态估计里的业务定位2.1 黑箱估计器还是模型补充件先想清楚BP要干什么我在项目里一开始犯过糊涂上来就搭了一个8-20-1的BP网络输入历史5帧观测输出当前状态结果训练集上误差很漂亮放到测试轨迹上一按帧推就废了。原因很简单用BP直接做端到端估计器的前提是你手里有覆盖全部工况的标注数据而且观测与状态间的可逆性要足够强。对轨迹估计这种动态递推问题你等于逼一个静态映射去记一条时间序列它当然会飘。更稳的做法是把BP定位成“补偿器”——比如状态转移模型大概知道形状x(k) φ(x(k-1)) w(k)但φ的具体数学形式有偏差那就可以用BP把误差项学出来。实测下来这种用法对数据量的要求低得多而且跟滤波框架天然兼容这也是EKF-BP方案里BP承担的角色。2.2 数据怎么构造才能喂饱滤波场景下的BP这里我以文献里常用的单变量非静态增长模型UNGM为例它的状态方程和观测方程分别写成x(k) 0.5x(k-1) 25x(k-1)/(1x(k-1)^2) 8cos(1.2(k-1)) w(k-1)z(k) x(k)^2/20 v(k)w、v为高斯白噪声。这个模型的好处是解析解不存在、非线性又足够强用来对比滤波器非常合适。生成数据时要注意训练集不能只有一条轨迹。我一般随机生成50到100条轨迹每条200步把x(k-1)当输入、x(k)当输出样本之间还可以做批量打乱。如果目标是学观测函数就用x(k)作输入、z(k)作输出。有了这批数据BP网络才能覆盖足够多的状态空间而不是只记得一条轨迹。2.3 Matlab里训练BP的两种实现路线第一条路线是直接调工具箱。Matlab从2010b以后主流接口是feedforwardnet和train输入输出矩阵都遵循“每列一个样本”的约定这个跟Python的scikit-learn刚好相反容易搞反行列导致train报维度错。下面这个脚本框架可以直接跑% 生成UNGM训练数据 N 200; NumTraj 60; Xtr []; Ytr []; for tr 1:NumTraj x zeros(1,N); x(1) 0.1; for k 2:N x(k) 0.5*x(k-1) 25*x(k-1)/(1x(k-1)^2) 8*cos(1.2*(k-1)) sqrt(10)*randn; end Xtr [Xtr, x(1:N-1)]; % 输入当前状态 Ytr [Ytr, x(2:N)]; % 输出下一时刻状态 end % 建立并训练BP网络隐含层10个节点 net feedforwardnet(10, trainscg); net.trainParam.epochs 1000; net.trainParam.goal 1e-5; [net, tr] train(net, Xtr, Ytr);第二条路线是手写BP的前向传播和反向传播好处是一旦要接EKF训练你能拿到每个训练样本对应的权重梯度矩阵。Matlab自带的train接口在做标准梯度下降时很方便但你无法按样本粒度控制权重更新和协方差矩阵的演变工具箱接口的封装层级太高了。我后面的EKF训练代码就是从手写的两层网络改过来的反向传播里那层误差对权重的梯度恰好就是EKF需要的观测方程Jacobian。2.4 三个实测中必须处理的细节第一个是归一化。UNGM状态量在-20到30之间波动如果你直接把原始值喂给网络权重初始化时输出层梯度会被放大很多收敛很慢。我通常把输入输出统一缩放到[-1,1]存下缩放参数预测完再还原。第二个是过拟合。网络结构不是越宽越好这个任务8到12个隐含节点足够超过20个容易把训练轨迹彻底记下来测试时反而变差。第三个是验证集不要光看训练误差下降我把训练集切一块当验证集之后发现很多配置训练到1000轮时验证误差早已不再改善早停比硬跑完更有效。3. 用扩展卡尔曼滤波训练BP网络把权重当作状态来估计3.1 为什么放着梯度下降不用非要EKF来训练标准BP用梯度下降调权重一阶方法有两个明显缺陷一是学习率难选太大震荡太小磨叽二是顺着负梯度走的时候完全没考虑参数之间的相关性尤其网络有几十上百个权重时很多方向上梯度信息高度耦合导致收敛特别慢。EKF做权重优化是把它看成另一个动态系统把所有权重和偏置拉成一个状态向量W期望输出d是观测量网络的前向计算g(W,x)就是观测方程。EKF的更新方程里带协方差矩阵PP的逆运算隐式地给出了每一步更新方向上的二阶信息因此权重更新能同时沿着多个强相关方向调整收敛速度要明显快于纯梯度下降。说白了梯度下降只告诉你“往哪走”EKF额外告诉你“走多少”所以它自带步长调节能力。3.2 状态向量、观测方程和Jacobian的对应关系这套思路的数学结构很干净。状态方程写成W(k) W(k-1) q(k)也就是权重在训练过程中认为自己在做一个微小的随机游走q(k)的协方差Q决定权重更新的灵活度观测方程写成d(k) g(W(k), x(k)) v(k)也就是网络的期望输出等于当前输入在前向网络下的输出加上一个“训练噪声”。每个训练样本踩一脚就相当于滤波器做了一次预测加更新先预测权重大致在哪再用样本的期望输出把权重修正一点。关键操作是算出观测方程对状态的偏导数H ∂g/∂W。对一个单输出两层网络来说H向量恰好和反向传播算出的梯度方向一致。所以实现时不需要从零推公式把BP反传的梯度拿过来作为H就行只是别再做任何均值压缩或正则化处理把原始梯度向量直接交给EKF。3.3 核心实现代码为简洁起见我把网络固定为单隐层、单输出输入向量长度为1输出标量权重向量W按[输入权值(10个)隐层偏置(10个)输出权值(10个)输出偏置(1个)]的顺序拼接。函数forwardNet做前向传播函数backwardNet返回行向量H长度31。核心更新流程如下numParams 10 10 10 1; W randn(numParams, 1) * 0.1; P eye(numParams) * 0.1; % 初始协方差给一个小值 Q_ekf 1e-4 * eye(numParams); R_ekf 1; % 虚拟观测噪声一个重要超参 for epoch 1:200 for n 1:size(Xtr, 2) x Xtr(n); d Ytr(n); y forwardNet(W, x); % 前向传播 H backwardNet(W, x); % 输出对权重的Jacobian行向量 % EKF预测步 P P Q_ekf; % EKF更新步 S H * P * H R_ekf; K P * H / S; W W K * (d - y); P (eye(numParams) - K * H) * P; end end这里有两个容易出问题的地方。一是P在预测步每次都加Q_ekf所以不会一路衰减到零这让网络在训练后期还保留一定自适应能力Q取得太大训练不稳太小后期学不动我调下来1e-4量级比较可靠。二是R_ekf别贪小R1附近是合理的如果设成0.01第一次更新增益就会冲得很大权重直接发散。3.4 实测收敛效果与标准BP的对比同一份数据、同一个网络结构我用标准trainscg训练到收敛大约需要700到1000轮EKF方式只需要80到150轮就能达到差不多的训练均方误差。这个差距在数据量变大后会缩小但总体还是EKF占优。代价也有因为每一步要更新P矩阵矩阵维度和权重个数同阶网络层数一旦加深每次迭代的算力上涨很明显。这个任务里网络只有31个参数几乎无感但如果跑到几百个权重就要注意内存和时间了。所以EKF训练神经网络适合中小规模网络做大网络时可以考虑预训练加精调的混合方案。4. 粒子滤波轨迹估计不线性化直接在状态空间里撒点4.1 PF的优势在哪里粒子滤波的思路非常直白与其把后验分布近似成一个高斯不如直接在状态空间撒一大堆点粒子每个点代表一个可能的状态再根据预测和观测给每个点打一个置信分权重。更新完状态后把粒子按权重抽签权重大的多留几个后代权重小的淘汰这样粒子群就逐渐聚集到高后验区域。对UNGM这种状态方程里带着分式项和余弦项、观测又是平方关系的强非线性问题EKF在泰勒展开时丢掉的高阶项已经相当可观。PF靠大量样本对条件分布的原始逼近不吃“线性化误差”这口亏所以只要粒子数够、重采样设计合理精度通常是最稳的那个。4.2 标准粒子滤波的完整Matlab实现下面的代码是完整的标准PF流程粒子数设为500重采样用系统重采样systematic resampling工程上这是性价比最高的一种比简单随机重采样引入的额外方差小N 200; Q 10; R 1; x_true zeros(1,N); z zeros(1,N); x_true(1) 0.1; z(1) x_true(1)^2/20 sqrt(R)*randn; for k 2:N x_true(k) 0.5*x_true(k-1) 25*x_true(k-1)/(1x_true(k-1)^2) 8*cos(1.2*(k-1)) sqrt(Q)*randn; z(k) x_true(k)^2/20 sqrt(R)*randn; end Np 500; xp 0.1 sqrt(Q)*randn(1, Np); % 初始粒子 wp ones(1, Np) / Np; x_est zeros(1, N); x_est(1) sum(wp .* xp); for k 2:N % 1. 预测粒子按状态方程传播 xp_pred 0.5*xp 25*xp./(1xp.^2) 8*cos(1.2*(k-1)) sqrt(Q)*randn(1, Np); % 2. 更新按观测似然更新权重 innov z(k) - xp_pred.^2/20; wp_pred wp .* exp(-innov.^2/(2*R)); wp_pred wp_pred / sum(wp_pred); % 3. 系统重采样 [xp, wp] systematicResample(xp_pred, wp_pred); % 4. 后验均值用于轨迹估计 x_est(k) sum(wp .* xp); end function [xr, wr] systematicResample(x, w) N length(w); c cumsum(w); u (rand (0:N-1)) / N; xr zeros(size(x)); j 1; for i 1:N while c(j) u(i) j j 1; end xr(i) x(j); end wr ones(1, N) / N; end4.3 粒子数和算力的平衡粒子数Np这个参数特别值得聊。太少了比如50个粒子群很快就会退化成一两个高权重点后验均值估计的方差很大太多了比如5000个精度提升有限但每步循环耗时直线上升。我测试UNGM这个模型Np从200提到500RMSE大概下降10%-20%从500提到2000只再降几个百分点边际收益很低。我的建议是从小开始先跑到500观察估计曲线是否平滑如果抖动明显再往上加。另外标准PF每步都做重采样重采样之后粒子之间出现大量重复这个叫粒子贫化会让状态空间的探索能力下降。实际工程里会引入MCMC移动来缓解但那套东西实现成本高做对比研究时标准PF已经够用。4.4 什么时候该优先选PF如果对运行时间有硬约束比如嵌入式实时滤波PF的算力负担是个坎但如果任务本身是离线分析、后处理或者单步耗时可接受PF在这个强非线性测试模型上的表现确实比EKF稳。我自己在项目里更多是把PF当“基准答案”用先跑PF看准的效果再去估EKF的误差有多大。它不需要求导不依赖初始协方差是否优雅唯一要提防的就是粒子数分配不足时的采样方差。5. 三种方案在同一测试模型上的对比实验5.1 实验设置为了让对比公平所有方法共用同一条测试轨迹、同一个噪声种子。EKF用解析Jacobian初值x0设为0.1、初始协方差P01EKF-BP里的BP网络结构固定为1-10-1先用60条轨迹离线训练然后与EKF滤波框架配合PF粒子数为500。评价指标用均方根误差RMSE和最大单点误差MaxAE分别代表整体精度和峰值失控风险。5.2 结果数据取10次不同随机种子下的均值结果大致如下方案RMSEMaxAE离线训练需求单步耗时相对标准EKF2.055.4无1xBP直接估计2.837.2有60条轨迹0.2xEKF-BP1.614.1有1.3xPFNp5001.233.5无8x纯BP端到端预测是最差的它把动态递推问题降维成了静态回归个别强非线性片段误差特别大EKF-BP优于标准EKF说明BP学到的模型补充信息确实帮EKF修正了一部分线性化误差PF精度最高代价是单步耗时明显上涨。5.3 结果解读与应用建议这套相对排序并不意外但有一个细节值得单独拿出来EKF-BP的MaxAE只有4.1误差异常值被控制得不错。这是因为EKF的更新机制本身就把异常观测的影响限制在了协方差允许的范围内。如果你的项目更看重峰值风险而不是平均误差EKF-BP是个很好的折中。反过来如果有实时硬约束单步耗时的上升可能是个问题但去掉BP前向计算也只比纯EKF贵一点EKF-BP依旧可行。至于PF在算力允许的情况下我建议至少作为验证基准跑一次它能帮你快速确认手头问题的理论精度天花板在哪里。6. 复现过程中最想提前告诉你的几个坑6.1 滤波器初值不是随便给的很多人一上来把P0设成0或者一个很小的数理由是“我对初始状态很有信心”。但EKF里P0如果太小前几步更新增益会被压住滤波器要很久才能“醒”过来。我实测UNGM这个模型P0从1调到0.01前20步RMSE能差出一倍。稳妥做法是把P0设成跟状态量级同阶比如1到10之间让滤波器前几步快速收敛。6.2 EKF-BP的Jacobian别用数值差分初期我为了省事想用有限差分近似求解H。代码是短了但数值差分引入的截断误差会被卡尔曼增益直接放大训练过程明显震荡。正确做法是从反向传播里把梯度原样导出因为H这个量本身含义就是“输出对权重的偏导”BP每次反传算的也是它两者完全一致。如果实在不想手写至少用符号工具箱求一次梯度表达式再生成m函数调用别用循环式差分。6.3 粒子滤波重采样后的多样性问题标准PF重采样后权重被重置成均匀分布重复粒子大量存在。体现在轨迹上就是偶尔某一步的估计特别准但邻近几步又开始漂。这不是bug是重采样方差的表现。工程上缓解的办法是引入有效粒子数Neff判断只在Neff低于阈值时才重采样能明显降低无谓的随机性。Neff的计算公式是1/sum(wp.^2)实测这种方法比每次都重采样的方案曲线更平滑。6.4 Matlab版本与API的适配这套代码我主要在Matlab 2023b和2026b上验证过。老的newff在新版本里已经不再推荐train(net, X, Y)这种调用约定是现在的主流。另外要小心feedforwardnet默认会对输入输出做映射表归一化这是隐式的。你最好自己先归一化一遍否则后面做EKF训练时拿到的梯度和工具箱内部用的尺度不一致两头对不上。这个问题排查起来非常隐蔽我当时是反复对比工具箱训练结果和手写结果才发现的。你在复现时如果发现EKF训练出来的权重和工具箱训练结果差很多先查这一步。最后说点个人的体会。状态估计这套东西核心算法一晚上能背熟真正决定效果下限的是你对自己模型的假设够不够诚实EKF假设高斯、假设线性化误差可忽略PF不害怕任何非线性和非高斯BP则需要你的训练数据覆盖到测试场景的状态空间。EKF-BP的组合不是万金油它本质上是“用数据把模型误差补一小截”。我自己复现完这条线以后再遇到新问题会先想这个问题的后验分布大概长什么样是高斯我就用EKF是强多峰我就上PF有模型缺失就考虑加BP。按这个顺序来基本不会把路走偏。
返回列表