很多人第一次接触“递推最小二乘法”时,脑子里冒出来的问题是:最小二乘我熟,线性回归嘛,线性代数课上不知道解了多少遍,为什么还搞一个“递推”版本?尤其是做自适应滤波、系统辨识、在线参数估计的工程师,迟早都会碰到RLS这个名字。其实RLS就是把批量最小二乘的求解过程改造成一条条数据流的迭代更新,核心是三条递推式,最难啃的也就是矩阵求逆引理那么一小块。下面直接围绕RLS公式的详细推导展开,把“为什么这样递推、每一步在干什么”讲清楚,顺带把初始化、调参与工程落地的经验一起交个底。这篇文章适合信号处理方向的学生、搞自适应算法的工程师,以及所有需要在线更新模型而不是离线批量拟合的人。
1. 递推最小二乘法到底在解决什么问题
1.1 从批量最小二乘到在线递推
先回到最基础的问题。假设我们要估计一个线性模型的权重向量 $w$,每一时刻拿到输入向量 $u(i)$ 和期望输出 $d(i)$。批量最小二乘的做法是一次性收集 $n$ 组数据,然后最小化误差平方和:
$$ J(w)=\sum_{i=1}^{n}\left[d(i)-w^{T}u(i)\right]^{2} $$
把 $n$ 组输入排成矩阵 $U$,期望值排成向量 $d$,解就是熟知的法方程:
$$ \hat{w}(n)=\left(U^{T}U\right)^{-1}U^{T}d $$
这个写法很干净,但工程上有个痛点:数据是流式到达的,每来一个新样本,如果都重新组装矩阵、重新求逆,计算量会越来越大,而且旧数据未必还值得保留。系统的特性可能随时间缓慢变化,越旧的数据越不该继续参与本次估计。这时候就需要一种“边到边算”的更新方式——RLS就是这个需求的产物。
RLS的名字里“递推”两个字,指的正是权重向量 $w(n)$ 可以由 $w(n-1)$ 加上某个修正量得到,不重算历史数据。它的数学形式看起来复杂,但思想其实和“记账”很像:每次来一笔新账,你只需要在余额上做一次增量更新,不需要把过去所有账单重新加一遍。
1.2 RLS能用在哪些场景
RLS在工程里的出场频率非常高。凡是需要在线估计一个时变参数的线性模型,基本都能看到它的身影。典型的应用包括:
- 自适应信道均衡:通信接收端实时补偿信道失真,权重必须跟随信道变化。
- 系统辨识:给未知系统输入激励信号,在线估计系统的传递函数或脉冲响应系数。
- 有源噪声控制:用自适应滤波器生成反相信号抵消噪声,要求算法快速收敛。
- 自动控制中的在线参数估计:控制器参数需要根据工况实时调整时,用RLS估计模型参数。
- 金融时间序列的在线预测:用近期数据训练一个线性预测器,跟踪市场状态变化。
这些场景有一个共同特点:数据非平稳,模型不能一劳永逸。LMS(最小均方)算法也能做在线自适应,但RLS的优势在收敛速度,代价是更高的单步计算量。后面有一章会专门对比这两种算法,先把RLS本身的推导啃下来。
1.3 遗忘因子 $\lambda$ 的直觉
RLS引入了一个关键参数 $\lambda$,叫做遗忘因子,取值范围通常在 $(0,1]$。它的作用是给历史样本的误差加上指数衰减的权:
$$ J(n)=\sum_{i=1}^{n}\lambda^{n-i}\left[d(i)-w^{T}u(i)\right]^{2} $$
注意公式里的 $\lambda^{n-i}$,$n$ 是当前时刻,$i$ 是样本下标。当前样本的权重是 $\lambda^{0}=1$,越老的样本幂次越大、权重越小。$\lambda=1$ 时所有历史样本权重一样,退化成无限记忆的普通最小二乘;$\lambda$ 越小,旧样本被遗忘得越快,算法跟踪变化的能力越强,但受噪声影响也越大。
很多人在推导公式前忽略了这个参数的意义,导致后面初始化 $\lambda$ 时全靠蒙。我这里先挑明:$\lambda$ 本质上是“时间常数”的离散版本。一个系数大约在 $1/(1-\lambda)$ 个样本后衰减到原始权重的 $1/e$ 左右。比如 $\lambda=0.95$ 时,有效记忆长度大约是20个样本;$\lambda=0.99$ 时大约是100个样本。这个直觉对于后面调参非常重要。
2. RLS公式详细推导
2.1 带遗忘因子的代价函数
正式的RLS推导从加权代价函数出发。定义 $n$ 时刻的代价函数:
$$ J(n)=\sum_{i=1}^{n}\lambda^{n-i}e^{2}(i) $$
其中瞬时误差:
$$ e(i)=d(i)-w^{T}u(i) $$
为了后面推导方便,定义两个累加量:
$$ R(n)=\sum_{i=1}^{n}\lambda^{n-i}u(i)u^{T}(i) $$
$$ r(n)=\sum_{i=1}^{n}\lambda^{n-i}u(i)d(i) $$
你可以把 $R(n)$ 理解为“加权输入自相关矩阵”,把 $r(n)$ 理解为“加权输入-期望互相关向量”。把 $J(n)$ 对 $w$ 求梯度并令其为零,得到法方程:
$$ R(n)w(n)=r(n) $$
所以理论上最优解就是:
$$ w(n)=R^{-1}(n)r(n) $$
问题在于,每来一个新样本,$R(n)$ 变了,如果直接求逆,复杂度大概是 $O(N^{3})$,其中 $N$ 是滤波器阶数。RLS的全部数学改造,都是为了让这个求逆过程变成低成本的递推更新。
2.2 加权法方程的递推形式
先看 $R(n)$ 和 $r(n)$ 的递推关系。根据定义:
$$ R(n)=\sum_{i=1}^{n}\lambda^{n-i}u(i)u^{T}(i) =\lambda\sum_{i=1}^{n-1}\lambda^{n-1-i}u(i)u^{T}(i)+u(n)u^{T}(n) $$
于是得到:
$$ R(n)=\lambda R(n-1)+u(n)u^{T}(n) $$
同理:
$$ r(n)=\lambda r(n-1)+u(n)d(n) $$
这两个递推式本身很简单,就是“旧值整体衰减 $\lambda$,再加上当前样本贡献”。麻烦的是下一步:已知 $R(n-1)$ 和 $r(n-1)$,如何用它们快速得到 $w(n)$?
直接的想法是:
$$ w(n)=R^{-1}(n)r(n) $$
但需要解 $R^{-1}(n)$。如果能让 $P(n)=R^{-1}(n)$ 也满足一个递推式,整个问题就顺了。这就要用到矩阵求逆引理。
2.3 矩阵求逆引理:递推的数学支柱
矩阵求逆引理也叫 Sherman-Morrison-Woodbury 公式,它说的是:
$$ (A+BCD)^{-1}=A^{-1}-A^{-1}B\left(C^{-1}+DA^{-1}B\right)^{-1}DA^{-1} $$
看起来复杂,但它的作用很直观:已知一个矩阵 $A$ 的逆,如果它加上一个低秩的扰动 $BCD$,新矩阵的逆可以用 $A^{-1}$ 和几个矩阵乘除得到,不需要重新做高斯消元或显式求逆。
在RLS里,我们要处理的是:
$$ R(n)=\lambda R(n-1)+u(n)u^{T}(n) $$
对应引理中的:
$$ A=\lambda R(n-1),\quad B=u(n),\quad C=1,\quad D=u^{T}(n) $$
代入矩阵求逆引理,令 $P(n)=R^{-1}(n)$,$P(n-1)=R^{-1}(n-1)$:
$$ P(n)=\frac{1}{\lambda}P(n-1)-\frac{\frac{1}{\lambda}P(n-1)u(n)u^{T}(n)\frac{1}{\lambda}P(n-1)}{1+\frac{1}{\lambda}u^{T}(n)P(n-1)u(n)} $$
整理分母,得到:
$$ P(n)=\frac{1}{\lambda}P(n-1)-\frac{P(n-1)u(n)u^{T}(n)P(n-1)}{\lambda+u^{T}(n)P(n-1)u(n)} $$
这个式子里,分母 $\lambda+u^{T}(n)P(n-1)u(n)$ 是一个标量,整个第二项是一个秩为1的矩阵修正。它的复杂度是 $O(N^{2})$,比直接求逆的 $O(N^{3})$ 低一个量级,这正是RLS递推的核心优势。
2.4 三个核心递推式的完整推演
有了 $P(n)$ 的递推式,剩下的推演就顺理成章了。
先定义增益向量:
$$ K(n)=\frac{P(n-1)u(n)}{\lambda+u^{T}(n)P(n-1)u(n)} $$
那么 $P(n)$ 可以写成更整洁的形式:
$$ P(n)=\frac{1}{\lambda}\left[I-K(n)u^{T}(n)\right]P(n-1) $$
这个形式在代码里非常常见。它等价于“先用增益向量修正,再整体缩放”。
接下来推导权重更新。由法方程 $R(n)w(n)=r(n)$:
$$ R(n)w(n)=\lambda r(n-1)+u(n)d(n) $$
另一方面,用 $R(n)$ 的递推式去乘 $w(n-1)$:
$$ R(n)w(n-1)=\left[\lambda R(n-1)+u(n)u^{T}(n)\right]w(n-1) =\lambda r(n-1)+u(n)u^{T}(n)w(n-1) $$
两式相减:
$$ R(n)\left[w(n)-w(n-1)\right]=u(n)\left[d(n)-u^{T}(n)w(n-1)\right] $$
令先验误差为:
$$ e(n)=d(n)-u^{T}(n)w(n-1) $$
于是:
$$ w(n)-w(n-1)=R^{-1}(n)u(n)e(n)=P(n)u(n)e(n) $$
最后还需要证明 $P(n)u(n)=K(n)$。从 $P(n)$ 的递推式左乘 $u(n)$:
$$ P(n)u(n)=\frac{1}{\lambda}P(n-1)u(n)-\frac{1}{\lambda}K(n)u^{T}(n)P(n-1)u(n) $$
令 $\alpha=u^{T}(n)P(n-1)u(n)$,则:
$$ P(n)u(n)=\frac{1}{\lambda}P(n-1)u(n)-\frac{1}{\lambda}K(n)\alpha $$
注意 $K(n)=\frac{P(n-1)u(n)}{\lambda+\alpha}$,代入后:
$$ P(n)u(n)=\frac{1}{\lambda}P(n-1)u(n)-\frac{\alpha}{\lambda}\cdot\frac{P(n-1)u(n)}{\lambda+\alpha} $$
$$ =\frac{1}{\lambda}P(n-1)u(n)\left(1-\frac{\alpha}{\lambda+\alpha}\right) =\frac{1}{\lambda}P(n-1)u(n)\cdot\frac{\lambda}{\lambda+\alpha} =T(n)u(n) $$
等一下,这里要小心符号。重新写一下:
$$ P(n)u(n)=\frac{1}{\lambda}P(n-1)u(n)\left(1-\frac{\alpha}{\lambda+\alpha}\right) $$
括号里 $1-\frac{\alpha}{\lambda+\alpha}=\frac{\lambda}{\lambda+\alpha}$,所以:
$$ P(n)u(n)=\frac{1}{\lambda}P(n-1)u(n)\cdot\frac{\lambda}{\lambda+\alpha} =\frac{P(n-1)u(n)}{\lambda+\alpha}=K(n) $$
所以结论成立。这样权重更新就得到经典形式:
$$ w(n)=w(n-1)+K(n)e(n) $$
完整的三条核心递推式汇总如下:
$$ e(n)=d(n)-w^{T}(n-1)u(n) $$
$$ K(n)=\frac{P(n-1)u(n)}{\lambda+u^{T}(n)P(n-1)u(n)} $$
$$ w(n)=w(n-1)+K(n)e(n) $$
$$ P(n)=\frac{1}{\lambda}\left[I-K(n)u^{T}(n)\right]P(n-1) $$
这个顺序就是工程实现RLS时每一轮迭代的执行顺序,先算误差,再算增益,然后更新权重,最后更新协方差矩阵。很多资料把 $P$ 更新放在权重前面,本质上等价,但按顺序记更好理解。
2.5 先验误差与后验误差的关系
RLS推导里还有一个容易被忽略的细节:$e(n)$ 是用旧权重 $w(n-1)$ 计算出来的,叫先验误差;更新后的权重 $w(n)$ 对应的误差叫后验误差:
$$ \varepsilon(n)=d(n)-w^{T}(n)u(n) $$
代入 $w(n)=w(n-1)+K(n)e(n)$:
$$ \varepsilon(n)=e(n)-e(n)u^{T}(n)K(n)=e(n)\left[1-u^{T}(n)K(n)\right] $$
而 $K(n)=P(n)u(n)$,所以:
$$ u^{T}(n)K(n)=u^{T}(n)P(n)u(n) $$
进一步可以得到:
$$ \varepsilon(n)=e(n)\cdot\frac{\lambda}{\lambda+u^{T}(n)P(n-1)u(n)} $$
这个关系说明后验误差总是小于先验误差的绝对值,因为分母中 $\lambda+u^{T}(n)P(n-1)u(n)>\lambda$。换句话说,RLS每步更新都会利用新样本把预测误差降下来,这也从误差层面解释了为什么RLS收敛快。这个恒等式在推导更高级的RLS变形时经常用到,比如Exponentially Weighted RLS的稳定形式证明。
3. 参数含义、工程初始化与可复现实操
3.1 P(0)、λ、输入尺度到底怎么定
公式推完了,但直接上代码的人通常会懵在第一行:$P(0)$ 到底设成什么?
$P(n)$ 是输入自相关矩阵逆矩阵的估计,初始时刻没有数据,最常用的做法是设为单位阵乘以一个常数:
$$ P(0)=\delta I $$
$\delta$ 的取值有讲究。如果 $\delta$ 很小,比如 $10^{-6}$,相当于初始时刻“假装”输入能量很大、协方差矩阵很满,算法会非常不信任初始权重 $w(0)$ 的猜测,前几步修正幅度会很大;如果 $\delta$ 很大,比如 $100$,意味着初始协方差很大,模型“有把握说不知道”,前几步修正反而更大?这里需要掰扯清楚。
很多教材直接给“$\delta$取小正数”,但没解释为什么。实际经验是:$\delta$ 的选择要参照输入信号的功率。如果输入信号方差为 $\sigma_u^2$,那么 $R$ 的初始估计大约应该和 $N$ 倍方差一个量级。一个稳妥的工程做法是:
$$ \delta = \frac{1}{\sigma_u^{2}\cdot N} $$
或者干脆观测一段数据后取 $R(0)$ 的迹作为估计。否则 $\delta$ 一旦比实际信号能量小太多,$P$ 的初值会偏大,早期增益 $K$ 异常大,权重发生“假性剧震”,看起来像发散,实际只是初始化不当。
$\lambda$ 的取值同样取决于应用。平稳环境中,$\lambda$ 尽量接近1,比如 $0.995\sim1$,稳态误差小;非平稳环境中,需要跟踪参数变化,$\lambda$ 要调低一些,比如 $0.95\sim0.98$。$\lambda$ 越小,算法越“神经质”,跟随快但噪声放大;$\lambda$ 越大,算法越“迟钝”,估计平稳平滑但跟不上突变。这个权衡没有万能解,只能根据实际信号时间尺度试。
3.2 一个可复现的在线系统辨识示例
说了这么多,不如跑一个最简单的例子看效果。假设有一个未知的4阶FIR系统,真实系数为:
$$ w_{\text{true}}=[0.5,,-1.2,,0.8,,0.3] $$
我们给系统输入高斯白噪声,观测值叠加少量噪声,用RLS在线估计系统系数。用Python实现核心更新逻辑,代码非常简洁:
import numpy as np def rls_update(w, P, x, d, lam=0.98): alpha = x @ P @ x K = (P @ x) / (lam + alpha) e = d - x @ w w = w + K * e P = (P - np.outer(K, x) @ P) / lam return w, P, e np.random.seed(42) N = 4 w_true = np.array([0.5, -1.2, 0.8, 0.3]) w = np.zeros(N) P = np.eye(N) * 0.1 lam = 0.98 for n in range(2000): x = np.random.randn(N) d = w_true @ x + 0.01 * np.random.randn() w, P, e = rls_update(w, P, x, d, lam) if n in [0, 10, 50, 200, 1000]: print(f"step {n:5d}, w = {np.round(w, 4)}")运行之后你会看到,第一步由于 $P(0)=0.1I$,增益不算太大,权重只挪动一点;大约10步左右四个系数已经很有轮廓;到200步基本贴近真实值;之后一直在真值附近小幅波动,不再显著偏离。
这个例子还暴露了一个容易被忽略的点:输入信号必须是满秩激励。如果输入老是同一个值,$R(n)$ 是奇异矩阵或者条件数极大,RLS的表现会非常差。实际做系统辨识时,一旦发现不收敛,优先检查激励信号是否持续存在,而不是急着调 $\lambda$。
3.3 收敛速度与计算复杂度
RLS为什么收敛快?核心在于它用了输入的二阶统计量信息。增益向量 $K(n)$ 里的 $P(n-1)$ 本质上是对输入协方差的逆矩阵估计,相当于对不同方向的自适应步长做了归一化处理。LMS只有单个步长参数 $\mu$,输入信号特征值散布越大,各方向收敛速度差异就越大;而RLS相当于对输入做了“白化”,各个方向收敛速度接近,所以整体收敛快很多。
代价是计算量。RLS每一步复杂度是 $O(N^{2})$,主要来自 $P u$ 这个矩阵向量乘以及 $K u^{T}P$ 这个外积更新。LMS只需要 $O(N)$。在 $N$ 几百以上、采样率又很高的场景,RLS的实时性压力会很大。这也是为什么很多工业场景中简单系统用LMS,复杂系统才上RLS。选择不只取决于收敛要求,还要看硬件算力。
4. 使用中的常见问题、坑位与改进方向
4.1 常见问题排查速查表
接触RLS这几年,周围同事问得最多的问题几乎都集中在这几类。我整理了一张速查表,基本覆盖绝大多数现场故障:
| 现象 | 可能原因 | 排查与建议 |
|---|---|---|
| 权重早期剧烈震荡甚至发散 | $P(0)$ 与输入能量不匹配 | 检查输入信号方差,调整 $\delta$;或先用小批量数据初始化 $R(0),r(0)$ |
| 收敛太慢、跟踪不上变化 | $\lambda$ 太接近1 | 逐步降低 $\lambda$,观察误差均方值变化 |
| 稳态误差大、系数抖动明显 | $\lambda$ 太小 | 适当增大 $\lambda$,或用可变遗忘因子策略 |
| 权重重现突然跳变 | 输入信号出现共线性或短暂断激励 | 检查激励质量;考虑加入正则项或改用鲁棒版本 |
| 数值结果出现NaN或Inf | $P$ 矩阵失去正定性 | 改用平方根RLS或QR-RLS;检查是否有过大数值输入 |
| 系统参数突变后恢复慢 | 固定 $\lambda$ 导致记忆过长 | 考虑误差门控的变遗忘因子机制 |
4.2 数值稳定性:RLS最容易被忽视的弱点
标准RLS在理论上是漂亮的,但在有限精度浮点运算下,$P(n)$ 可能失去对称正定性。$P$ 一旦不再是对称正定,增益方向就错了,权重更新就会发散。这个问题在定点DSP实现上尤其严重,因为量化误差会不断累积。
工程上常用的改进思路有几条。第一条是平方根RLS(Square-Root RLS),直接递推 $P$ 的Cholesky因子或QR因子,不显式计算 $P$ 本身,数值稳定性好很多。第二条是加正则化项,在 $P$ 更新后强制对称化,比如 $P=(P+P^{T})/2$,虽然不彻底,但能延后数值恶化。第三条是定期复位或监测 $P$ 的特征值,发现异常就重建。如果你的系统需要7×24小时连续运行,强烈建议不要裸用标准RLS,至少要做对称化处理。
还有一个容易踩的坑:输入量级跨度太大时,$u^{T}Pu$ 这个标量可能溢出。比如输入信号幅值达到 $10^{3}$,$P$ 中某些元素又偏大,中间结果很容易爆。解决办法是先对输入做归一化,或者用对数增益的形式重写更新式。
4.3 RLS与LMS选型对比
很多新手会在RLS和LMS之间犹豫。两者都做在线自适应,但思路完全不同。LMS基于随机梯度下降,每次只沿瞬时误差梯度的反方向走一小步;RLS基于最小二乘的解,直接利用输入二阶统计量做一步最优修正。
| 对比维度 | LMS | RLS |
|---|---|---|
| 单步复杂度 | $O(N)$ | $O(N^{2})$ |
| 收敛速度 | 慢,受输入特征值分布影响大 | 快,基本无特征值散布敏感问题 |
| 稳态失调 | 通常偏大 | 可调 $\lambda$ 做到很小 |
| 跟踪非平稳能力 | 中等 | 强,但需要调 $\lambda$ |
| 数值敏感性 | 低 | 高,需维护 $P$ 正定性 |
| 实现复杂度 | 低 | 高 |
实际选型我给三条建议:如果阶数很低且输入接近白噪声,LMS完全够用,没必要上RLS;如果阶数高、输入相关性大、又要求快速收敛,上RLS,但优先考虑加固数值稳定性的改进版本;如果硬件资源紧张、需要极大规模并行,LMS类算法仍然是现实选择。
4.4 工程落地心得
最后分享一个这几年实打实攒下来的经验:RLS调参的第一要务不是调 $\lambda$,而是先调 $P(0)$。很多人一看到不收敛就猛调 $\lambda$,把值越调越小,结果系统越来越抖,最后以为是算法不行。其实大多数前期发散问题都出在初始化不合理。先花时间搞清楚输入信号的功率、阶数 $N$,把 $\delta$ 设到合理范围,再动 $\lambda$,问题往往解决一大半。
另外一个心得是:永远别在真实系统上直接跑标准的、没有任何防护的RLS。最少加三样东西:输入限幅、$P$ 对称化、以及误差超限保护。这三行代码成本极低,却能避免绝大多数灾难性故障。很多论文里RLS效果惊艳,是因为仿真数据干净;拿到现场,噪声、断流、脉冲干扰全来了,不加保护等于裸奔。
懂了这些,RLS对你来说就不再是一堆黑盒子公式,而是一个可以按部就班上手的顺手工具。真要写代码时,把三条核心递推式按顺序摆好,再处理好 $P(0)$ 和数值细节,整个系统就能稳定跑了。