告别玄学调参:用Python手把手实现一个卡尔曼滤波器(附AR序列预测完整代码)

卡尔曼滤波在工程实践中常被戏称为"玄学调参"——明明公式清晰,但面对状态转移矩阵、噪声协方差等参数时,工程师们往往陷入"调参看运气"的困境。本文将以AR(2)时间序列预测为例,用不到100行Python代码,带您拆解参数设置的底层逻辑,实现一个可复用的卡尔曼滤波器。

1. 从AR模型到状态空间:数学语言的工程转换

AR(2)模型定义为 $x_t = \phi_1 x_{t-1} + \phi_2 x_{t-2} + w_t$,其中$w_t$是过程噪声。要将其转换为卡尔曼滤波框架,关键在于构建状态向量。这里我们定义状态向量为:

state_vector = np.array([[x_t], [x_{t-1}]])  # 形状(2,1)

对应的状态转移矩阵F和过程噪声协方差Q为:

F = np.array([[phi1, phi2], 
              [1,   0]])  # 形状(2,2)
Q = np.array([[sigma_w**2, 0], 
              [0,          0]])  # 形状(2,2)

观测矩阵H设计为提取当前状态:

H = np.array([[1, 0]])  # 形状(1,2)
R = np.array([[sigma_v**2]])  # 观测噪声方差

工程经验 :当状态变量物理意义不同时(如同时包含位置和速度),Q的非对角线元素可能不为零。但在AR模型中,我们通常假设过程噪声只影响当前时刻状态。

2. 卡尔曼滤波器的Python实现

完整的滤波器类实现如下,关键步骤已添加注释:

class KalmanFilter:
    def __init__(self, F, H, Q, R, P0, x0):
        self.F = F  # 状态转移矩阵
        self.H = H  # 观测矩阵
        self.Q = Q  # 过程噪声协方差
        self.R = R  # 观测噪声协方差
        self.P = P0 # 初始估计协方差
        self.x = x0 # 初始状态估计

    def predict(self):
        # 预测状态
        self.x = self.F @ self.x
        # 预测协方差
        self.P = self.F @ self.P @ self.F.T + self.Q
        return self.x

    def update(self, z):
        # 计算卡尔曼增益
        S = self.H @ self.P @ self.H.T + self.R
        K = self.P @ self.H.T @ np.linalg.inv(S)
        # 更新状态估计
        self.x = self.x + K @ (z - self.H @ self.x)
        # 更新协方差估计
        self.P = self.P - K @ self.H @ self.P
        return self.x

常见坑点

  • 矩阵维度不匹配:确保所有矩阵乘法维度相容,特别是当状态维度>1时
  • 协方差矩阵非正定:理论上P、Q、R都应是半正定矩阵,实践中可添加小量单位矩阵保证数值稳定

3. 参数调优实战:噪声方差的影响

通过生成AR(2)序列并添加不同噪声,我们可以直观观察Q和R的影响:

场景 Q设置 R设置 滤波效果特征
低过程噪声 [[0.01, 0],[0,0]] [[1,0],[0,1]] 跟踪滞后,对观测变化不敏感
高观测噪声 [[1, 0],[0,0]] [[10,0],[0,10]] 预测平滑但偏差较大
理想匹配 [[0.1, 0],[0,0]] [[0.5,0],[0,0.5]] 跟踪及时且平滑

调整策略:

  1. 先固定R,从小到大调整Q直到跟踪响应速度合适
  2. 固定Q,调整R直到滤波结果不过度震荡
  3. 使用创新序列(实际观测-预测观测)检验:理想情况应呈白噪声

4. 诊断与调试技巧

当滤波器表现异常时,可按以下流程排查:

  1. 检查协方差矩阵演化

    print("预测协方差:", kf.P)
    print("后验协方差:", kf.P_post)
    

    正常情况下应收敛到稳态值

  2. 验证卡尔曼增益

    K = kf.P @ kf.H.T @ np.linalg.inv(kf.H @ kf.P @ kf.H.T + kf.R)
    print("计算增益:", K)
    

    增益值应在[0,1]区间,过大或过小都表明参数设置不当

  3. 监测新息序列

    innovation = z - kf.H @ kf.x_pred
    plt.plot(innovation)  # 应近似白噪声
    

典型故障模式

  • 发散:通常因Q设置过小或P0设置过小
  • 过度平滑:R相对于Q设置过大
  • 震荡:Q过大或R过小

5. 扩展应用:多维状态与非线性系统

对于更复杂的系统(如机器人定位),状态向量可能包含位置、速度等多维信息。此时状态转移矩阵变为:

F = np.array([[1, dt, 0,  0],
              [0,  1, 0,  0],
              [0,  0, 1, dt],
              [0,  0, 0,  1]])  # 匀速模型

对于非线性系统,可采用扩展卡尔曼滤波(EKF):

  1. 在预测步骤使用非线性函数f(x)
  2. 通过雅可比矩阵线性化计算协方差传播
def f(x):
    return np.array([x[0] + x[1]*dt, 
                     x[1],
                     x[2] + x[3]*dt,
                     x[3]])

# 计算雅可比矩阵
J = numerical_derivative(f, x_current)
P_pred = J @ P_current @ J.T + Q

实际项目中,建议先用本文的线性滤波器验证基础流程,再逐步过渡到复杂模型。完整的AR(2)预测代码已上传GitHub仓库(见文末链接),包含数据生成、滤波实现和可视化对比功能。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