2. NLMS算法

任务目标

本案例实现归一化最小均方(NLMS)自适应滤波器,核心解决LMS算法中固定步长对输入信号功率敏感的问题。通过仿真对比,验证NLMS在不同输入功率下保持一致的收敛速度,而标准LMS在输入功率变化时收敛性能显著退化。

算法原理与实现

NLMS的核心改进在于将步长参数除以输入向量的能量(加正则化常数防止除零):

μ_nlms = μ / (||x(n)||² + ε)

这使得等效步长与输入功率无关。滤波器系数更新公式为:

w(n+1) = w(n) + μ_nlms · e(n) · x(n)

其中误差信号 e(n) = d(n) - wᵀ(n)x(n)。本实现采用FIR滤波器结构,通过仿真对比NLMS与标准LMS在输入功率相差20dB时的学习曲线。

完整实现代码

import numpy as np
import matplotlib.pyplot as plt

def nlms_filter(x, d, M, mu, eps=1e-6):
    """NLMS自适应滤波器
    Args:
        x: 输入信号 (N,)
        d: 期望信号 (N,)
        M: 滤波器阶数
        mu: 归一化步长 (0 < mu < 2)
        eps: 正则化常数
    Returns:
        w_history: 系数历史 (N-M+1, M)
        e_history: 误差历史 (N-M+1,)
    """
    N = len(x)
    w = np.zeros(M)
    w_history = np.zeros((N - M + 1, M))
    e_history = np.zeros(N - M + 1)
    
    for n in range(N - M + 1):
        x_n = x[n:n+M][::-1]  # 当前输入向量
        y = np.dot(w, x_n)
        e = d[n+M-1] - y
        # 归一化步长更新
        norm_sq = np.dot(x_n, x_n) + eps
        w = w + (mu / norm_sq) * e * x_n
        w_history[n] = w
        e_history[n] = e**2
    return w_history, e_history

def lms_filter(x, d, M, mu):
    """标准LMS滤波器(用于对比)"""
    N = len(x)
    w = np.zeros(M)
    e_history = np.zeros(N - M + 1)
    
    for n in range(N - M + 1):
        x_n = x[n:n+M][::-1]
        y = np.dot(w, x_n)
        e = d[n+M-1] - y
        w = w + mu * e * x_n
        e_history[n] = e**2
    return e_history

# ===== 仿真参数 =====
np.random.seed(42)
M = 8           # 滤波器阶数
N = 2000        # 样本数
mu_nlms = 0.5   # NLMS步长
mu_lms = 0.01   # LMS步长(按高功率调优)

# 真实未知系统(随机FIR系数)
w_true = np.random.randn(M)
w_true /= np.linalg.norm(w_true)

# 生成两种功率的输入信号
x_high = np.random.randn(N) * 1.0    # 高功率 (方差=1)
x_low = np.random.randn(N) * 0.1     # 低功率 (方差=0.01)

# 通过未知系统生成期望信号(加噪声)
def generate_d(x, w_true, noise_var=1e-4):
    d = np.zeros_like(x)
    for n in range(M-1, len(x)):
        d[n] = np.dot(w_true, x[n-M+1:n+1][::-1])
    d += np.sqrt(noise_var) * np.random.randn(len(x))
    return d

d_high = generate_d(x_high, w_true)
d_low = generate_d(x_low, w_true)

# ===== 运行滤波 =====
# NLMS
_, e_nlms_high = nlms_filter(x_high, d_high, M, mu_nlms)
_, e_nlms_low = nlms_filter(x_low, d_low, M, mu_nlms)

# LMS(步长按高功率调优)
e_lms_high = lms_filter(x_high, d_high, M, mu_lms)
e_lms_low = lms_filter(x_low, d_low, M, mu_lms)

# ===== 绘制学习曲线(平滑处理) =====
def smooth(x, alpha=0.99):
    y = np.zeros_like(x)
    y[0] = x[0]
    for i in range(1, len(x)):
        y[i] = alpha * y[i-1] + (1-alpha) * x[i]
    return y

plt.figure(figsize=(10, 6))
plt.semilogy(smooth(e_nlms_high), label='NLMS 高功率')
plt.semilogy(smooth(e_nlms_low), label='NLMS 低功率')
plt.semilogy(smooth(e_lms_high), label='LMS 高功率')
plt.semilogy(smooth(e_lms_low), label='LMS 低功率')
plt.xlabel('迭代次数')
plt.ylabel('均方误差 (log)')
plt.title('NLMS vs LMS 在不同输入功率下的收敛性能')
plt.legend()
plt.grid(True, which='both', alpha=0.3)
plt.tight_layout()
plt.savefig('nlms_comparison.png', dpi=150)
plt.show()

# ===== 定量评估:稳态误差 =====
def steady_state_mse(e, skip=500):
    return 10 * np.log10(np.mean(e[-skip:]))

print("=== 稳态MSE (dB) ===")
print(f"NLMS 高功率: {steady_state_mse(e_nlms_high):.2f} dB")
print(f"NLMS 低功率: {steady_state_mse(e_nlms_low):.2f} dB")
print(f"LMS  高功率: {steady_state_mse(e_lms_high):.2f} dB")
print(f"LMS  低功率: {steady_state_mse(e_lms_low):.2f} dB")

运行与验证

环境依赖:Python 3.8+,NumPy,Matplotlib。无需额外安装。

运行方式:将代码保存为 nlms_demo.py,在终端执行:

python nlms_demo.py

验证方法:

  1. 程序运行后生成 nlms_comparison.png 图像,观察四条学习曲线。
  2. 通过标准:NLMS的两条曲线(高/低功率)收敛速度应基本一致,稳态误差接近;LMS在高功率下正常收敛,但在低功率下收敛速度显著变慢(曲线下降缓慢),且稳态误差明显高于NLMS。
  3. 终端输出的稳态MSE应显示:NLMS两种功率下数值接近(差异<3dB),而LMS两种功率下差异显著(通常>10dB)。

关键参数说明

  • μ_nlms=0.5:归一化步长,理论稳定范围(0,2),取0.5保证快速收敛且稳定。
  • μ_lms=0.01:固定步长,按高功率输入调优。若按低功率调优,则高功率下会发散。
  • 正则化常数ε=1e-6:防止输入能量为零时除零,同时避免步长过大。
  • 输入功率差异20dB:高功率方差1.0,低功率方差0.01,模拟实际信号功率波动场景。

常见问题

  • 若NLMS低功率收敛慢于预期:检查ε是否过大(ε应远小于信号能量),或μ值是否过小。
  • 若LMS低功率完全不收敛:这是预期现象,说明固定步长无法适应功率变化,这正是NLMS的改进动机。
  • 滤波器阶数M的影响:M越大,稳态误差越低但收敛越慢。本案例M=8为平衡选择。