4、最小二乘法基础:离线最小二乘(LS)与递推最小二乘(RLS)原理

说到参数辨识,最小二乘法是绕不开的基础。我入行那会儿,第一次接触电机参数辨识,师傅丢给我一堆数据说:「用最小二乘算一下。」我当时一脸懵,心想这玩意儿到底怎么把电阻电感算出来的?后来啃了几本书,又亲手调过几版代码,才算真正搞明白。

说白了,最小二乘法的核心就一句话:找一组参数,让模型输出和实际测量值的误差平方和最小。你想想看,我们建电机模型,不就是想让模型算出来的电流、转速跟实际测到的尽量接近吗?

核心思想:误差平方和最小化。数学上就是求解一个优化问题,让残差向量的二范数最小。

4.1 离线最小二乘(LS)—— 一次算清

离线最小二乘,也叫批处理最小二乘。它的做法很简单:先把所有数据都采集完,然后一次性把参数算出来。

假设我们有一个线性模型:

y = θ₁·x₁ + θ₂·x₂ + ... + θₙ·xₙ + e

写成矩阵形式就是:

Y = X·θ + E

其中 Y 是测量值向量,X 是数据矩阵,θ 是待求参数向量,E 是误差向量。

那么最小二乘的解就是:

θ̂ = (Xᵀ·X)⁻¹·Xᵀ·Y

这个公式看着简单,但实际用起来有几个坑。我在项目中遇到过一个问题:采集了1000组数据,直接套公式算,结果参数完全不对。后来一查,发现 Xᵀ·X 这个矩阵接近奇异,根本求不了逆。

注意:Xᵀ·X 必须满秩才能求逆。如果数据不够丰富,或者某些输入高度相关,这个矩阵就可能奇异或病态。我建议在计算前先检查条件数(condition number),如果太大就要考虑加正则化或者重新设计实验。

离线LS的优点很明显:

  • 精度高——用了全部数据,统计特性好
  • 实现简单——一个公式搞定
  • 适合离线分析——做仿真、标定的时候很实用

缺点也很突出:

  • 计算量大——矩阵求逆是 O(n³) 的复杂度,数据量大了扛不住
  • 不能在线更新——新数据来了必须全部重算
  • 存储开销大——所有历史数据都得存着

所以,离线LS更适合做离线标定、实验室分析。要是想在电机运行过程中实时更新参数,就得用递推最小二乘了。

4.2 递推最小二乘(RLS)—— 边跑边学

递推最小二乘,说白了就是「在线版」的最小二乘。它的思路是:每来一个新数据,就用当前的结果加上一个修正项,得到新的参数估计。不用重新算全部数据,效率高很多。

RLS的递推公式长这样:

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) / λ

其中:

  • θ̂(k) 是第 k 步的参数估计
  • φ(k) 是第 k 步的回归向量(输入数据)
  • y(k) 是第 k 步的测量值
  • K(k) 是增益向量,决定修正力度
  • P(k) 是协方差矩阵,反映估计的不确定性
  • λ 是遗忘因子,0 < λ ≤ 1

我的经验:遗忘因子 λ 很关键。λ=1 就是标准RLS,所有数据权重一样;λ<1 会逐渐「遗忘」旧数据,适合时变系统。我一般从 λ=0.98 开始调,如果参数变化快就降到 0.95,变化慢就升到 0.995。但注意,λ 太小会导致协方差矩阵 P 膨胀,参数会剧烈抖动。

RLS的流程可以概括为:

  1. 初始化——设置 θ̂(0) 和 P(0)。我习惯用 P(0)=α·I,α 取 100~1000,表示初始不确定性大
  2. 预测误差——计算 e(k) = y(k) - φᵀ(k)·θ̂(k-1)
  3. 计算增益——K(k) = P(k-1)·φ(k) / [λ + φᵀ(k)·P(k-1)·φ(k)]
  4. 更新参数——θ̂(k) = θ̂(k-1) + K(k)·e(k)
  5. 更新协方差——P(k) = [I - K(k)·φᵀ(k)]·P(k-1) / λ
  6. 循环——回到步骤2,等待下一个数据

