4. 自适应滤波算法:LMS、NLMS 与 RLS 的实战选择
自适应滤波,说白了就是让滤波器自己学会怎么干活。你不需要事先知道噪声的统计特性,算法会一边处理一边调整系数。我最早接触这玩意儿是在一个车载通话降噪项目里,那时候天真地以为随便找个算法就能搞定,结果被现实狠狠教育了一顿。
今天咱们就聊聊三种最常用的自适应算法:LMS、NLMS 和 RLS。它们各有脾气,选对了事半功倍,选错了……嗯,你会浪费不少调试时间。
4.1 LMS 算法:最朴素的梯度下降
LMS(最小均方)算法是自适应滤波的入门课。它的思想特别直白:每次迭代都沿着误差曲面的负梯度方向走一小步,让均方误差越来越小。
数学上长这样:
// LMS 算法核心更新公式
// 滤波器系数向量 w(n),输入向量 x(n),期望信号 d(n)
// 步长因子 μ,滤波器阶数 N
y(n) = w^T(n) * x(n); // 滤波器输出
e(n) = d(n) - y(n); // 误差信号
w(n+1) = w(n) + 2 * μ * e(n) * x(n); // 系数更新
这里有个关键参数 μ(步长因子)。它决定了算法收敛的快慢和稳定性。
| μ 取值 | 收敛速度 | 稳定性 | 稳态误差 |
|---|---|---|---|
| μ 太小(如 0.001) | 极慢,可能需要数万次迭代 | 非常稳定 | 低 |
| μ 适中(如 0.01) | 较快,几百次迭代可收敛 | 良好 | 中等 |
| μ 太大(如 0.1) | 可能不收敛 | 发散风险高 | 高 |
LMS 最大的优点是 计算量小——每次迭代只需要 O(N) 次乘加运算。在 DSP 或者低功耗 MCU 上跑起来毫无压力。但它的缺点也很明显:收敛速度对输入信号的功率非常敏感。如果输入信号忽大忽小,步长就不好选了。
4.2 NLMS 算法:给步长加个归一化
NLMS(归一化 LMS)就是来解决上面那个问题的。它把步长除以输入信号的功率,让更新步长自动适应信号的强弱变化。
// NLMS 算法核心更新公式
// 相比 LMS,多了一个归一化因子
e(n) = d(n) - w^T(n) * x(n);
// 归一化步长:μ / (||x(n)||² + δ)
// δ 是一个很小的正数,防止分母为零
w(n+1) = w(n) + (μ / (x^T(n)*x(n) + δ)) * e(n) * x(n);
你看,分母是输入向量的能量(L2 范数的平方)。当输入信号突然变大时,分母变大,步长自动缩小;信号变小时,步长自动放大。这样一来,收敛速度就稳定多了。
NLMS 的收敛速度比 LMS 快不少,尤其是在输入信号动态范围大的场景。比如在通话降噪中,说话声音大小变化很大,NLMS 能自动适应,不会因为突然的大声导致滤波器发散。
4.3 RLS 算法:用遗忘因子换速度
RLS(递归最小二乘)走的是另一条路。它不搞梯度下降,而是直接最小化从开始到现在所有误差的加权平方和。每次迭代都利用之前的信息,所以收敛速度极快。
// RLS 算法核心步骤(简化版)
// 遗忘因子 λ(通常 0.95 ~ 0.9995)
// 逆相关矩阵 P(n)
// 增益向量
k(n) = (P(n-1) * x(n)) / (λ + x^T(n) * P(n-1) * x(n));
// 先验误差
e(n) = d(n) - w^T(n-1) * x(n);
// 系数更新
w(n) = w(n-1) + k(n) * e(n);
// 逆相关矩阵更新
P(n) = (1/λ) * (P(n-1) - k(n) * x^T(n) * P(n-1));
RLS 的收敛速度通常比 LMS 快一个数量级。你想想看,LMS 可能需要 1000 次迭代才能收敛,RLS 可能 100 次就搞定了。这在跟踪快速变化的噪声时特别有用。
但代价也很明显:计算量是 O(N²)。N=64 时,RLS 每次迭代需要 4000 多次乘加,而 LMS 只要 64 次。在嵌入式系统上,这个差距是致命的。
| 对比项 | LMS | NLMS | RLS |
|---|---|---|---|
| 每次迭代计算量 | O(N) | O(N) | O(N²) |
| 收敛速度 | 慢 | 中等 | 快 |
| 对输入功率敏感度 | 高 | 低 | 低 |
| 稳态误差 | 中等 | 中等 | 低 |
| 数值稳定性 | 好 | 好 | 较差(需正则化) |
| 适用硬件 | MCU, DSP | MCU, DSP | ARM Cortex-A, FPGA |
4.4 收敛速度与稳定性的博弈
这三个算法本质上都在做同一件事:在收敛速度和稳态误差之间找平衡。你想想看,步长越大收敛越快,但到了稳态之后,滤波器系数会在最优值附近来回晃,稳态误差就大。步长小了,稳态误差小,但收敛慢得像蜗牛。
RLS 之所以快,是因为它用了遗忘因子 λ。λ 越接近 1,算法记忆越长,收敛越慢但稳态误差越小;λ 越小(比如 0.95),算法能快速跟踪变化,但稳态误差会变大。
最后说一句:别迷信算法复杂度。我在一个主动降噪耳机项目里,一开始用 RLS 觉得高大上,结果功耗超标、延迟过大。后来换成 NLMS,把阶数从 128 降到 64,收敛速度虽然慢了点,但整体效果反而更好——因为延迟低了,系统稳定性提升了。
选算法,先看你的硬件能承受多少 MIPS,再看噪声是平稳还是突变,最后才决定用哪个。这个顺序别搞反了。