4. 递推最小二乘法(RLS)

各位工程师朋友,今天我们来聊聊递推最小二乘法。说实话,这个算法我在项目里用过不下几十次了。从早期的电机参数辨识,到后来做电池SOC估计,RLS几乎成了我工具箱里的常客。

为什么它这么重要?因为实际系统是时变的。你想想看,一个电机运行久了,电阻会发热变化;电池用久了,内阻会逐渐增大。传统的批处理最小二乘法,每次都要拿全部历史数据重新算一遍,这在嵌入式系统里根本跑不动。RLS就是来解决这个问题的——它只靠新数据来更新旧参数,计算量小,响应快。

4.1 从批处理到递推:为什么需要RLS?

先回忆一下批处理最小二乘。假设我们有N组数据,模型是:

y(k) = φᵀ(k)θ + e(k)

其中φ(k)是回归向量,θ是待估参数。批处理解是:

θ̂ = (ΦᵀΦ)⁻¹ΦᵀY

这个公式看着简单,但每次有新数据,你都得重新算一遍矩阵求逆。N=1000时还好,N=10万时,你的单片机可能就卡死了。

我当年做第一个项目时,就傻乎乎地用了批处理。数据量一上来,采样周期根本跟不上。后来换成RLS,计算时间从秒级降到了毫秒级。嗯,这就是递推的魅力。

4.2 递推公式推导:一步步来

RLS的核心思想很简单:用上一时刻的参数估计,加上当前新数据的修正量。我们一步步推。

设k时刻的增益矩阵为K(k),协方差矩阵为P(k)。那么递推公式长这样:

K(k) = P(k-1)φ(k) / [1 + φᵀ(k)P(k-1)φ(k)]

θ̂(k) = θ̂(k-1) + K(k)[y(k) - φᵀ(k)θ̂(k-1)]

P(k) = [I - K(k)φᵀ(k)]P(k-1)

看着有点复杂?其实拆开看就三件事:

  1. 计算增益:K(k)决定了新数据对参数更新的贡献大小
  2. 更新参数:用新数据的预测误差来修正旧参数
  3. 更新协方差:P(k)代表参数估计的不确定性,随着数据增多,P会越来越小

我个人习惯把P(k)理解成「学习率」。刚开始P很大,参数更新快;后来P变小,参数趋于稳定。这个类比在调试时很有用。

关键点:RLS的递推公式本质上是矩阵求逆引理的在线版本。它避免了每次都要计算(ΦᵀΦ)⁻¹,而是用标量除法代替了矩阵求逆。这就是它快的根本原因。

4.3 遗忘因子引入:让算法学会「忘记」

标准RLS有个问题:所有历史数据权重一样。对于时变系统,老数据反而会拖累新参数的估计。怎么办?引入遗忘因子λ。

遗忘因子λ的取值范围是(0, 1],通常取0.95~0.99。λ越小,遗忘越快。修改后的递推公式变成:

K(k) = P(k-1)φ(k) / [λ + φᵀ(k)P(k-1)φ(k)]

θ̂(k) = θ̂(k-1) + K(k)[y(k) - φᵀ(k)θ̂(k-1)]

P(k) = [I - K(k)φᵀ(k)]P(k-1) / λ

注意看,分母多了个λ,P(k)更新时也除了个λ。这相当于给老数据打了个折扣。

我曾经在电池SOC估计项目里吃过亏。一开始λ设成0.99,结果电池内阻变化时,参数更新太慢,估计误差越来越大。后来改成0.96,跟踪速度就上来了。但λ也不能太小,否则参数会乱跳。

经验之谈:λ的选择可以这样试:系统变化快就取0.95~0.97,变化慢就取0.98~0.99。如果实在拿不准,可以试试变遗忘因子——误差大时自动减小λ,误差小时增大λ。

4.4 RLS的收敛性分析

收敛性说白了就是:算法能不能稳定地找到真值?

对于定常系统(参数不变),标准RLS在持续激励条件下是渐近收敛的。所谓持续激励,就是输入信号要足够丰富,不能一直是个常数。你想想看,如果输入一直是1,你永远分不清两个参数谁是谁。

对于时变系统,引入遗忘因子后,收敛性变成了有界收敛——参数会在真值附近波动,不会发散。波动幅度取决于λ和噪声水平。

我遇到过最头疼的情况是:系统参数变化太快,而λ又设得不够小,结果估计值一直追不上真值。后来我加了个简单的检测机制:当预测误差持续偏大时,自动重置P矩阵,让算法重新快速收敛。

注意:持续激励条件不是理论上的摆设。实际中,如果系统长时间处于稳态,RLS的协方差矩阵P会变得非常小,导致算法对参数变化「失明」。这时候需要人为注入激励信号,或者定期重置P矩阵。

4.5 数值稳定性问题与改进

RLS在实际跑的时候,最怕的就是数值问题。尤其是P矩阵,理论上应该是对称正定的,但计算机浮点运算会引入误差,时间一长P就可能变成非对称甚至奇异。

常见的数值问题有:

  • P矩阵失去对称性:由于舍入误差,Pᵀ ≠ P
  • P矩阵负定:增益K变成负的,参数更新方向反了
  • 除法溢出:当λ + φᵀPφ接近0时,K会变得巨大

怎么解决?我推荐两种方法:

  1. 对称化处理:每次更新后强制P = (P + Pᵀ)/2
  2. UD分解法:把P分解成UᵀDU形式,只更新U和D,保证正定性

UD分解的代码实现稍微复杂点,但稳定性好很多。我在做工业控制器时,就用的UD分解RLS,跑了两年没出过问题。

// UD分解RLS核心步骤(伪代码)
// P = U * D * Uᵀ
// 更新时只操作U和D,保证P始终对称正定
for j = 1 to n:
    d_j = D(j) * λ
    // 更新U和D的各个元素
    // 具体公式略长,建议参考Ljung的《System Identification》
end

小技巧:如果你不想自己实现UD分解,可以试试带QR分解的RLS变体。计算量稍大,但数值稳定性更好。我一般在ARM Cortex-M4上跑标准RLS,在PC上跑QR-RLS。

4.6 知识体系总览

下面这张图是我自己总结的RLS知识框架,帮你理清思路:

RLS递推最小二乘法知识体系 递推最小二乘法 (RLS) 递推公式推导 遗忘因子引入 收敛性分析 数值稳定性 增益K(k)计算 参数θ̂(k)更新 协方差P(k)更新 λ取值0.95~0.99 变遗忘因子策略 持续激励条件 渐近收敛/有界收敛 UD分解法 对称化处理

这张图把RLS的四个核心模块串起来了。从递推公式出发,引入遗忘因子解决时变问题,再分析收敛性保证算法靠谱,最后用数值稳定性技巧让算法在嵌入式系统里跑得稳。每一步都有坑,但每一步也都有解法。

好了,关于RLS的内容就讲到这里。记住,理论再漂亮,最终还是要落地到代码里。我建议你找个实际系统的数据,自己动手写一遍RLS,调一调λ,看看P矩阵的变化。只有亲手调过,才能真正理解这个算法的脾气。

可可精品资料库