下面这张图展示了RLS的核心逻辑:

递推最小二乘(RLS)核心流程 初始化 θ̂(0), P(0) 新数据 φ(k), y(k) 到达 计算预测误差 e(k) 计算增益 K(k) 更新参数 θ̂(k) 和协方差 P(k) θ̂(k) = θ̂(k-1) + K(k)·e(k) 关键参数 λ:遗忘因子 P(0):初始协方差 θ̂(0):初始参数 优点 ✓ 在线实时更新 ✓ 存储量小 ✓ 适合时变系统

4.3 LS 与 RLS 的对比

这两种方法各有各的用武之地。我整理了一个对比表,方便你快速把握:

对比项 离线最小二乘(LS) 递推最小二乘(RLS)
数据使用方式 一次性使用全部数据 逐点递推,边来边算
计算复杂度 O(N·n² + n³),N为数据量 O(n²) 每步,n为参数个数
存储需求 大,需存所有数据 小,只需存当前状态
在线更新能力 不支持,需重算 支持,实时更新
时变系统适应性 差,无法跟踪变化 好,遗忘因子可调
典型应用场景 离线标定、实验室分析 在线辨识、自适应控制

4.4 实际应用中的注意事项

我在实际项目中踩过不少坑,这里分享几个关键点:

数据激励问题:RLS虽然能在线更新,但如果输入信号不够丰富(比如电机恒速运行),参数估计会发散。我遇到过一台风机,转速基本不变,RLS跑着跑着参数就飘了。解决办法是注入持续激励信号,比如在电流环上加一个小幅的PRBS(伪随机二进制序列)。

数值稳定性:RLS的协方差矩阵 P(k) 理论上是对称正定的,但实际计算中由于舍入误差,可能会失去正定性。我建议每步更新后强制对称化:P = (P + Pᵀ)/2。另外,如果发现 P 矩阵对角线元素异常增大,说明数值不稳定了,需要重置或者改用平方根RLS。

还有一个容易被忽略的点:初始参数的选择。如果 θ̂(0) 离真实值太远,RLS初期会有很大的超调。我一般先用离线LS跑一小段数据得到一个初始值,再切换到RLS模式。这样既保证了初值合理,又能在线跟踪变化。

4.5 一个简单的RLS代码示例

下面是一个Python风格的RLS实现,用于辨识一个一阶系统:

import numpy as np

class RLS:
    def __init__(self, n_params, lambda_=0.98, P0=1000):
        self.n = n_params
        self.lambda_ = lambda_
        self.theta = np.zeros(n_params)
        self.P = P0 * np.eye(n_params)

    def update(self, phi, y):
        # 预测误差
        e = y - np.dot(phi, self.theta)

        # 增益向量
        denom = self.lambda_ + np.dot(phi, np.dot(self.P, phi))
        K = np.dot(self.P, phi) / denom

        # 更新参数
        self.theta = self.theta + K * e

        # 更新协方差
        self.P = (np.eye(self.n) - np.outer(K, phi)) @ self.P / self.lambda_

        # 强制对称
        self.P = (self.P + self.P.T) / 2

        return self.theta.copy()

# 使用示例
rls = RLS(n_params=2, lambda_=0.98)
for k in range(1000):
    phi = np.array([data_x1[k], data_x2[k]])
    y = data_y[k]
    theta_est = rls.update(phi, y)

关键点:代码中的 np.outer(K, phi) 就是 K(k)·φᵀ(k) 的向量化实现。强制对称那一步虽然多了一点点计算,但能避免很多数值问题。我建议你在实际代码里都加上这一行。

好了,这一章的内容就到这里。最小二乘法是参数辨识的基石,离线LS和递推RLS各有各的适用场景。搞懂了这两个,后面再学扩展卡尔曼滤波、粒子滤波这些高级方法,你就能理解它们是怎么在最小二乘的基础上一步步演进的。