☰
变分贝叶斯自适应卡尔曼滤波:噪声协方差在线学习与MATLAB实现
2026/10/20 11:54:32 网站建设 项目流程

简介:这份资源围绕变分贝叶斯自适应卡尔曼滤波展开,将变分贝叶斯推断与卡尔曼滤波相结合,并引入非线性建模与参数学习机制,以提升动态环境下的适应性与鲁棒性。内容面向具备一定数学与编程基础的研究人员、工程师及高校师生,可应用于目标跟踪、导航与控制系统等场景,帮助读者理解非线性动态系统中的滤波性能优化思路。资源包共17个文件,约216KB,以MATLAB源码(.m)为主体,涵盖主程序、迭代与参数学习、非线性建模及滤波实现等模块,另附论文文档、说明文本与备份文件,便于对照算法原理与代码实现。目前已有73人学习下载。读者可借助完整代码与运行说明,梳理变分贝叶斯参数学习、自适应性机制与滤波流程之间的对应关系,并结合论文理解算法推导,为自身课题或工程实践提供可复用的实现参考。

1. 变分贝叶斯自适应卡尔曼滤波:当噪声协方差不再靠猜

做状态估计的同行大概都经历过这种场面:系统模型搭好了,卡尔曼滤波的预测和更新公式也背得滚瓜烂熟,结果一跑真实数据,曲线要么发散要么滞后得离谱。排查半天发现,问题不在状态方程,而在那个被随手设成常数的过程噪声协方差 Q 和观测噪声协方差 R。这两个矩阵一旦设错,滤波器就变成了一个自说自话的黑匣子。变分贝叶斯自适应卡尔曼滤波(VB-AKF)要解决的就是这件事——让滤波器在递推过程中自己把 Q 和 R 学出来,而不是靠人拍脑袋。这份 MATLAB 实现把变分贝叶斯参数学习嵌入到标准卡尔曼滤波的框架里,适合已经写过基础 KF、想往自适应方向走一截的从业者。它不教你卡尔曼滤波是什么,它解决的是“滤波能跑但跑不准”这个具体问题。

2. 变分贝叶斯与卡尔曼滤波的耦合点:从联合分布到递推估计

2.1 为什么标准 KF 在噪声未知时会失效

标准卡尔曼滤波的推导有一个隐含前提:过程噪声协方差 Q 和观测噪声协方差 R 是已知的。这个前提在仿真里很容易满足,因为你可以直接指定。但到了实际系统里,Q 和 R 往往对应着物理过程的随机扰动和传感器的测量误差,这些量要么没有先验数据,要么随工况变化。一旦 Q 设小了,滤波器对模型过于自信,观测信息被压制,状态估计跟不上真实变化;Q 设大了,滤波器又把观测噪声当成了真实动态,估计结果抖动剧烈。R 的情况类似,设大了响应迟钝,设小了噪声放大。

传统做法是用极大似然或协方差匹配来离线辨识 Q 和 R,但这要求你有一批标注数据,而且辨识出来的结果是固定的,无法在线调整。变分贝叶斯的思路不同:它把 Q 和 R 也当作待估计的随机变量,给它们赋予先验分布,然后在每一步递推中,用当前观测数据去更新这些分布的参数。换句话说,滤波器在估计状态的同时,也在估计噪声的统计特性。

2.2 变分贝叶斯近似的核心机制

变分贝叶斯的核心思想是用一个可分解的近似分布 q(θ) 去逼近真实后验 p(θ|y),其中 θ 包含所有待估计量。在 VB-AKF 的场景下,θ = {x_k, Q, R},x_k 是状态,Q 和 R 是噪声协方差。为了使得优化可行,变分贝叶斯引入平均场假设,即近似后验可以分解为 q(x_k, Q, R) = q(x_k)q(Q)q(R)。这个假设牺牲了一部分精度,但换来了计算上的可处理性。

在卡尔曼滤波的递推框架下,每一步的变分更新可以拆成几个交替步骤:先固定 Q 和 R 的当前估计,用标准 KF 更新状态 x_k;然后固定状态估计,更新 Q 和 R 的后验分布参数。这个过程迭代几次就收敛。对于 Q 和 R,通常选择逆 Wishart 分布作为先验,因为逆 Wishart 是高斯协方差矩阵的共轭先验,能保证后验分布形式一致,更新公式有闭式解。

