无迹卡尔曼滤波算法推导(上):状态预测步骤详解
好,咱们今天来啃UKF最核心的一块——状态预测。说实话,我刚接触UKF那会儿,看到一堆sigma点和权重计算,头都大了。后来在项目里调了两个月电池SOC估计,才慢慢摸到门道。今天我就把这块掰开揉碎了讲给你听。
为什么需要无迹变换?
先想一个问题:电池模型是非线性的,对吧?EKF(扩展卡尔曼滤波)的做法是把非线性函数泰勒展开,只保留一阶项。说白了就是“近似”。但近似就有误差,尤其在电池这种强非线性系统里,误差会累积。
UKF的思路完全不同——它不近似函数,而是近似概率分布。怎么近似?用一组精心挑选的采样点(sigma点)来代表状态分布。这些点经过非线性函数后,再加权组合,就能得到预测后的均值和协方差。
核心思想:对非线性函数的概率分布进行近似,比直接近似函数本身更准确。
我在做磷酸铁锂电池SOC估计时遇到过这种情况:EKF在低SOC区域(<20%)误差能到8%,换成UKF后直接降到3%以内。原因就是磷酸铁锂的OCV-SOC曲线在两端特别平缓,非线性度极高,EKF的线性近似根本扛不住。
UKF状态预测的五个步骤
状态预测说白了就是“根据当前状态,猜下一步状态”。UKF分五步走,我一个个说。
第一步:计算sigma点
假设当前时刻k-1,我们有状态估计值 x̂_{k-1} 和协方差矩阵 P_{k-1}。现在要生成2n+1个sigma点(n是状态维度)。
公式长这样:
χ₀ = x̂
χᵢ = x̂ + (√((n+λ)P))ᵢ, i = 1,...,n
χᵢ = x̂ - (√((n+λ)P))ᵢ₋ₙ, i = n+1,...,2n
这里λ = α²(n+κ) - n,是个缩放参数。α控制sigma点的散布范围,一般取0.001到1之间。κ是次级缩放参数,通常设0或3-n。
我的经验:α取0.01左右效果不错。太小了sigma点太集中,非线性信息抓不住;太大了又可能采样到不合理区域。我在电池项目里试过α=0.5,结果协方差矩阵老是非正定,后来调回0.01就稳了。
你可能会问:“开根号那个怎么算?”嗯,这是对协方差矩阵做Cholesky分解。Python里用np.linalg.cholesky就行。
第二步:计算sigma点的权重
每个sigma点都有对应的权重,分均值权重和协方差权重两种:
W₀ᵐ = λ / (n + λ) # 均值权重(第0个点)
W₀ᶜ = λ / (n + λ) + (1 - α² + β) # 协方差权重(第0个点)
Wᵢᵐ = Wᵢᶜ = 1 / (2(n + λ)) # 其他点的权重,i=1,...,2n
β是状态分布的先验知识参数。高斯分布时β=2最优,这是我试过多次验证的。
| 参数 | 典型取值 | 作用 |
|---|---|---|
| α | 0.01 | 控制sigma点散布范围 |
| β | 2(高斯分布) | 融入先验分布信息 |
| κ | 0 或 3-n | 次级缩放参数 |
注意:权重之和必须等于1。我曾经因为手算时四舍五入,导致权重和不等于1,结果滤波器直接发散。检查权重和是个好习惯。
第三步:将sigma点通过状态方程
这一步最直接——把每个sigma点代入非线性状态方程f(·):
χᵢ⁻ = f(χᵢ, u_{k-1})
对于电池模型,状态方程通常长这样:
# 电池状态方程示例(离散时间)
def state_equation(x, u, dt):
"""
x: [SOC, V1, V2] # 状态向量
u: [I] # 输入:电流
"""
SOC, V1, V2 = x
I = u[0]
# SOC更新(安时积分)
SOC_next = SOC - (I * dt) / Q_nominal
# RC网络电压更新
V1_next = V1 * exp(-dt/(R1*C1)) + I * R1 * (1 - exp(-dt/(R1*C1)))
V2_next = V2 * exp(-dt/(R2*C2)) + I * R2 * (1 - exp(-dt/(R2*C2)))
return np.array([SOC_next, V1_next, V2_next])
每个sigma点都过一遍这个函数,得到一组新的点χᵢ⁻。注意上标“-”表示预测(先验)值。
第四步:计算预测状态均值
把变换后的sigma点加权求和:
x̂ₖ⁻ = Σᵢ Wᵢᵐ · χᵢ⁻
说白了就是加权平均。权重Wᵢᵐ就是第二步算出来的均值权重。
这里有个细节——如果状态变量有物理约束(比如SOC必须在0~1之间),加权平均后可能会越界。我一般会在这一步之后加个裁剪:
x̂ₖ⁻[0] = np.clip(x̂ₖ⁻[0], 0, 1) # 保证SOC在合理范围
第五步:计算预测协方差
最后一步,计算预测状态的不确定性:
Pₖ⁻ = Σᵢ Wᵢᶜ · (χᵢ⁻ - x̂ₖ⁻)(χᵢ⁻ - x̂ₖ⁻)ᵀ + Q
Q是过程噪声协方差矩阵。它代表你对模型本身的信任程度。Q越大,说明你觉得模型误差大,会更相信测量值。
调参心得:Q矩阵的对角线元素我一般设成状态量方差的1%~10%。比如SOC的方差如果是0.01²,那Q[0,0]就设1e-6左右。太小了滤波器收敛慢,太大了又容易震荡。
我曾经在一个项目中把Q设得特别小,结果滤波器对模型过度自信,测量更新基本不起作用,SOC估计值漂到天上去都不知道。后来花了三天才排查出来——就是Q太小惹的祸。
完整的状态预测代码
把上面五步串起来,就是完整的UKF状态预测函数:
def ukf_predict(x, P, f, Q, u, dt, alpha=0.01, beta=2, kappa=0):
n = len(x)
lam = alpha**2 * (n + kappa) - n
# 1. 生成sigma点
sqrt_P = np.linalg.cholesky((n + lam) * P)
sigma_points = np.zeros((2*n + 1, n))
sigma_points[0] = x
for i in range(n):
sigma_points[i+1] = x + sqrt_P[:, i]
sigma_points[n+i+1] = x - sqrt_P[:, i]
# 2. 计算权重
W_m = np.full(2*n + 1, 1 / (2*(n + lam)))
W_c = np.full(2*n + 1, 1 / (2*(n + lam)))
W_m[0] = lam / (n + lam)
W_c[0] = lam / (n + lam) + (1 - alpha**2 + beta)
# 3. sigma点通过状态方程
sigma_pred = np.array([f(sp, u, dt) for sp in sigma_points])
# 4. 预测均值
x_pred = np.dot(W_m, sigma_pred)
# 5. 预测协方差
diff = sigma_pred - x_pred
P_pred = np.dot(W_c * diff.T, diff) + Q
return x_pred, P_pred
这段代码我实际跑过很多次,用在18650电芯的SOC估计上,效果很稳。你直接拿去用,注意根据你的电池模型改一下state_equation函数就行。
一张图看懂UKF状态预测
下面这张SVG图把整个流程串起来了,建议你保存下来对照着看:
从这张图能清楚看到:输入是上一时刻的状态和协方差,经过五步处理,输出预测后的状态和协方差。这就是UKF状态预测的全部秘密。
几个容易踩的坑
最后分享几个我实际踩过的坑:
- Cholesky分解失败:协方差矩阵必须正定。如果出现非正定,检查一下是不是数值精度问题,或者加个很小的单位矩阵
P + 1e-6*I。 - sigma点越界:电池SOC、电压都有物理范围。sigma点生成后如果超出合理区间,可以考虑用反射法或裁剪法处理。
- 权重精度:Python浮点数精度足够,但如果你用C/C++实现,注意float和double的区别。
好了,UKF状态预测这部分就讲到这里。你把这五步吃透了,后面测量更新那部分就顺理成章了。下一节咱们接着聊UKF的测量更新——说白了就是怎么用实际测量值来修正预测结果。
公众号:蓝海资料掘金营,微信deep3321