DEV Community

Cover image for Tuning an LMS Adaptive Filter: The Trade-off Between Speed and Steady-State Error
NovaSolver
NovaSolver

Posted on Originally published at novasolver.jp

Tuning an LMS Adaptive Filter: The Trade-off Between Speed and Steady-State Error

Why adaptive filtering shows up everywhere

Any system that has to track something changing — an echo path in a phone call, a jamming signal in a receiver, a drifting sensor bias — needs a filter that updates itself instead of a fixed set of coefficients computed once and left alone. The Least Mean Squares (LMS) algorithm is the workhorse for this. It's not the most sophisticated adaptive algorithm (RLS converges faster, Kalman filtering handles nonstationarity more rigorously), but it's cheap, numerically robust, and it's what you reach for first because it requires no matrix inversion and barely any memory.

The catch is that LMS has exactly one primary tuning knob — the step size μ — and that knob controls two things that fight each other: how fast the filter converges, and how much residual error it settles into once it has converged. Get μ wrong in either direction and the filter is useless: too small and it never catches up to a moving target, too large and it oscillates around the solution instead of sitting on it, or diverges outright.

The update rule

The LMS algorithm updates its tap weights on every new sample using the instantaneous gradient of the squared error, not the true gradient (which would require knowing the underlying statistics). That's the "least mean squares" trick — it's a stochastic approximation, and the noise in that approximation is exactly what produces the steady-state error floor.

e(n)   = d(n) - w(n)^T x(n)          instant error e(n)
w(n+1) = w(n) + μ · e(n) · x(n)      tap update
Enter fullscreen mode Exit fullscreen mode

Here x(n) is the input vector (length equal to the tap count), d(n) is the desired signal, and w(n) is the current weight vector. The step size μ sits directly in front of the update — double it and every correction doubles.

Two conditions bound where μ can live. For stability, it has to stay below a value set by the input power and the tap count:

0 < μ < 2 / (tap_count × input_power)
Enter fullscreen mode Exit fullscreen mode

Above that ceiling the weight vector diverges. Below it, μ trades off two competing effects. A larger μ inside the stable range converges faster (fewer iterations to reach steady state) but leaves a larger residual, because each update overreacts to whatever instantaneous noise happens to sit in e(n) on a given sample. A smaller μ averages out that noise better, giving lower steady-state MSE, but takes longer to get there — and if the plant you're tracking is drifting, "longer to get there" can mean the filter never actually catches up.

That's the misadjustment trade-off in one sentence: excess MSE (the gap between the achievable minimum and what you actually get) grows roughly with μ, tap count, and input power, while convergence time shrinks roughly as 1/μ. A commonly used rule of thumb approximates the misadjustment as M ≈ μ × tap_count × input_power / 2, which is a useful sanity check even though it only holds well away from the stability boundary — near the boundary the real behavior gets noticeably worse than this linear approximation suggests.

It's also worth being explicit about what tap count itself is doing here, separate from μ. More taps let the filter model a longer impulse response — useful if the plant you're identifying genuinely has a long memory, like a reverberant room. But every extra tap adds another dimension the filter has to estimate from the same noisy gradient, which is exactly why the stability bound and the misadjustment both scale with tap count directly. Over-provisioning taps "just in case" quietly makes the whole filter noisier and slower to converge for no benefit if the true plant doesn't need them.

A worked example: tracking a slowly drifting plant

Say you're identifying an unknown system — a room's acoustic echo path, modeled as an 8-tap FIR filter — using white noise input with unit power. Observation noise sits around -30 dB relative to signal, and the plant itself drifts slowly (its true coefficients shift a little every few thousand samples, which is realistic for a system whose physical characteristics change with temperature or geometry).

With tap count = 8 and input power ≈ 1, the stability bound is μ < 2/8 = 0.25. That's the theoretical ceiling; in practice you stay well under it.

Try μ = 0.02 first. Over the first few hundred iterations n, the instant error e(n) shrinks from near unity down toward the noise floor, and MSE [dB] drops steeply — a fast initial descent, typically visible as a fairly clean slope on a log scale over the first 300–500 samples. Once it flattens out, the steady-state MSE with this μ settles somewhere around -25 dB given the -30 dB observation floor and the misadjustment penalty from the step size itself.

Now drop μ to 0.002, ten times smaller. Convergence stretches out to several thousand iterations before MSE flattens — plausible if your plant drift happens on a similar timescale, in which case the filter is chasing a moving target it never fully catches, and instant error e(n) never fully settles because the plant has already moved again by the time the filter tracks it. But the steady-state MSE floor it eventually approaches is lower, closer to -30 dB, because the smaller step size averages out gradient noise more effectively.

Push μ up to 0.3, above the 0.25 stability bound for this tap count and input power, and the picture changes entirely: e(n) stops shrinking and instead grows without bound over iterations, and MSE [dB] climbs instead of falling. That's divergence, not just poor tuning.

The practical takeaway: pick μ based on how fast the plant drift actually is, not by chasing the lowest possible steady-state MSE in isolation. A filter with excellent steady-state performance that can't track a drifting plant is worse than a slightly noisier filter that keeps up.

Where engineers get the step size wrong

The most common mistake is tuning μ against a static test signal and then deploying against a nonstationary one. If your bench test uses a plant that never changes, you'll be tempted to shrink μ until MSE looks great — and then the deployed system, tracking a plant with real drift, lags behind badly because you tuned away all its tracking headroom.

The second common mistake is forgetting that the stability bound scales with tap count. Adding more taps to capture a longer impulse response (say, a longer acoustic echo tail) shrinks the safe range for μ proportionally. A μ that was comfortably stable at 8 taps can be dangerously close to the boundary at 32 taps if you don't rescale it.

Third, input power isn't always what you assume it is. If your input signal's power changes over time (voice signals, for instance, have wildly varying short-term power), a μ chosen for the average power can push the filter unstable during loud segments. Normalized LMS variants address this by dividing the update by an estimate of instantaneous input power, but plain LMS doesn't protect you automatically.

A fourth, subtler issue is observation noise interacting with plant drift in ways that aren't obvious from either parameter alone. High observation noise pushes you toward a smaller μ to keep steady-state MSE reasonable, but a fast-drifting plant pushes you the opposite direction, toward a larger μ so the filter doesn't fall permanently behind. When both are present at once, there may simply be no single μ that satisfies both constraints well, and the honest conclusion is that plain LMS is the wrong tool — you need a variable step-size variant, or a completely different structure such as a Kalman filter that can model the drift explicitly instead of just reacting to it after the fact.

Try it yourself

The interplay between step size, tap count, input power, observation noise, and plant drift is easier to feel than to compute by hand — small changes in μ produce nonlinear changes in both convergence time and steady-state MSE, and the two failure modes (too slow to track, too noisy to be useful) look completely different on the error curve.

You can sweep all five parameters and watch e(n) and MSE [dB] evolve over iteration n in real time with the Adaptive Filter LMS Simulator. It's a good way to build intuition for exactly where your own system's stability boundary sits before committing to a step size in production code.

Top comments (0)