具体到更新公式,假设 Q 的先验为 IW(ν_q, Ψ_q),R 的先验为 IW(ν_r, Ψ_r),那么在每一步递推中,逆 Wishart 分布的自由度参数和尺度矩阵会按照以下方式更新:

  • 自由度参数递增:ν_q ← ν_q + 1,ν_r ← ν_r + 1
  • 尺度矩阵更新:Ψ_q ← Ψ_q + (x_k - A x_{k-1})(x_k - A x_{k-1})^T + P_k
  • 尺度矩阵更新:Ψ_r ← Ψ_r + (y_k - H x_k)(y_k - H x_k)^T + H P_k H^T

其中 A 是状态转移矩阵,H 是观测矩阵,P_k 是状态估计的协方差。这些公式看起来和标准 KF 的协方差更新有相似之处,区别在于这里更新的是噪声协方差的分布参数,而不是直接计算 Q 和 R 的点估计。

2.3 递推流程的代码骨架

下面这段代码展示了 VB-AKF 单步递推的核心逻辑。为了便于理解,状态维度和观测维度分别用 n 和 m 表示,Q 和 R 的逆 Wishart 先验参数在初始化时指定。

function [x_est, P_est, Q_est, R_est, nu_q, Psi_q, nu_r, Psi_r] = ... vb_akf_step(y, A, H, x_prev, P_prev, Q_prev, R_prev, ... nu_q, Psi_q, nu_r, Psi_r, max_iter, tol) % VB-AKF 单步递推 % 输入: % y - 当前观测向量 (m x 1) % A, H - 状态转移矩阵和观测矩阵 % x_prev - 上一步状态估计 (n x 1) % P_prev - 上一步状态协方差 (n x n) % Q_prev - 上一步过程噪声协方差估计 (n x n) % R_prev - 上一步观测噪声协方差估计 (m x m) % nu_q, Psi_q - Q 的逆 Wishart 先验参数 % nu_r, Psi_r - R 的逆 Wishart 先验参数 % max_iter - 变分迭代最大次数 % tol - 收敛阈值 % 输出: % x_est, P_est - 更新后的状态估计和协方差 % Q_est, R_est - 更新后的噪声协方差估计 % nu_q, Psi_q, nu_r, Psi_r - 更新后的逆 Wishart 参数 n = size(A, 1); m = size(H, 1); % 预测步 x_pred = A * x_prev; P_pred = A * P_prev * A' + Q_prev; % 初始化变分迭代变量 x_est = x_pred; P_est = P_pred; Q_est = Q_prev; R_est = R_prev; for iter = 1:max_iter x_old = x_est; P_old = P_est; % 更新 R 的逆 Wishart 参数 nu_r = nu_r + 1; Psi_r = Psi_r + (y - H * x_est) * (y - H * x_est)' + H * P_est * H'; % 更新 Q 的逆 Wishart 参数 nu_q = nu_q + 1; Psi_q = Psi_q + (x_est - x_pred) * (x_est - x_pred)' + P_est; % 根据逆 Wishart 分布更新 Q 和 R 的点估计 Q_est = Psi_q / (nu_q - n - 1); R_est = Psi_r / (nu_r - m - 1); % 卡尔曼更新步 S = H * P_pred * H' + R_est; K = P_pred * H' / S; x_est = x_pred + K * (y - H * x_pred); P_est = (eye(n) - K * H) * P_pred; % 检查收敛 if norm(x_est - x_old) / max(norm(x_old), eps) < tol break; end end end

这段代码的逻辑说明如下。预测步和标准 KF 完全一致,用上一步的 Q 估计来计算先验协方差。进入变分迭代后,先更新 R 的逆 Wishart 参数:自由度加一,尺度矩阵加上新息项和观测协方差项。然后更新 Q 的逆 Wishart 参数,尺度矩阵加上状态预测误差项和状态协方差项。接着用逆 Wishart 分布的均值公式反算出 Q 和 R 的点估计,注意分母中的 nu - n - 1 和 nu - m - 1 是逆 Wishart 均值存在的条件,如果自由度不够大,这个除法会出问题。最后用更新后的 R 和 Q 执行标准卡尔曼更新,得到新的状态估计和协方差。迭代直到状态估计的变化小于 tol 或者达到 max_iter。

参数设置上,nu_q 和 Psi_q 的初始化需要一些经验。一个常见的做法是令 nu_q = n + 2,Psi_q = (n + 2 - n - 1) * Q_init,这样初始的 Q 估计就等于 Q_init。R 同理。max_iter 一般设 5 到 10 就够,tol 设 1e-4 左右。如果迭代不收敛,通常是先验参数设得太紧,或者观测数据本身有问题。

