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
验证方法:
- 程序运行后生成
nlms_comparison.png图像,观察四条学习曲线。 - 通过标准:NLMS的两条曲线(高/低功率)收敛速度应基本一致,稳态误差接近;LMS在高功率下正常收敛,但在低功率下收敛速度显著变慢(曲线下降缓慢),且稳态误差明显高于NLMS。
- 终端输出的稳态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为平衡选择。