简介:这是一套基于MATLAB实现的LMS算法源码,并整合了AdaGrad、RMSProp、Adam三种自适应学习率优化策略,面向毕业设计、课程设计以及项目开发场景,适合需要研究自适应滤波、梯度下降优化或算法对比的MATLAB使用者。资源包共13个文件,其中6个.m脚本分别实现标准LMS训练、测试以及三种优化器训练版本,3个.fig和3个.jpg用于保存和展示对比结果,1个.md说明文档介绍代码结构与实验思路;整个压缩包仅496KB,轻量易用,便于快速下载与二次修改。目前已有101人学习下载。代码刻意将标准LMS与三种优化版本分开存放,主程序统一调用,可直观对比不同学习率策略下的收敛曲线和稳态误差;同时支持修改步长、迭代次数等参数进行扩展实验,既适合本科毕设和课程设计中的算法演示,也适合工程人员在此基础上做进一步开发。
1. 用自适应学习率改造LMS:从固定步长到逐分量缩放
跑过LMS自适应滤波的人多半有过这种体验:步长μ调大一点,误差曲线刚冲下去就开始震荡;调小一点,权值像蜗牛爬,等收敛了参考信号早已变了。固定步长是LMS的核心设计,也是它最别扭的地方——收敛速度与稳态失调是一对天然矛盾。把AdaGrad、RMSProp、Adam这三种在深度学习中常用的自适应学习率优化器套到LMS的权值更新上,正是为了用逐分量、逐时刻的缩放来解决这个矛盾。
这个题目是毕业设计、课程设计里的常见选题,但常见的写法止步于“跑通三种优化器并画出对比图”。本文会把LMS的递推骨架先拆开,再分别给出三种优化器的MATLAB实现、参数含义和调节边界,最后落到系统辨识场景下怎么对比收敛曲线、怎么查发散原因。信号处理方向的学生、需要做自适应滤波仿真的工程师,以及想用MATLAB把“经典算法+现代优化器”做成完整项目的读者,都能按文中的代码直接起步,不需要额外工具箱,有MATLAB基础环境就够。
2. LMS算法的递推骨架与固定学习率的边界
2.1 从维纳滤波到LMS:递推公式拆开看
维纳滤波要求已知输入信号的自相关矩阵R与输入-期望互相关向量p,再解维纳-霍夫方程。现实中的信号统计特性随时间变化,矩阵求逆既昂贵又不实时。LMS的做法是用瞬时梯度替代统计梯度:定义误差
e(n)=d(n)-wᵀx(n)
其中x(n)是由最近order个输入样本组成的向量,w是权值向量。取损失函数J(w)=0.5e²(n),则瞬时梯度为
∇J=-e(n)x(n)
顺着负梯度方向走一步,得到最核心的权值递推式:
w(n+1)=w(n)+μ·e(n)·x(n)
这个式子就是整篇文章的地基。μ是固定学习率,它同时控制收敛速度与稳态失调。收敛的必要条件是0<μ<2/λ_max,λ_max是输入自相关矩阵的最大特征值。输入功率越大,μ必须越小,否则权值更新会在最优解附近来回跳动甚至发散。
2.1.1 为什么“最小均方”却无法同时要速度和精度
把权值更新看成随机逼近过程:μ大,等效于在噪声梯度方向上放大步伐,收敛快,但稳态时权值在维纳解周围的波动也大;μ小,波动小,但瞬态时间常数与1/(μλ)同量级,收敛慢。实际工程中若输入信号的功率波动范围超过一个数量级,固定μ几乎不可能选得合适。这就是引入自适应学习率的最大理由:让每个权值分量拥有独立的等效学习率。
2.2 最小可运行:MATLAB里写一个LMS函数
为了后续对比公平,先实现一个不带任何优化的固定步长LMS,输入为x、期望信号d、滤波器阶数order和步长mu,输出为权值序列与误差序列。
function [w, e] = lms_fixed(x, d, order, mu) % LMS固定步长实现 % x: 输入信号,列向量 % d: 期望信号,列向量 % order: 滤波器抽头数 % mu: 固定学习率 N = length(x); w = zeros(order, 1); % 权值初始化为0 e = zeros(N, 1); % 记录每个时刻的误差 for n = order:N xn = x(n:-1:n-order+1); % 构造输入向量,注意时间倒序 yn = w.' * xn; % 滤波器输出 en = d(n) - yn; % 先算误差 w = w + mu * en * xn; % 再更新权值 e(n) = en; end end代码逻辑分三步:构造输入向量、计算误差、执行权值更新。这里必须强调x(n:-1:n-order+1)的倒序切片,它让抽头延迟线与卷积的定义对齐,顺序反了会导致权值与输入错位,实验结果直接发散。mu的取值与输入信号幅度强相关,文件开头可以加一行归一化:
x = (x - mean(x)) / std(x);把输入标准化后再跑,mu=0.01、0.03这类数值才有可移植性。
2.2.1 用误差平方的滑动平均看收敛
直接画误差序列会看到剧烈抖动,肉眼很难判断收敛趋势。常见做法是对误差平方做指数滑动平均,MATLAB里一句即可:
mse_avg = filter(0.01, [1, -0.99], e.^2);这里0.99是遗忘因子,等效时间窗口约为100个样本。比plot(e)更可靠,后续所有对比曲线的绘图都建议用这种处理后的曲线。
2.3 学习率μ的稳定性上界与发散迹象
固定步长LMS的调参本质上是在两个指标之间找平衡,下表列出μ在不同取值区间下的典型表现,可作为调试时的判断依据。
| μ取值区间 | 收敛速度 | 稳态失调 | 误差曲线形态 |
|---|---|---|---|
| 远小于上界 | 很慢 | 很小 | 单调平缓下降 |
| 接近上界的一半 | 快 | 适中 | 快速下降后进入平稳带 |
| 接近或超过上界 | 快但危险 | 显著增大 | 先降后震荡,甚至上翘 |
| 明显超过上界 | 不收敛 | 无法定义 | 持续增长,出现NaN |
工程上常用μ<1/trace(R)作为粗略上界,若不想估计自相关矩阵,也可以直接用1/mean(x.^2)估计。发散的两个典型标志:误差平方的滑动平均连续若干点不降反升;或者权值向量w出现数量级跳变。遇到NaN或Inf时先不要怀疑算法,优先检查输入是否包含NaN、d与x是否长度一致、xn切片是否越界。
3. AdaGrad、RMSProp、Adam优化LMS:三种策略的MATLAB实现
3.1 为什么深度学习里的优化器能搬到LMS上
深度学习优化器解决的问题是梯度在不同维度上尺度不一致,希望每个参数都有独立的有效学习率。LMS的瞬时梯度−e(n)x(n)同样存在这个问题:当输入信号是有色噪声或语音时,各抽头位置的输入功率差异明显,单一μ会让大功率抽头震荡、小功率抽头收敛迟缓。把AdaGrad、RMSProp、Adam中的累积梯度统计量引入LMS权值更新,本质上是把标量μ替换成一个逐分量调节的自适应系数。三者共享同一个框架:维护额外的梯度统计量,用统计量对瞬时梯度做归一化。区别只在于统计量的计算方式不同。
3.1.1 统一视角:三个优化器都是“梯度除以尺度”
AdaGrad用全历史梯度的平方和做分母;RMSProp用指数滑动平均的梯度平方做分母;Adam在RMSProp基础上增加一阶矩估计并做偏差校正。理解了这一层,LMS部分不需要任何改动,只需改动权值更新那一行,这也是这个课题最适合做进阶实验的原因:LMS的滤波器结构是固定的,变量全在更新策略上,实验结果容易归类解释。
3.2 AdaGrad-LMS:累积平方梯度做逐分量缩放
AdaGrad为每个权值分量维护一个累积量G(n),每一步把瞬时梯度的平方累加进去,更新时用sqrt(G)+ε去除学习率。算法特征很明显:梯度平方持续累积,分母单调增大,等效学习率随时间严格衰减,越到后期更新越保守。
function [w, e] = lms_adagrad(x, d, order, eta, eps) % AdaGrad优化的LMS % eta: 基础学习率,区别于固定步长mu % eps: 防止除零的小常数,通常取1e-8 N = length(x); w = zeros(order, 1); G = zeros(order, 1); e = zeros(N, 1); for n = order:N xn = x(n:-1:n-order+1); en = d(n) - w.' * xn; grad = -en * xn; % LMS瞬时梯度 G = G + grad.^2; % 累积平方梯度,逐元素平方 w = w - eta * grad ./ (sqrt(G) + eps); e(n) = en; end endgrad.^2是对每个分量单独做平方,G的维度与w保持一致;./表示逐元素相除。AdaGrad-LMS中eta的典型取值在0.01到0.1之间,比固定步长的mu可以大一些,因为分母项会不断压制学习率。它适合输入信号较稀疏、梯度方差大的场合,但不适合非平稳输入——一旦G累计得过大,后期无论误差多大都没有能力继续更新,这也是它后来被RMSProp取代的主要原因。
3.3 RMSProp-LMS:滑动窗口解决累积衰减问题
RMSProp把AdaGrad的“全历史累积”改成“指数滑动平均”,引入衰减系数rho。每一步更新为
G(n)=rho·G(n−1)+(1−rho)·grad.²
这样近期的梯度信息权重更大,远古梯度的影响按指数衰减,等效于只统计最近约1/(1−rho)个样本的梯度能量。在MATLAB里,只需要把上一节函数里的累积行替换掉。
function [w, e] = lms_rmsprop(x, d, order, eta, rho, eps) % RMSProp优化的LMS % rho: 滑动平均衰减系数,常用0.9或0.99 N = length(x); w = zeros(order, 1); G = zeros(order, 1); e = zeros(N, 1); for n = order:N xn = x(n:-1:n-order+1); en = d(n) - w.' * xn; grad = -en * xn; G = rho * G + (1 - rho) * grad.^2; % 指数滑动平均 w = w - eta * grad ./ (sqrt(G) + eps); e(n) = en; end endrho取0.9时有效窗口约10个样本,取0.99时约100个样本。窗口越短,对输入功率突变的响应越快,但稳态时梯度估计抖动也越大。做LMS实验时我一般先用rho=0.99,因为滤波器的收敛过程相对平缓,过短的窗口会把噪声引入等效学习率中,产生不必要的权值抖动。eta与AdaGrad同量级,仍为0.01左右。
3.4 Adam-LMS:一阶矩与二阶矩联合修正
Adam在RMSProp基础上多维护一个一阶矩估计m,配合beta1、beta2两个衰减系数,并对初始时刻的矩估计做偏差校正。更新公式可以写为:
m = beta1·m + (1−beta1)·grad v = beta2·v + (1−beta2)·grad.² m_hat = m / (1−beta1^t) v_hat = v / (1−beta2^t) w = w − lr·m_hat ./ (sqrt(v_hat)+eps)
偏差校正这一步不能省:m和v初始为0,早期几步若不做校正,估计值严重偏小,等效学习率会异常放大,导致前几个样本就把权值推飞。
function [w, e] = lms_adam(x, d, order, lr, beta1, beta2, eps) % Adam优化的LMS % lr: 基础学习率,典型0.001~0.01 % beta1: 一阶矩衰减系数,默认0.9 % beta2: 二阶矩衰减系数,默认0.999,LMS场景可调小为0.99 N = length(x); w = zeros(order, 1); m = zeros(order, 1); v = zeros(order, 1); e = zeros(N, 1); for n = order:N xn = x(n:-1:n-order+1); en = d(n) - w.' * xn; grad = -en * xn; m = beta1 * m + (1 - beta1) * grad; % 一阶矩 v = beta2 * v + (1 - beta2) * grad.^2;% 二阶矩 t = n - order + 1; % 当前迭代次数 m_hat = m / (1 - beta1^t); % 偏差校正 v_hat = v / (1 - beta2^t); w = w - lr * m_hat ./ (sqrt(v_hat) + eps); e(n) = en; end end深度学习中的默认参数beta1=0.9、beta2=0.999在LMS场景下不是最优的。原因是深度学习的训练样本数以万计,而LMS实验数据长度通常只有几千点,beta2取0.999时v的估计严重滞后于梯度变化,前几百步的分母估计失真。LMS场景下把beta2降到0.99或0.995,收敛更平稳。lr的调节范围比前两者更小,通常0.001到0.005,不要一开始就取0.1。
4. 系统辨识实测:三种优化LMS在MATLAB里的对比与参数调节
4.1 实验场景:未知FIR系统的在线辨识
把LMS用于未知FIR系统辨识是最经典的验证场景:输入信号x通过一个未知的权值向量w_true生成输出,再叠加少量噪声作为期望信号d;LMS在线估计w_hat,观察w_hat与w_true的差。这个场景的好处是“真值已知”,可以精确计算权值误差,比单纯看误差收敛更直观。
MATLAB里生成实验数据只需几句话,注意固定随机种子以保证实验可复现:
rng(42); % 固定随机种子 N = 5000; % 样本数 order = 8; % 滤波器阶数 w_true = [1, -0.5, 0.25, -0.1, 0.05, -0.02, 0.01, -0.005]; x = randn(N, 1); % 白噪声激励 d = filter(w_true, 1, x) + 0.01 * randn(N, 1); % 期望信号filter(w_true, 1, x)把w_true当作FIR系数直接滤波,生成的d与x之间存在真实的线性关系。0.01倍的randn是观测噪声,幅度控制为信号能量的1%左右,让稳态失调有一个可比的下限。
4.2 对比脚本:把四种LMS跑在同一个数据集上
写一个统一入口脚本,调用第2、3章中的四个函数,分别计算权值误差的归一化范数。
% 对比四种LMS的收敛行为 mu = 0.02; % 固定步长LMS参数 eta = 0.01; % AdaGrad与RMSProp基础学习率 rho = 0.99; lr = 0.003; % Adam学习率 [~, e_fixed] = lms_fixed(x, d, order, mu); [~, e_adag] = lms_adagrad(x, d, order, eta, 1e-8); [~, e_rms] = lms_rmsprop(x, d, order, eta, rho, 1e-8); [~, e_adam] = lms_adam(x, d, order, lr, 0.9, 0.99, 1e-8); % 误差平方的滑动平均 alpha = 0.01; mse_fixed = filter(alpha, [1, alpha-1], e_fixed.^2); mse_adag = filter(alpha, [1, alpha-1], e_adag.^2); mse_rms = filter(alpha, [1, alpha-1], e_rms.^2); mse_adam = filter(alpha, [1, alpha-1], e_adam.^2); % 用半对数坐标观察早期收敛速度 semilogy(mse_fixed); hold on; semilogy(mse_adag); semilogy(mse_rms); semilogy(mse_adam); legend('LMS', 'LMS-AdaGrad', 'LMS-RMSProp', 'LMS-Adam'); xlabel('迭代次数'); ylabel('误差平方滑动平均');filter的第二个参数写成[1, alpha-1]等价于y(n)=0.01·e²(n)+0.99·y(n−1)。为什么用semilogy而不是plot?因为LMS收敛早期误差可能跨越两三个数量级,线性坐标会把后期细节压扁在横轴附近,半对数坐标能同时看清早期速度和稳态高度。
4.2.1 脚本运行后先看什么
第一次运行不要急着调参,先看四条曲线是否分开、是否有发散。固定步长LMS若选择得当,会在前500步快速下降然后进入平稳区;AdaGrad-LMS的下降速度在初期与固定步长接近,但后半段会出现明显“变慢”的拐点,这是累积平方梯度持续增大的正常表现;RMSProp-LMS与Adam-LMS的曲线整体更平滑,其中Adam因为一阶矩的作用,早期峰值比RMSProp更小。
4.3 参数表与选择建议
不同优化器在LMS场景下的参数调节范围与适用场景总结如下,表中数值基于平稳白噪声输入、阶数在8到32之间的常见实验设置。
| 优化器 | 核心参数 | 常见取值区间 | 适用场景 |
|---|---|---|---|
| 固定步长LMS | mu | 0.005~0.05 | 输入平稳、功率已知 |
| AdaGrad-LMS | eta, eps | eta=0.01~0.1, eps=1e-8 | 稀疏激励、短实验 |
| RMSProp-LMS | eta, rho, eps | eta=0.005~0.02, rho=0.9~0.99 | 输入功率波动、非平稳 |
| Adam-LMS | lr, beta1, beta2 | lr=0.001~0.005, beta1=0.9, beta2=0.99 | 不想精细调mu、要稳定收敛 |
需要注意,这些数值不是从某份官方文档里抄来的,而是这套代码在白噪声激励下的通用经验值。把输入换成语音、心电信号或通信信号时,幅度差异可能高达几十倍,最稳妥的做法是先对输入做零均值单位方差归一化,再套用表中区间。AdaGrad与RMSProp的基础学习率eta可以比固定步长mu大一倍,因为分母项会持续压制步长;Adam的lr反而要小,因为它有动量累积,过大的lr容易在早期冲出稳定区域。
4.4 收敛曲线的四个检查点与调参方向
曲线画出来后,按下面四个顺序逐一检查,能少走很多弯路:
- 前50步是否出现尖峰脉冲。出现说明基础学习率偏大或eps偏小,先检查eps是不是取成了0,再把lr/eta缩小3倍重跑。
- AdaGrad曲线是否过早进入平台。若后半段几乎水平且误差值差于固定步长LMS,说明eta过大导致累积平方梯度快速膨胀,把eta减半再试。
- RMSProp与Adam的稳态抖动是否过大。把rho从0.99改成0.999,或把beta2从0.99改成0.995,通常可以压制稳态波动,代价是收敛速度略慢。
- 固定步长LMS的mu是否真的“调到合理”。常见误区是拿一个明显偏小的mu去做对比,然后声称自适应学习率“全面碾压”。合理做法是先粗略搜索mu,找到临界发散为止,再取临界值的四分之一到二分之一作为对比基准,这样得到的结论才有说服力。
5. 收敛异常排查与毕业设计代码组织的三个技巧
5.1 发散不一定是学习率问题:先查这四件事
实验发散时先别急着调参数,先检查数据通路。第一,输入向量x(n:-1:n-order+1)与期望d(n)对齐没有:若期望信号在时间上超前或滞后一拍,相关关系被破坏,任何优化器都会发散。第二,输入是否包含异常值或NaN:训练数据里混入Inf会让G和v瞬间爆炸,即使Adam也救不回来,用any(isnan(x))先做检查。第三,数据长度是否足够:固定步长LMS的时间常数约为1/(μλ),若N只有500点而μ取0.001,曲线根本来不及下降,容易被误判为“算法不行”。第四,对比时是否用了相同的输入顺序:四种算法必须在同一份x、d上跑,否则任何对比都没有意义。
5.2 用继承体系组织源码:更适合答辩展示的MATLAB结构
把四种算法各写成独立函数虽然能跑,但横向对比时重复代码多、参数传递啰嗦。课程设计和毕业设计答辩中,面试老师更希望看到清晰的扩展结构。常见做法是定义一个抽象基类,统一的构造方法接收x、d、order,子类各自实现更新策略:
classdef LMSFilter < handle properties w e end methods (Abstract) update(obj, xn, en) end methods function run(obj, x, d, order) N = length(x); obj.w = zeros(order, 1); obj.e = zeros(N, 1); for n = order:N xn = x(n:-1:n-order+1); en = d(n) - obj.w.' * xn; obj.update(xn, en); obj.e(n) = en; end end end end继承类只需实现update方法。比如Adam子类把m、v、beta1、beta2作为属性,update里按第3.4节公式更新。这样做的好处是新增一种优化器不用重写run方法,答辩时也可以现场演示“增加一个继承类就能跑新算法”。MATLAB继承语法在R2014b之后都是通用的,老版本也能运行。
5.3 用rng、参数表与半对数坐标保住实验可复现性
自适应滤波实验的随机性来自输入信号与噪声,不做种子控制的实验几乎不可能复现。每个脚本开头固定rng(42)是最低要求,更进一步把每次实验的mu、eta、rho、beta1、beta2与最终稳态MSE写入表格:
params = {'LMS', 'mu', mu; 'Adam', 'lr', lr; ...}; T = cell2table(params, 'VariableNames', {'Algo', 'ParamName', 'Value'}); writetable(T, 'exp_records.csv');这能让评审老师看到“参数可追溯,结果可复核”。绘图时统一用semilogy展示误差曲线,最后20%样本的平均误差作为对比指标,而不是挑一个幸运时刻的误差值。
5.4 用稳态MSE随学习率变化的U形曲线做参数验证
一个很实用的验证技巧:固定其他参数,只改变一个优化器的学习率,记录每个学习率下的稳态MSE(取后20%样本的均值),然后以学习率为横轴、稳态MSE为纵轴画图。固定步长LMS会得到一条先降后升的U形曲线,U形底部的宽度就是该优化器的稳定区间;AdaGrad因累积机制,曲线右侧的上升段更陡;Adam因方差校正,底部相对更宽更平。这张图比单次收敛曲线更有信息量,既能说明“固定步长对μ敏感”,又能说明“自适应学习率拓宽了稳定区间”,是毕业设计论文里最值得放的一类实验图。把稳态MSE的计算封装成一个函数,四种算法共用同一个评估逻辑,后续换数据、换阶数时只需改一行。
本文还有配套的精品资源,点击获取