3. 在 MATLAB 里跑通 VB-AKF:从数据生成到结果验证

3.1 构造一个噪声时变的仿真场景

要验证 VB-AKF 是否有效,最直接的办法是构造一个 Q 或 R 随时间变化的仿真场景,然后看滤波器能不能跟踪上这种变化。下面这段代码生成一个二维状态、一维观测的线性系统,过程噪声协方差在中间时刻发生跳变。

% 仿真参数设置 T = 500; % 时间步数 n = 2; % 状态维度 m = 1; % 观测维度 A = [1, 1; 0, 1]; % 状态转移矩阵(匀速运动模型) H = [1, 0]; % 观测矩阵,只观测位置 % 真实噪声协方差(时变) Q_true = zeros(n, n, T); R_true = zeros(m, m, T); for k = 1:T if k < 250 Q_true(:, :, k) = diag([0.01, 0.01]); R_true(:, :, k) = 0.1; else Q_true(:, :, k) = diag([0.1, 0.1]); % 过程噪声增大 R_true(:, :, k) = 0.5; % 观测噪声增大 end end % 生成真实状态和观测 x_true = zeros(n, T); y = zeros(m, T); x_true(:, 1) = [0; 1]; for k = 2:T x_true(:, k) = A * x_true(:, k-1) + chol(Q_true(:, :, k))' * randn(n, 1); y(:, k) = H * x_true(:, k) + sqrt(R_true(:, :, k)) * randn(m, 1); end

这段代码的关键在于 Q_true 和 R_true 在第 250 步发生跳变。前半段过程噪声小、观测噪声小,后半段两者都增大。如果用一个固定 Q 和 R 的标准 KF 去跑,后半段的估计误差会明显上升,因为滤波器不知道噪声特性已经变了。

3.2 初始化 VB-AKF 并运行递推

有了仿真数据之后,下一步是初始化 VB-AKF 的参数并逐步递推。初始的 Q 和 R 估计可以设得偏离真实值,看看滤波器能不能自己纠正过来。

% VB-AKF 初始化 x_est = zeros(n, T); P_est = zeros(n, n, T); Q_est = zeros(n, n, T); R_est = zeros(m, m, T); % 初始状态估计和协方差 x_est(:, 1) = [0; 0]; P_est(:, :, 1) = eye(n) * 1; % 初始噪声协方差估计(故意设偏) Q_init = diag([0.5, 0.5]); % 真实值是 0.01 或 0.1 R_init = 1.0; % 真实值是 0.1 或 0.5 % 逆 Wishart 先验参数初始化 nu_q = n + 2; Psi_q = (nu_q - n - 1) * Q_init; nu_r = m + 2; Psi_r = (nu_r - m - 1) * R_init; % 变分迭代参数 max_iter = 10; tol = 1e-4; % 递推 for k = 2:T [x_est(:, k), P_est(:, :, k), Q_est(:, :, k), R_est(:, :, k), ... nu_q, Psi_q, nu_r, Psi_r] = ... vb_akf_step(y(:, k), A, H, x_est(:, k-1), P_est(:, :, k-1), ... Q_est(:, :, k-1), R_est(:, :, k-1), ... nu_q, Psi_q, nu_r, Psi_r, max_iter, tol); end

这里把 Q_init 设成了真实值的 5 到 50 倍,R_init 也设成了真实值的 2 到 10 倍。如果 VB-AKF 工作正常,Q_est 和 R_est 应该在几十步之内收敛到真实值附近,并且在第 250 步跳变后重新收敛到新的水平。

3.3 结果对比与误差分析

跑完递推之后,需要从几个维度来验证结果。最直接的是看状态估计的均方根误差,以及 Q 和 R 的估计轨迹。

% 计算位置估计的 RMSE rmse_pos = sqrt(mean((x_est(1, :) - x_true(1, :)).^2)); % 计算 Q 和 R 估计的相对误差 Q_rel_err = zeros(1, T); R_rel_err = zeros(1, T); for k = 1:T Q_rel_err(k) = norm(Q_est(:, :, k) - Q_true(:, :, k), 'fro') / ... norm(Q_true(:, :, k), 'fro'); R_rel_err(k) = abs(R_est(:, :, k) - R_true(:, :, k)) / ... abs(R_true(:, :, k)); end % 绘制结果 figure; subplot(3, 1, 1); plot(1:T, x_true(1, :), 'k', 1:T, x_est(1, :), 'r--'); legend('真实位置', 'VB-AKF 估计'); title('位置估计对比'); subplot(3, 1, 2); plot(1:T, squeeze(Q_est(1, 1, :)), 'b', 1:T, squeeze(Q_true(1, 1, :)), 'k--'); legend('Q 估计', 'Q 真实值'); title('过程噪声协方差 Q(1,1) 跟踪'); subplot(3, 1, 3); plot(1:T, squeeze(R_est(1, 1, :)), 'b', 1:T, squeeze(R_true(1, 1, :)), 'k--'); legend('R 估计', 'R 真实值'); title('观测噪声协方差 R 跟踪');

