Stable adaptive IIR system identification¶
Tutorial goal
Identify a synthetic recursive system while keeping the learned denominator stable.
Note
New to the terminology? See the lattice DSP concept map and the causality/data-use guide for how online, offline, block, and MIMO examples should be read.
Context¶
This is a two-signal system-identification example, not one-step
self-prediction. A known stable target system receives the reference
input x[n] and generates the desired signal d[n]. The adaptive
lattice-ladder model receives the same reference input and learns a
stable recursive approximation to the target input-output map.
Key idea and equations¶
The data relationship is
The instantaneous error is
The adaptive model updates numerator/ladder parameters and reflection
coefficients after forming the current output and error. The reflection
update is bounded so the learned denominator remains stable during
training. This is causal adaptive filtering because \widehat d[n]
uses current/past reference samples and previous filter state, not future
desired samples.
Causality and data use¶
This is inductive/streaming system identification on a synthetic sequence: the target generates d[n] from the same reference x[n] seen by the adaptive filter. It is not a single-signal predictor, and it is not a production echo canceller.
How to read the result¶
Compare the initial and final MSE. Also inspect the learned reflection coefficients and pole radius if printed.
Run command¶
python examples/adaptive_iir_system_identification.py
Source code¶
1"""Adaptive stable IIR system identification.
2
3This example identifies both numerator taps and reflection coefficients. The
4reflection updates are performed through unconstrained raw variables that are
5mapped with tanh, so the learned denominator remains stable during adaptation.
6"""
7
8from __future__ import annotations
9
10import numpy as np
11
12from lattice_dsp import AdaptiveLatticeLadderNLMS, LatticeIIR
13
14rng = np.random.default_rng(42)
15x = rng.normal(size=6000)
16
17true_reflection = [0.45, -0.25]
18true_numerator = [0.4, -0.1, 0.7]
19target = LatticeIIR(true_reflection, true_numerator)
20desired = np.asarray(target.process(x), dtype=float)
21
22adaptive = AdaptiveLatticeLadderNLMS(
23 initial_reflection=[0.0, 0.0],
24 initial_taps=[0.0, 0.0, 0.0],
25 mu_taps=0.08,
26 mu_reflection=0.002,
27 margin=1e-4,
28)
29errors = np.asarray(adaptive.adapt_block(x.tolist(), desired.tolist()), dtype=float)
30
31print("target reflection:", true_reflection)
32print("learned reflection:", np.round(adaptive.reflection, 4).tolist())
33print("target numerator:", true_numerator)
34print("learned numerator:", np.round(adaptive.numerator, 4).tolist())
35print("initial MSE:", float(np.mean(errors[:500] ** 2)))
36print("final MSE:", float(np.mean(errors[-500:] ** 2)))