简介:面向信号处理、控制工程与通信系统等需要在线参数估计的学习者,提供基于递归最小二乘(RLS)算法的自适应滤波MATLAB实现,可用于非平稳信号滤波与动态系统建模,相较LMS拥有更快收敛与更高精度。压缩包仅40KB,共含2个文件:RLS.m为核心算法脚本,完成权重迭代更新、遗忘因子调节及误差计算;RLS.fig为可视化图形界面,便于调整参数、观察滤波效果。目前已有239人学习下载,适合正在学习自适应滤波理论、希望快速获得可运行代码的学生或工程师直接参考。读者下载后可运行脚本对照公式理解RLS更新过程中预测向量、误差项与遗忘因子的作用,也可通过界面文件修改参数,对比不同遗忘因子下的收敛速度与稳态误差,为后续系统辨识、噪声抵消等应用提供可复用的代码框架。
1. 从 RLS.zip 开始:RLS 滤波解决的是自适应估计问题
RLS.zip 解压后通常是一两个 .c、.m 或 .py 文件,函数名里写着 rls 滤波。直接搬进工程,大概率会撞上输出发散、权系数震荡、结果和卡尔曼滤波拉不开差距这类问题。RLS(递推最小二乘)滤波算法属于自适应滤波家族,解决的不是把毛刺拉平,而是在线估计一个线性模型系数:每来一个新样本就修正一次权值。它比 LMS 收敛快一个数量级,代价是每步 O(N²) 的矩阵运算和更苛刻的数值稳定性。遗忘因子、初始协方差矩阵、更新方程里那一次除法,比复制代码重要得多。这套思路适合用过 LMS 或滑动平均滤波、觉得收敛太慢想换 RLS 的工程师,也覆盖从仿真到嵌入式 C 移植的完整路径。
2. RLS 滤波的递推原理与最小化 Python 实现
2.1 最小二乘目标函数与递推更新公式
批量最小二乘的做法是:给一组输入向量 u(i) 和期望值 d(i),求权向量 w 使误差平方和最小,标准解是 w = (UᵀU)⁻¹Uᵀd。问题在于矩阵求逆每次都要重算,不适合逐样本在线更新。RLS 把目标函数改成带遗忘因子的形式:
J(n) = Σ(i=0..n) λ^(n-i) e²(i)
λ 是 0 到 1 之间的遗忘因子,越早的误差权重越小。记 R(n) = Σ λ^(n-i) u(i)uᵀ(i),P(n) = R⁻¹(n),用矩阵求逆引理可以得到免显式求逆的递推式。每来一个样本,先算增益向量:
k(n) = P(n-1)u(n) / (λ + uᵀ(n)P(n-1)u(n))
再更新权系数和协方差矩阵:
w(n) = w(n-1) + k(n)e(n) P(n) = (P(n-1) - k(n)uᵀ(n)P(n-1)) / λ
其中 e(n) = d(n) - wᵀ(n-1)u(n) 是先验误差。整个递推每步 O(N²),N 是滤波器阶数;LMS 每步只有 O(N),这就是 RLS 收敛快但计算量大的根本原因。
2.2 RLS 滤波的 3 个核心参数及其作用
RLS 滤波可调参数不多,但每个都直接决定行为。实际调试时我按下面表格设初值,再根据信号特征微调:
| 参数 | 含义 | 常见取值 | 影响 |
|---|---|---|---|
| λ | 遗忘因子 | 0.98~0.999 | 越接近 1 跟踪越慢、稳态误差越小;越小对突变响应越快、噪声放大越明显 |
| δ(正则化) | 初始协方差倒数 | 0.01~10 | 控制 P(0) 大小,防止启动阶段增益过大导致权值乱跳 |
| N(阶数) | 回归向量长度 | 4~64 | 阶数不足时模型失配;过高时计算量与稳态波动同步上升 |
初始协方差取 P(0) = I/δ。δ 取 1 表示初始置信度中等;δ 很小相当于初始 P 很大,启动阶段权值跳动明显。λ 没有万能值,只能结合信号变化速度来整定,方法放到第 4 章展开。
2.3 Python 最小实现:rls_filter 函数逐行拆解
import numpy as np def rls_filter(x, d, order=4, lam=0.99, delta=1.0): """标准 RLS 滤波,逐样本在线更新。 x: 参考输入信号 d: 期望信号 order: 滤波器阶数 lam: 遗忘因子 delta: 正则化参数,P(0) = I / delta """ n = len(x) w = np.zeros(order) # 权系数向量 P = np.eye(order) / delta # 初始协方差矩阵 y = np.zeros(n) # 滤波器输出 e = np.zeros(n) # 先验误差 for i in range(order, n): u = x[i - order:i + 1][::-1] # 取最新 order+1 个点并反序 Pi = P @ u denom = lam + u @ Pi k = Pi / denom # 增益向量 k(n) y[i] = w @ u # 输出 e[i] = d[i] - y[i] # 期望与输出之差 w = w + k * e[i] # 权系数更新 P = (P - np.outer(k, u @ P)) / lam # 协方差递推 return y, e, w这段代码与上一节公式一一对应。u 取x[i-order:i+1][::-1]是为了让最新样本排在向量最前,符合数字滤波器的卷积习惯;顺序取窗会让权系数和真实滤波器系数反转,辨识结果对不上。denom 是标量,除法不涉及矩阵求逆,这是 RLS 的核心。P 的更新先减后除,浮点运算下 P 会缓慢失去对称性,正式工程里通常每几十步强制对称化一次:P = (P + P.T) / 2,这一行能避免大量隐蔽的数值问题。
3. 用 RLS 滤波跑通噪声对消与系统辨识
3.1 噪声对消实验:参考信号与期望信号怎么接
RLS 滤波最典型的场景是自适应噪声对消。一个麦克风拾取语音加干扰,另一个参考传感器拾取与干扰相关、与语音无关的噪声。参考噪声作为 x,主麦克风信号作为 d,RLS 估计的是干扰从参考点到主麦克风之间的传递路径,输出 y 拟合干扰分量,误差 e 就是滤除干扰后的信号。下面这段模拟完整过程:
import numpy as np rng = np.random.default_rng(42) n = 4000 t = np.arange(n) / 100.0 clean = np.sin(2 * np.pi * 3 * t) # 待保留的信号 ref_noise = rng.standard_normal(n) # 参考噪声 path = np.array([0.3, -0.5, 0.2, -0.1]) # 干扰传播路径 interf = np.convolve(ref_noise, path)[:n] # 与参考相关的干扰 d = clean + interf # 主通道期望信号 y, e, w = rls_filter(ref_noise, d, order=4, lam=0.995, delta=1.0) snr_in = 10 * np.log10(np.var(clean) / np.var(interf)) snr_out = 10 * np.log10(np.var(clean) / np.var(e - clean)) print(f"输入SNR={snr_in:.1f}dB 输出SNR={snr_out:.1f}dB")这里的关键是参考信号与干扰必须高度相关,且与有用信号不相关。参考通道一旦混进语音成分,RLS 会把有用信号一起消掉,输出 SNR 反而变差。工程上遇到“滤波结果发闷”时,先检查参考信号怎么接的,再调 λ,多数问题不在算法而在通道接法。
3.2 系统辨识实验:权系数收敛到真实冲激响应
第二种常见用法是系统辨识。给未知系统输入白噪声,把系统输出作为 d,输入作为 x,收敛后 w 就是系统冲激响应的估计。与批处理最小二乘不同,RLS 可以边采数据边输出估计,适合信道估计、回声消除这类在线场景。判断收敛的方法很直接:把权向量轨迹画出来,看是否逼近真实系数。
rng = np.random.default_rng(42) true_w = np.array([0.5, -0.3, 0.2]) # 待辨识的真实系统 x = rng.standard_normal(2000) d = np.convolve(x, true_w)[:2000] + 0.01 * rng.standard_normal(2000) y, e, w = rls_filter(x, d, order=3, lam=0.99, delta=1.0) print("估计系数:", np.round(w, 3))对 3 阶系统,收敛后 w 会稳定在 [0.5, -0.3, 0.2] 附近,误差 e 的功率降到接近加性噪声功率。阶数低于真实系统时残差明显变大;阶数给高时多余权值不会完全归零,而是保留小幅波动。选阶数的原则是宁高勿低,同时接受多一点计算量。
3.3 和滑动窗口滤波、卡尔曼滤波相比 RLS 赢在哪
RLS 滤波经常被拿来和滑动窗口滤波、滑动平均滤波、卡尔曼滤波比较。滑动平均和滑动窗口滤波本质上是固定系数低通,适合 ADC 采样去毛刺这类频率分离明确的场景,不需要参考信号,但应对不了相关性随时间变化的干扰。卡尔曼滤波是状态空间方法,需要过程噪声 Q 和观测噪声 R,模型准确时误差最优;RLS 可以看作状态向量恒定时卡尔曼滤波的简化版,少调两个噪声协方差矩阵,代价是没有状态预测能力。
| 方法 | 每步计算量 | 需要模型 | 典型场景 |
|---|---|---|---|
| 滑动平均滤波 | O(N) | 无 | ADC 平滑、毛刺抑制 |
| LMS 自适应滤波 | O(N) | 参考信号 | 收敛要求不高的初版方案 |
| RLS 滤波 | O(N²) | 参考信号 | 回声消除、信道估计、振动对消 |
| 卡尔曼滤波 | O(N²)~O(N³) | 状态转移与噪声统计 | 组合导航、目标跟踪 |
选型判断标准我一般这么定:干扰和有用信号频带重叠但存在相关参考源,用 RLS;没有参考源、只做平滑,滑动平均更划算;系统有明确动态方程,用卡尔曼滤波。互补滤波在姿态解算里融合加速度计和陀螺仪两个异源信号,与 RLS 没有直接替代关系。
4. RLS 滤波参数整定:遗忘因子、发散处置与嵌入式移植
4.1 遗忘因子 λ 的整定:跟踪速度与稳态波动的权衡
λ 是 RLS 滤波里唯一需要反复试的参数。λ = 0.99 时算法大约记住前 1/(1-λ) ≈ 100 个样本;λ = 0.999 时约 1000 个样本。有效记忆越长稳态误差越小,但对系统突变响应越慢。要直观验证,可以在数据中间点突然改变真实权系数,观察权向量重新收敛需要多少步:λ 大时要几百步,λ 小时几十步追上,但稳态波动也更大。
实际整定流程我一般走三步。先固定 order 和 delta,把 λ 从 0.999 逐步往 0.95 试,观察误差 e 的均方值,出现明显噪声放大就回退一档;再叠加一次人为跳变,确认重新收敛时长满足系统响应要求;最后用第 5 章的 Monte Carlo 方法确认稳态失调量可接受。不要同时改两个参数,RLS 的响应是参数耦合的,一次只动一个才能定位问题。
提示:改动输入信号幅值后先做归一化再调 λ,否则两个变量互相干扰,调出来的参数换一段数据就失效。
4.2 P 矩阵发散、数值下溢与正则化手段
RLS 滤波最典型的故障是 P 矩阵发散。症状是前几十步正常,之后权系数突然跳变、误差震荡。原因通常是输入幅值过小导致 P 数值膨胀,或者 P 在递推中失去对称正定性。处置手段按优先级排列:先把输入归一化,让 x 的方差落在 0.1~1 之间,P 的更新对尺度非常敏感;每步递推后执行P = (P + P.T) / 2强制对称;仍然发散就改用平方根 RLS,递推里更新 P 的 Cholesky 因子,本质等价但对舍入误差不敏感。P 出现负对角元是数值退化的明确信号,应该当作错误标志处理。
正则化 δ 的本质是约束初始协方差。δ 取 0.01 相当于 P(0) = 100I,前期权值调整幅度大,适合信噪比低的启动阶段;δ 取 10 相当于 P(0) = 0.1I,收敛慢但稳定。固定信号场景下 δ 在 1 附近通常不需要大改。下面表格是现场排错时对照用的:
| 现象 | 原因 | 处置 |
|---|---|---|
| 权系数突然跳变 | P 矩阵失去正定性 | 强制对称化 + 输入归一化 |
| 启动阶段输出过大 | δ 太小,P(0) 太大 | δ 提到 1~10 |
| 收敛后噪声偏大 | λ 太小或阶数过高 | λ 回退到 0.99 以上,降低阶数 |
| 突变跟踪跟不上 | λ 过于接近 1 | λ 调到 0.95~0.98 |
4.3 C 语言移植:固定数组与单步更新函数
到嵌入式端,RLS 滤波的移植重点是去掉动态内存、把矩阵运算摊平。下面阶数固定为 4 的单步函数可以直接放进定时中断,按采样率逐点调用:
#define RLS_ORDER 4 typedef struct { float lam; float w[RLS_ORDER]; // 权系数 float P[RLS_ORDER][RLS_ORDER]; // 协方差矩阵 float buf[RLS_ORDER]; // 窗缓存,buf[0] 为最新样本 } rls_t; float rls_step(rls_t *rls, float x, float d) { int i, j; float u[RLS_ORDER], k[RLS_ORDER], Pu[RLS_ORDER]; float denom, y, e; for (i = RLS_ORDER - 1; i > 0; i--) // 窗口右移,腾出最新位 rls->buf[i] = rls->buf[i - 1]; rls->buf[0] = x; for (i = 0; i < RLS_ORDER; i++) u[i] = rls->buf[i]; for (i = 0; i < RLS_ORDER; i++) { // Pu = P * u float s = 0.0f; for (j = 0; j < RLS_ORDER; j++) s += rls->P[i][j] * u[j]; Pu[i] = s; } denom = rls->lam; // 标量分母 for (i = 0; i < RLS_ORDER; i++) denom += u[i] * Pu[i]; for (i = 0; i < RLS_ORDER; i++) // 增益向量 k k[i] = Pu[i] / denom; y = 0.0f; for (i = 0; i < RLS_ORDER; i++) y += rls->w[i] * u[i]; e = d - y; // 先验误差 for (i = 0; i < RLS_ORDER; i++) // 权系数更新 rls->w[i] += k[i] * e; for (i = 0; i < RLS_ORDER; i++) // 协方差递推 for (j = 0; j < RLS_ORDER; j++) rls->P[i][j] = (rls->P[i][j] - k[i] * Pu[j]) / rls->lam; return e; }这段代码把协方差更新展开成一维循环,Pu 复用了两次计算结果,避免重复乘算。和单片机低通滤波里用左移右移代替乘除的做法不同,RLS 的分母除法没法省,float 精度在连续运行数万步后误差会累积,资源允许时建议 P 用 double,w 和 u 用 float。与 MCU 上常见的“C 语言 ADC 值滤波函数”相比,RLS 的接口多了参考输入和期望输入两个参数,移植后最常见的错误是通道接反:参考信号接成被滤波信号本身,RLS 就退化成一个意义不大的预测器。
5. 用 Monte Carlo 数据验证 RLS 滤波的收敛与跟踪
5.1 多次独立实验的误差均方与失调量
单次运行看到权系数“差不多收敛”不足以证明滤波器可靠,RLS 单次实现的方差不小。常见做法是跑 50 次独立实验,每次重新生成输入和噪声,统计误差功率的平均:
trials = 50 mses = np.zeros(trials) for t in range(trials): x = rng.standard_normal(2000) d = np.convolve(x, true_w)[:2000] + 0.01 * rng.standard_normal(2000) y, e, w = rls_filter(x, d, order=3, lam=0.99, delta=1.0) mses[t] = np.mean(e[1000:] ** 2) # 只统计收敛后的后 1000 点 misadjustment = np.mean(mses) / 0.0001 # 除以噪声功率 0.01^2 print(f"失调量 ≈ {misadjustment:.2f}")失调量是稳态额外误差与最小误差之比,RLS 在 λ = 0.99 时通常小于 0.1,同样数据长度下 LMS 很难达到这个水平。统计时前 1000 点属于收敛期,必须从均值里剔除,否则结果会被启动阶段拉高,得出滤波器“性能差”的错误结论。
5.2 阶跃突变下的跟踪验证
在线滤波场景里最重要的验证项是跟踪能力:在数据中点把真实权系数乘以 -1,观察误差均方是否出现一个高峰后又回落。回落时间大约等于有效记忆长度 1/(1-λ),如果超过系统允许的响应时间,说明 λ 偏大,需要回到第 4 章重新整定。把 RLS 的稳态误差和卡尔曼滤波放在同一组数据上对比,是很好的交叉验证:卡尔曼在状态模型准确时给出理论最优结果,RLS 若明显差于卡尔曼,多半是 λ 或 order 设置不合理,而不是算法本身的问题。验证通过后再把第 4 章的 C 代码移植到目标板,用同一份数据回放测试,对比嵌入式端和 Python 端的误差曲线,确认字节级一致后,这套 RLS 滤波才算真正落地。
本文还有配套的精品资源,点击获取