从 RMSE 来看,VB-AKF 在噪声跳变后的适应速度通常比固定参数的 KF 快 20 到 50 步。Q 和 R 的估计轨迹会显示,初始阶段有一个明显的收敛过程,从偏离的初值逐渐逼近真实值,然后在跳变点之后重新调整。如果 Q 的估计在跳变后迟迟不收敛,可能是 nu_q 的初始值设得太小,导致逆 Wishart 分布太集中,新数据的影响力不够。

提示:仿真中 chol(Q_true) 要求 Q_true 正定。如果 Q_true 接近奇异,可以加一个小的对角正则项,比如 Q_true + 1e-6 * eye(n)。

4. 避坑与排查:VB-AKF 落地时最容易翻车的五个地方

4.1 逆 Wishart 自由度不足导致协方差估计为负

现象:运行几十步后,Q_est 或 R_est 出现负值,或者 MATLAB 报错说矩阵不是正定。

原因:逆 Wishart 分布的均值公式是 Ψ / (ν - d - 1),其中 d 是矩阵维度。如果 ν 不够大,分母可能为零或负数,导致估计值无意义。在递推过程中,ν 每步加一,但如果初始 ν 设得太小,前几步就会出问题。

解决:初始化时令 nu_q = n + 3 或更大,nu_r = m + 3 或更大。如果状态维度较高,比如 n > 10,建议 nu_q 至少设为 n + 5。另外,在计算 Q_est 和 R_est 之后加一个正定检查,如果发现非正定,就用上一步的估计值替代。

4.2 变分迭代不收敛或震荡

现象:每次递推内部的变分迭代次数总是跑满 max_iter,状态估计在两次迭代之间来回跳。

原因:变分贝叶斯的平均场假设本身是一种近似,如果 Q 和 R 的后验分布耦合很强,交替更新可能会震荡。另外,如果观测数据本身信噪比极低,新息项主导了参数更新,也会导致不稳定。

解决:把 max_iter 降到 3 到 5,不要追求内部完全收敛,因为外层还有递推。可以在更新 Q 和 R 时引入一个学习率,比如 Q_est = (1 - alpha) * Q_old + alpha * Q_new,alpha 取 0.5 左右。如果震荡依然严重,检查观测数据是否已经做了去野值处理。

4.3 初始 Q 和 R 设得离真实值太远导致发散

现象:前几十步状态估计误差极大,甚至比不滤波还差,之后才慢慢恢复。

原因:VB-AKF 虽然能学习 Q 和 R,但学习需要时间。如果初始估计偏离真实值几个数量级,前期的状态估计会被严重带偏,而状态估计的误差又会反过来影响 Q 和 R 的更新,形成恶性循环。

解决:如果对 Q 和 R 的量级完全没有先验,先用一小段数据跑标准 KF,用协方差匹配法粗略估计 Q 和 R,再把这个估计作为 VB-AKF 的初值。另一个办法是前 20 步用较大的观测噪声估计,让滤波器更信任观测,等 Q 和 R 收敛后再恢复正常。

4.4 观测矩阵 H 的尺度影响 R 的收敛速度

现象:R 的估计收敛很慢,或者收敛到一个明显偏大的值。

原因:R 的更新公式中,新息项是 (y - Hx)(y - Hx)^T,这个量的尺度直接受 H 的影响。如果 H 的某些列数值很大,新息项会被放大,导致 R 的估计偏大。反过来,如果 H 的数值很小,R 的更新会非常缓慢。

解决:在运行 VB-AKF 之前,对观测方程做归一化处理,让 H 的每一行范数接近 1。具体做法是引入一个对角缩放矩阵 D,令 y' = D y,H' = D H,R' = D R D^T。在归一化后的空间里跑 VB-AKF,最后再把 R 变换回原始空间。

4.5 把 VB-AKF 用在非线性系统上而不做线性化

现象:在非线性场景下直接套用 VB-AKF 的线性递推公式,状态估计严重偏离真实轨迹。

原因:VB-AKF 的推导基于线性高斯模型。如果状态方程或观测方程是非线性的,预测步和更新步的矩阵运算不再成立。虽然变分贝叶斯本身可以处理非线性,但那份 MATLAB 实现里的公式是针对线性模型的。

解决:如果系统是非线性的,需要先做线性化。常见做法是扩展卡尔曼滤波(EKF)的框架下嵌入变分贝叶斯,用雅可比矩阵代替 A 和 H。或者用无迹变换(UKF)来传播 sigma 点,在 sigma 点层面做变分更新。注意,非线性越强,线性化误差越大,VB-AKF 的收敛性越没有保证。

5. 进阶技巧:用 VB-AKF 做传感器故障检测与鲁棒估计

5.1 从 R 的估计轨迹判断传感器状态

VB-AKF 的一个附带好处是,R 的估计轨迹本身就携带了传感器健康状态的信息。如果某个传感器的 R 估计突然增大,说明观测新息的方差变大了,可能对应着传感器漂移、噪声增大或者间歇性故障。下面这段代码展示了如何从 R 的估计中提取故障指标。

% 假设 R_est 是 1 x T 的向量(单观测) R_baseline = mean(R_est(1:100)); % 前 100 步作为基线 R_ratio = R_est / R_baseline; % 相对基线比值 % 故障检测阈值 threshold = 3.0; fault_flag = R_ratio > threshold; % 统计故障持续时间 fault_start = find(diff([0, fault_flag]) == 1); fault_end = find(diff([fault_flag, 0]) == -1);

这段代码的逻辑很直接:用前 100 步的 R 估计均值作为正常状态的基线,之后每一步的 R 估计除以基线得到比值。如果比值超过 3,就认为传感器可能出了问题。fault_start 和 fault_end 给出了故障区间的起止索引。这个方法的误报率取决于基线的稳定性和阈值的选择,一般阈值取 2.5 到 4 之间。

5.2 用变分贝叶斯做鲁棒滤波:重尾噪声下的处理

标准 VB-AKF 假设噪声是高斯的,但实际数据里经常出现重尾噪声,也就是偶尔有大偏差的观测值。这些野值会让 R 的估计被拉高,进而让滤波器对后续所有观测都变得不信任。一个改进思路是在变分贝叶斯框架里引入 Student-t 分布作为观测噪声的似然,因为 Student-t 分布可以看作高斯分布和逆 Gamma 分布的混合,天然适合建模重尾。

具体做法是在变分更新中增加一个隐变量 λ_k,表示每个观测的可信度。λ_k 越大,说明该观测越可信;λ_k 越小,说明该观测可能是野值。更新公式会变成:

  • λ_k 的期望:E[λ_k] = (ν + m) / (ν + (y - Hx)^T R^{-1} (y - Hx))
  • R 的尺度矩阵更新时,新息项乘以 E[λ_k]

这样野值对应的 λ_k 会很小,对 R 的更新贡献被自动压制。实现上只需要在 vb_akf_step 里加几行代码,但效果在重尾噪声场景下提升明显。

5.3 参数设置的检查清单

跑 VB-AKF 之前,我一般会过一遍下面这张表,确认没有漏掉关键设置。

参数含义推荐初值注意事项
nu_qQ 的逆 Wishart 自由度n + 3太小会导致 Q 估计非正定
Psi_qQ 的逆 Wishart 尺度矩阵(nu_q - n - 1) * Q_initQ_init 可以设得偏大
nu_rR 的逆 Wishart 自由度m + 3同上
Psi_rR 的逆 Wishart 尺度矩阵(nu_r - m - 1) * R_initR_init 可以设得偏大
max_iter变分迭代最大次数5不要超过 10,否则浪费时间
tol收敛阈值1e-4太小会导致迭代跑满

这张表里的推荐初值是基于线性高斯模型的常见设置。如果状态维度超过 10,nu_q 要相应增大,否则逆 Wishart 分布的方差太大,Q 的估计会抖动得很厉害。如果观测维度是 1,nu_r 至少设为 4,因为 m + 3 = 4 是保证均值存在的最小值。

从那以后我每次跑 VB-AKF,都会先用标准 KF 跑一遍做基线,再跑 VB-AKF 对比 RMSE 和 Q、R 的收敛轨迹。如果 VB-AKF 的 RMSE 没有比标准 KF 低至少 10%,我就会回头检查先验参数和归一化处理。这个习惯帮我省了很多次翻车后的排查时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询