"In a previous life I computed my gain from a covariance and a Riccati equation, and I could prove I was optimal. Then they fed me a million sequences, replaced my proof with a sigmoid, and told me to learn the gain myself. I no longer know my own variance, but I can finally track a turn I was never told about."
A Hidden State That Used to Be a Kalman Filter
An LSTM or GRU is a Kalman filter that learned its own gain. The Kalman filter carries a state forward, predicts where it should go, and then corrects toward the new measurement by an amount, the gain, computed analytically from an assumed model. A gated recurrent cell carries a cell state forward, and its gates decide, as a learned nonlinear function of the data, exactly how much of that state to keep and how much of the new input to admit. The forget gate is a learned state-propagation coefficient; the input gate is a learned Kalman gain; the cell-state carry is the state propagation. This section completes the temporal thread that Section 7.6 opened. There we showed the Kalman mean update is a linear recurrent cell. Here we close the loop: we align the Kalman predict-update equations with the LSTM and GRU gate equations term by term, name precisely what the learned cell gains (arbitrary nonlinear dynamics) and what it loses (the closed-form calibrated covariance), point to where deep state-space models reconcile the two, and then prove the claim with code, training a GRU on data from a known linear-Gaussian system and watching it rediscover the Kalman estimate. You leave able to read any gated cell as a learned filter and to say exactly which guarantee you traded for which flexibility.
In Section 10.2 and Section 10.3 we built the LSTM and the GRU as engineering answers to the vanishing-gradient problem of Section 9.4: gates that let gradients flow and let the cell choose what to remember. That is the right way to derive them. It is not the only way to understand them. This section offers a second reading, the one the temporal thread of Chapter 1 has been promising since Part II: a gated RNN is the learned descendant of the Kalman filter, and its gates are the place where the filter's hand-computed gain became a trainable function. The closing bridge of Part II, Section 7.6, established the first half of this claim by mapping the Kalman mean update onto a linear RNN. The piece it deferred, and that this section delivers, is the role of the gate: why a constant input weight is not enough, and how the LSTM and GRU restore the data-dependent gain that made the Kalman filter adaptive. We keep the unified notation of Appendix A: $\mathbf{x}_t$ the input, $\mathbf{h}_t$ the hidden state, $\mathbf{c}_t$ the cell state, $\hat{\mathbf{y}}_t$ the prediction.
This reading earns its place because it changes what you do at the desk. Seen as an opaque bundle of gates, an LSTM is something you tune by trial. Seen as a learned Kalman filter, it is something you reason about: you know its cell state is a running estimate, you know its forget gate is choosing a memory horizon, you know its input gate is setting a trust level on each measurement, and you know the one thing it does not give you for free is a calibrated error bar, because that is exactly the covariance the Kalman recursion carried and the plain RNN dropped. Every design choice in the rest of Part III (when to add a probabilistic head in Chapter 19, when to reach for the linear-recurrence efficiency of Chapter 13) is clearer once you hold this correspondence in mind.
The four concrete competencies this bridge installs: to restate the Kalman predict-update recurrence and read it as a data-dependent gain on a carried state; to align that recurrence term by term with the LSTM and GRU gate equations and identify which gate plays the gain; to state precisely the gain-and-loss ledger of replacing the analytic filter with a learned cell, calibrated uncertainty being the central casualty; and to implement both a Kalman update and a gated cell on one tracking problem and show the learned cell approximating the optimal estimate from data. These close the arc that runs from Section 7.6 through this chapter.
1. Recall: The Kalman Filter Is a Recurrence With a Data-Dependent Gain Beginner
Before we can align the Kalman filter with a gated cell we need its recurrence in front of us, in the same notation we use for RNNs. Section 7.6 wrote the Kalman mean update as a single linear recurrence; we restate it here and, crucially, foreground the one feature that the linear-RNN reading of Part II had to set aside: the gain is recomputed every step from the filter's own uncertainty, so the effective input weight is not constant but data-dependent.
The Kalman filter tracks a latent state through two moves per step. The predict step propagates the previous posterior mean $\hat{\mathbf{m}}_{t-1}$ through the transition matrix $\mathbf{F}$ and inflates the covariance by the process noise; the update (correct) step nudges the predicted mean toward the new measurement $\mathbf{x}_t$ in proportion to the Kalman gain $\mathbf{K}_t$:
$$\underbrace{\hat{\mathbf{m}}_t^{-} = \mathbf{F}\,\hat{\mathbf{m}}_{t-1}, \quad \mathbf{P}_t^{-} = \mathbf{F}\mathbf{P}_{t-1}\mathbf{F}^{\top} + \mathbf{Q}}_{\text{predict}}, \qquad \underbrace{\hat{\mathbf{m}}_t = \hat{\mathbf{m}}_t^{-} + \mathbf{K}_t\big(\mathbf{x}_t - \mathbf{H}\hat{\mathbf{m}}_t^{-}\big), \quad \mathbf{K}_t = \mathbf{P}_t^{-}\mathbf{H}^{\top}\big(\mathbf{H}\mathbf{P}_t^{-}\mathbf{H}^{\top} + \mathbf{R}\big)^{-1}}_{\text{update}}.$$The quantity $\mathbf{x}_t - \mathbf{H}\hat{\mathbf{m}}_t^{-}$ is the innovation, the surprise: the gap between what the filter expected to measure and what it actually measured. The gain $\mathbf{K}_t$ sets how much of that surprise is admitted into the state. As Section 7.6 showed, collecting terms turns the mean update into one linear recurrence,
$$\hat{\mathbf{m}}_t = \big(\mathbf{I} - \mathbf{K}_t\mathbf{H}\big)\mathbf{F}\,\hat{\mathbf{m}}_{t-1} + \mathbf{K}_t\,\mathbf{x}_t, \qquad \hat{\mathbf{y}}_t = \mathbf{H}\,\hat{\mathbf{m}}_t,$$which is exactly the shape $\mathbf{h}_t = \mathbf{W}_t\mathbf{h}_{t-1} + \mathbf{U}_t\mathbf{x}_t$ of a recurrent cell, with recurrent weight $\mathbf{W}_t = (\mathbf{I} - \mathbf{K}_t\mathbf{H})\mathbf{F}$ and input weight $\mathbf{U}_t = \mathbf{K}_t$. The subscript $t$ on those weights is the whole story of this section. In a vanilla RNN the weights are constant; in the Kalman filter they breathe with the gain, which itself breathes with the covariance $\mathbf{P}_t^{-}$ (the Riccati recursion is the covariance update $\mathbf{P}_t^{-} = \mathbf{F}\mathbf{P}_{t-1}\mathbf{F}^{\top}+\mathbf{Q}$ that propagates uncertainty, from Section 7.6). When the filter is uncertain the gain is large and the new measurement is trusted; when the filter is confident the gain shrinks toward zero and the state coasts on its prediction. That single adaptive coefficient is what a vanilla RNN cannot represent and what a gate restores.
The Kalman gain $\mathbf{K}_t$ is a dial between two extremes. Push it to its maximum, $\mathbf{K}_t\mathbf{H} = \mathbf{I}$, and the update becomes $\hat{\mathbf{m}}_t = \mathbf{x}_t$: the filter throws away its memory and believes the measurement completely. Push it to zero and the update becomes $\hat{\mathbf{m}}_t = \mathbf{F}\hat{\mathbf{m}}_{t-1}$: the filter ignores the measurement and coasts on its model. Every useful filter lives between these, and the right setting depends on context, how noisy this measurement is, how uncertain the state currently is. The Kalman filter computes that setting analytically from the covariance. A gated RNN learns it: the input gate is precisely a trust dial whose position is a learned nonlinear function of the current state and input. Hold "gain equals trust dial" in mind and the gate equations of the next subsection read as filters.
2. The Explicit Mapping: Gates Are Learned Kalman Gains Intermediate
We now place the Kalman update and the gated-cell update side by side and read off the correspondence. The claim is not loose analogy but a structural alignment: the same three jobs, propagate the state, decide how much to trust the measurement, blend, are done by both, with the only difference being whether the trust coefficient is computed from a model or learned as a nonlinear function of the data. Recall the LSTM cell of Section 10.2, written for the cell state $\mathbf{c}_t$ and hidden state $\mathbf{h}_t$:
$$\mathbf{f}_t = \sigma(\mathbf{W}_f[\mathbf{h}_{t-1}, \mathbf{x}_t]), \quad \mathbf{i}_t = \sigma(\mathbf{W}_i[\mathbf{h}_{t-1}, \mathbf{x}_t]), \quad \tilde{\mathbf{c}}_t = \tanh(\mathbf{W}_c[\mathbf{h}_{t-1}, \mathbf{x}_t]),$$ $$\mathbf{c}_t = \mathbf{f}_t \odot \mathbf{c}_{t-1} + \mathbf{i}_t \odot \tilde{\mathbf{c}}_t, \qquad \mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{c}_t).$$Read the cell-state line $\mathbf{c}_t = \mathbf{f}_t \odot \mathbf{c}_{t-1} + \mathbf{i}_t \odot \tilde{\mathbf{c}}_t$ next to the rearranged Kalman update $\hat{\mathbf{m}}_t = (\mathbf{I} - \mathbf{K}_t\mathbf{H})\mathbf{F}\,\hat{\mathbf{m}}_{t-1} + \mathbf{K}_t\mathbf{x}_t$. Both are a state carried forward by a multiplicative coefficient plus a new contribution admitted by another coefficient. The forget gate $\mathbf{f}_t$ sits where $(\mathbf{I} - \mathbf{K}_t\mathbf{H})\mathbf{F}$ sits: it is the learned state-propagation factor, deciding how much of the old estimate survives. The input gate $\mathbf{i}_t$ sits where the gain $\mathbf{K}_t$ sits: it is the learned trust on the new information $\tilde{\mathbf{c}}_t$, which plays the role of the innovation-carrying input $\mathbf{x}_t$. The decisive difference is that $\mathbf{K}_t$ is computed from the Riccati covariance recursion under an assumed linear-Gaussian model, while $\mathbf{f}_t$ and $\mathbf{i}_t$ are sigmoids of a learned affine function of the data, free to implement any trust schedule the training distribution rewards. Figure 10.5.1 aligns the two updates term by term.
| Job in the recurrence | Kalman filter (model given) | LSTM cell (learned) | GRU cell (learned) |
|---|---|---|---|
| carried state | posterior mean $\hat{\mathbf{m}}_{t-1}$ (plus covariance $\mathbf{P}_{t-1}$) | cell state $\mathbf{c}_{t-1}$ | hidden state $\mathbf{h}_{t-1}$ |
| state propagation | $(\mathbf{I} - \mathbf{K}_t\mathbf{H})\mathbf{F}$, model-derived | forget gate $\mathbf{f}_t = \sigma(\cdot)$, learned | $(1 - \mathbf{z}_t)$, learned |
| trust on new info (the gain) | Kalman gain $\mathbf{K}_t$, from covariance | input gate $\mathbf{i}_t = \sigma(\cdot)$, learned | update gate $\mathbf{z}_t = \sigma(\cdot)$, learned |
| new information | innovation $\mathbf{x}_t - \mathbf{H}\hat{\mathbf{m}}_t^{-}$ | candidate $\tilde{\mathbf{c}}_t = \tanh(\cdot)$ | candidate $\tilde{\mathbf{h}}_t = \tanh(\cdot)$ |
| blend | $\hat{\mathbf{m}}_t = (\mathbf{I}-\mathbf{K}_t\mathbf{H})\mathbf{F}\hat{\mathbf{m}}_{t-1} + \mathbf{K}_t\mathbf{x}_t$ | $\mathbf{c}_t = \mathbf{f}_t\odot\mathbf{c}_{t-1} + \mathbf{i}_t\odot\tilde{\mathbf{c}}_t$ | $\mathbf{h}_t = (1-\mathbf{z}_t)\odot\mathbf{h}_{t-1} + \mathbf{z}_t\odot\tilde{\mathbf{h}}_t$ |
| how the gain is set | analytic Riccati recursion on $\mathbf{P}_t$ | sigmoid of learned affine map | sigmoid of learned affine map |
The GRU of Section 10.3 makes the correspondence even tighter because it uses a single coupled gate. Its update is $\mathbf{h}_t = (1 - \mathbf{z}_t) \odot \mathbf{h}_{t-1} + \mathbf{z}_t \odot \tilde{\mathbf{h}}_t$, where $\mathbf{z}_t = \sigma(\mathbf{W}_z[\mathbf{h}_{t-1}, \mathbf{x}_t])$ is the update gate. This is a convex blend governed by one coefficient $\mathbf{z}_t$, exactly the form of the scalar Kalman steady-state update $\hat{m}_t = (1-K)\hat{m}_{t-1} + K x_t$ that Section 7.6 showed equals an exponential smoother with $\alpha = K$. The GRU update gate $\mathbf{z}_t$ is a learned, per-step, per-dimension, data-dependent Kalman gain. Where the scalar Kalman filter pinned $K$ to a single value by the ratio of process to measurement variance, and the exponential smoother fixed $\alpha$ by hand, the GRU lets that blend coefficient vary with the input: high $\mathbf{z}_t$ right after a detected change (trust the new data, the filter's high-gain regime), low $\mathbf{z}_t$ during a stable stretch (coast on memory, the filter's low-gain regime).
The reason a vanilla RNN is strictly weaker than a Kalman filter at filtering is that its input weight $\mathbf{U}$ is a constant matrix, so it must commit to one trust level on incoming measurements for all time. But the optimal trust genuinely depends on context: a measurement arriving when the state is uncertain deserves more weight than the same measurement arriving when the state is settled. The Kalman gain $\mathbf{K}_t$ encodes this through the covariance, which is why it carries a time subscript. A gate restores exactly that missing degree of freedom: $\mathbf{i}_t$ and $\mathbf{z}_t$ are time-varying, data-dependent coefficients in $[0,1]$, recomputed every step from the current state and input. The gate is not an arbitrary nonlinearity bolted on for capacity; it is the structural element that lets a recurrence behave like a filter with an adaptive gain. The LSTM and GRU are the vanilla RNN given back the one thing the Kalman filter had that it lacked.
Take the scalar local-level model of Section 7.6 with steady-state Kalman gain $K = 0.3$, so the optimal update is $\hat{m}_t = 0.7\,\hat{m}_{t-1} + 0.3\,x_t$. A GRU dimension can reproduce this exactly if it learns to hold its update gate at $\mathbf{z}_t = 0.3$ and to set its candidate to the (linearized) input, since then $h_t = (1 - 0.3)h_{t-1} + 0.3\,\tilde{h}_t = 0.7\,h_{t-1} + 0.3\,x_t$. Concretely, if $h_{t-1} = 10.0$ and a measurement $x_t = 20.0$ arrives with $\tilde{h}_t \approx 20.0$, the GRU returns $0.7(10) + 0.3(20) = 13.0$, the identical number the Kalman filter and the exponential smoother return. Now suppose a regime change makes recent measurements far more informative; a trained GRU can raise $\mathbf{z}_t$ toward $0.8$ for a few steps, giving $0.2(10) + 0.8(20) = 18.0$, snapping toward the new level the way a Kalman filter with an inflated covariance would. The fixed-gain filter cannot do this; the learned gate can, because $\mathbf{z}_t$ is a function of the data, not a constant. The number to remember: at a constant gate value the GRU is the steady-state Kalman filter; the gate's freedom to vary is the strictly added capability.
3. What Is Gained and What Is Lost Advanced
The mapping cuts both ways, and a clear-eyed practitioner needs the full ledger. Replacing the analytic Kalman gain with a learned gate buys a large gain in flexibility and pays for it with a real loss in guarantees. Section 7.6 drew this ledger for the linear RNN; the gated cell shifts the balance, because gating recovers some of what the linear cell lost (the adaptive gain) while still forfeiting the rest (the calibrated covariance). This subsection states the trade precisely.
What is gained is nonlinear, model-free dynamics. The Kalman filter is provably optimal, in the minimum-mean-squared-error sense, but only on a contract: the dynamics must be linear, the noise Gaussian, and the matrices $\mathbf{F}$, $\mathbf{H}$, $\mathbf{Q}$, $\mathbf{R}$ correct. Honor the contract and the filter is the best possible estimator. Break it, say the true dynamics turn nonlinearly or switch regime, and the filter is confidently wrong, lagging the truth while reporting a tight covariance. The gated RNN assumes almost none of this. Its forget and input gates are arbitrary nonlinear functions of the state and input, so it can learn trust schedules and propagation factors no fixed linear filter can express: it can slam its gain to near one at a detected change, hold a value for hundreds of steps through a near-saturated forget gate, or implement a different effective filter in different regions of state space. For the messy, nonstationary, non-Gaussian series of the real world, that flexibility is decisive, and it is why the field reaches for an LSTM rather than an extended Kalman filter when the dynamics are unknown.
What is lost is the closed-form, calibrated uncertainty. The Kalman filter carries a covariance $\mathbf{P}_t$ alongside its mean, and that covariance is a genuine, calibrated quantification of how much to trust the estimate, delivered for free by the same recursion that produces the mean. A plain gated RNN carries only the cell state, a point estimate with no native sense of its own error. The error bars that fell out of the Kalman recursion must now be bolted back on. This is not a permanent loss but a deferred one: Chapter 19 on probabilistic forecasting teaches recurrent models to emit full predictive distributions and to be conformally calibrated, recovering honest uncertainty inside a learned model. The thing to carry forward from this subsection is that the missing covariance is the single most important casualty of learning the filter, and that the book buys it back deliberately rather than pretending it was never lost. Figure 10.5.2 lays the ledger out.
| Property | Kalman filter (model given) | Gated RNN, LSTM / GRU (model learned) |
|---|---|---|
| dynamics | fixed, linear; you supply $\mathbf{F},\mathbf{H},\mathbf{Q},\mathbf{R}$ | learned, nonlinear; fit from data |
| the gain | analytic $\mathbf{K}_t$ from the covariance recursion | learned gates $\mathbf{i}_t,\mathbf{f}_t,\mathbf{z}_t$, data-dependent |
| optimality | provably MMSE if the model is correct | only as good as the data and the fit |
| uncertainty | calibrated covariance $\mathbf{P}_t$, for free | none natively; add a probabilistic head (Chapter 19) |
| regime change | lags; covariance does not warn you | can learn to raise the gain and snap over |
| failure mode | confidently wrong when assumptions break | silently wrong off the training distribution |
There is a quiet poetry in the name "forget gate". The Kalman filter never forgets on purpose; its memory decays only as fast as the model's transition and the incoming gain dictate, and it would carry a confidently-wrong estimate forever if you let it. The LSTM was given an explicit, learnable knob for forgetting, and it turns out that the single most important thing for it to learn is often to not forget, to hold its forget gate near one so the cell state coasts unchanged across long gaps, which is exactly a Kalman filter with near-zero process noise and near-zero gain idling between rare informative measurements. The gate that sounds like a weakness is the mechanism that lets the learned filter choose its own memory horizon, something the fixed filter could only get by retuning $\mathbf{Q}$ by hand.
4. The Modern Reconciliation: Deep State-Space Models Advanced
The trade of subsection three looks like a clean either-or: hand-built linear filter with optimality and free uncertainty, or learned nonlinear RNN with flexibility and no closed form. The most active line of recent sequence-modeling research refuses the choice, and it does so by going back to the very recurrence this section started from. The reconciliation matters here because it is the natural endpoint of the temporal thread: the Kalman filter, having become the RNN, becomes learnable and efficient at once.
The nonlinearity that bought the RNN its flexibility also cost it something the Kalman filter quietly had: because the Kalman mean update is linear, it can be unrolled and computed in parallel across time, whereas a $\tanh$-gated RNN is strictly sequential, step $t$ cannot start until step $t-1$ finishes. Deep state-space models reclaim the linear recurrence while keeping it learnable. S4 (Gu, Goel, and RĂ©, 2022) and Mamba (Gu and Dao, 2023) parameterize a linear state-space recurrence $\mathbf{h}_t = \mathbf{A}\mathbf{h}_{t-1} + \mathbf{B}\mathbf{x}_t$, $\mathbf{y}_t = \mathbf{C}\mathbf{h}_t$, that is exactly the Kalman state-propagation skeleton, but with $\mathbf{A}$, $\mathbf{B}$, $\mathbf{C}$ learned from data rather than derived from a physical model. Because the recurrence is linear, it trains as a long convolution (parallel, like attention) and runs at inference as a recurrence (cheap, like a filter), recovering the parallelism the nonlinear RNN forfeited. Mamba's central innovation, input-dependent $\mathbf{A}$ and $\mathbf{B}$ matrices, is the selectivity that lets the linear recurrence decide what to keep, which is the Kalman gain idea once more: a data-dependent coefficient on the state, now made parallelizable. These models, the subject of Chapter 13, are quite literally the Kalman recurrence of Section 7.6 made learnable and made fast.
This section sits at a junction the whole book is built around. The carry-then-correct recurrence first appeared as the Kalman filter of Section 7.6: a state propagated by a model and corrected by an analytic gain, with optimality and a free covariance, but rigid and linear. It returns in this chapter as the gated RNN: the same recurrence with the gain replaced by a learned gate, gaining nonlinear flexibility but losing the closed-form uncertainty. It returns a third time, in Chapter 13, as the deep state-space model (S4, Mamba): the linear recurrence made learnable and parallel, reclaiming the efficiency the nonlinear RNN gave up while keeping the data-driven gain. The lost uncertainty is the through-line repaid in Chapter 19. One loop, three lives: hand-built and optimal, learned and flexible, learned and fast. Recognizing which life a new architecture is living is worth more than memorizing any one of them.
5. Worked Example: A GRU Learns to Be a Kalman Filter Advanced
We now make the central claim executable: a gated recurrent cell, trained only on noisy measurements from a known linear-Gaussian system, learns to approximate the optimal Kalman estimate, because the optimal estimate is exactly what minimizing prediction error on that data rewards. The plan has three parts. First, implement a scalar Kalman filter from scratch in NumPy and run it on a simulated local-level system to get the optimal (oracle) estimate. Second, implement a minimal GRU cell from scratch and confirm its update is the gated blend of subsection two. Third, train a PyTorch GRU on the same measurements to predict the latent state, and compare its output against the Kalman estimate. Code 10.5.1 builds the data and the from-scratch Kalman oracle.
import numpy as np
rng = np.random.default_rng(0)
T = 400
F, H, Q, R = 1.0, 1.0, 0.01, 0.25 # local-level model: slow drift, noisy sensor
# Simulate the hidden state (random walk) and noisy measurements.
state = np.zeros(T)
for t in range(1, T):
state[t] = F * state[t-1] + rng.normal(0, np.sqrt(Q)) # latent random walk
meas = state + rng.normal(0, np.sqrt(R), size=T) # what we actually observe
def kalman_filter(meas, F, H, Q, R):
"""Scalar Kalman filter: the OPTIMAL estimate for this linear-Gaussian model."""
m, P = 0.0, 1.0 # initial mean, covariance
est = np.zeros_like(meas)
for t, x in enumerate(meas):
m_pred = F * m # predict mean
P_pred = F * P * F + Q # predict covariance
K = P_pred * H / (H * P_pred * H + R) # Kalman gain (data-dependent)
m = m_pred + K * (x - H * m_pred) # correct toward measurement
P = (1 - K * H) * P_pred # correct covariance
est[t] = m
return est, K # K has converged by the end
kf_est, K_ss = kalman_filter(meas, F, H, Q, R)
kf_mse = np.mean((kf_est - state) ** 2)
print("steady-state Kalman gain K = %.4f" % K_ss)
print("Kalman MSE vs true state = %.5f" % kf_mse)
print("raw measurement MSE = %.5f" % np.mean((meas - state) ** 2))
steady-state Kalman gain K = 0.1817
Kalman MSE vs true state = 0.04300
raw measurement MSE = 0.24668
Now the from-scratch gated cell, to confirm its update is the gated blend we aligned with the Kalman filter in subsection two. Code 10.5.2 implements a minimal scalar GRU forward pass by hand, with the update gate $z$ playing the Kalman gain.
def gru_step(h, x, params):
"""One scalar GRU step. The update gate z IS the learned Kalman gain:
h_t = (1 - z) * h_{t-1} + z * h_tilde, a data-dependent convex blend."""
Wz, Uz, Wr, Ur, Wh, Uh = params
z = 1 / (1 + np.exp(-(Wz * h + Uz * x))) # update gate (the gain)
r = 1 / (1 + np.exp(-(Wr * h + Ur * x))) # reset gate
h_tilde = np.tanh(Wh * (r * h) + Uh * x) # candidate (the new info)
return (1 - z) * h + z * h_tilde # blend: carry vs admit
# Untrained, hand-set params just to exercise the forward pass and its blend.
demo = (0.0, 1.0, 0.0, 1.0, 0.0, 1.0)
h = 0.0
for x in meas[:5]:
h = gru_step(h, x, demo)
print("from-scratch GRU ran; example state after 5 steps: %.4f" % h)
from-scratch GRU ran; example state after 5 steps: 0.3142
Finally the library pair, which is where the claim is proven. Code 10.5.3 builds a PyTorch GRU, trains it to predict the latent state from the noisy measurements alone, and compares the trained network's estimate against the Kalman oracle. The from-scratch Kalman filter plus hand GRU step above ran roughly 35 lines of explicit recursion and gate arithmetic; the PyTorch version expresses the entire learned cell, its unrolled recurrence, and its backpropagation-through-time training in about a dozen, with nn.GRU and autograd handling the gates, the carry, and the gradients internally.
import torch
import torch.nn as nn
class GRUFilter(nn.Module): # a GRU that learns to filter
def __init__(self, hidden=16):
super().__init__()
self.gru = nn.GRU(input_size=1, hidden_size=hidden, batch_first=True)
self.readout = nn.Linear(hidden, 1) # g: state -> state estimate
def forward(self, x):
h, _ = self.gru(x) # the gated recurrence, unrolled
return self.readout(h).squeeze(-1)
x = torch.tensor(meas, dtype=torch.float32).reshape(1, -1, 1) # (batch, time, 1)
y = torch.tensor(state, dtype=torch.float32).reshape(1, -1) # target: TRUE state
model = GRUFilter()
opt = torch.optim.Adam(model.parameters(), lr=0.01)
for epoch in range(400): # train on noisy meas -> true state
opt.zero_grad()
loss = ((model(x) - y) ** 2).mean() # BPTT runs inside .backward()
loss.backward(); opt.step()
gru_est = model(x).detach().numpy().ravel()
gru_mse = np.mean((gru_est - state) ** 2)
print("GRU MSE vs true state = %.5f" % gru_mse)
print("Kalman MSE vs true state = %.5f" % kf_mse)
print("GRU-vs-Kalman agreement = %.5f (mean abs diff of the two estimates)"
% np.mean(np.abs(gru_est - kf_est)))
nn.GRU plus a linear readout, with .backward() running backpropagation through time over the unrolled gated recurrence. The GRU is never told the model $F, H, Q, R$; it learns the optimal trust from data.GRU MSE vs true state = 0.04461
Kalman MSE vs true state = 0.04300
GRU-vs-Kalman agreement = 0.01935 (mean abs diff of the two estimates)
Step back and read what the three blocks establish together. Code 10.5.1 computed the provably optimal estimate from the known model. Code 10.5.2 showed the GRU's update is the same gated carry-and-admit blend, by hand. Code 10.5.3 trained that gated cell on noisy data with no knowledge of the model and watched it converge to within a few percent of the optimal filter. The pedagogical payoff is the bridge made concrete: a GRU is not merely like a Kalman filter, it learns to be one when the data come from a system the Kalman filter is optimal for, and it keeps going where the Kalman filter cannot, into the nonlinear and nonstationary regimes that no fixed gain can follow.
Who: A battery-management team at an electric-vehicle maker estimating state-of-charge from current, voltage, and temperature streams, the sensor/IoT telemetry thread of Chapter 8 applied to an automotive pack.
Situation: Their production estimator was an extended Kalman filter built on an equivalent-circuit battery model, fusing noisy sensor readings into a smoothed state-of-charge with a calibrated covariance feeding the range display.
Problem: The circuit model's parameters drift with cell age and temperature, and the dynamics turn sharply nonlinear near the empty and full extremes; the extended Kalman filter lagged and biased exactly where accuracy mattered most, at low charge where a wrong estimate strands a driver.
Dilemma: Three paths. Keep recalibrating the circuit model per cell chemistry and age, accurate only until the next drift. Replace it with a GRU that learns the nonlinear aging dynamics directly from fleet data, but lose the calibrated covariance the range display depended on. Or a hybrid that learns the dynamics while restoring uncertainty.
Decision: They kept the recurrence skeleton and swapped the update: a GRU learned the nonlinear, age-dependent state-of-charge dynamics from logged fleet sequences, and a probabilistic output head (the technique of Chapter 19) restored a calibrated interval so the range logic carried over unchanged.
How: The GRU's update gate played the role the Kalman gain had, learning to trust voltage more during the informative steep parts of the discharge curve and less on the flat plateau, a data-dependent gain schedule no fixed extended-Kalman gain expressed; the calibrated head supplied the covariance the bare GRU dropped.
Result: The learned filter reduced state-of-charge error in the nonlinear low-charge region while the calibrated head kept the displayed-range confidence interval honest, the combination the analytic filter and the bare GRU each could not deliver alone.
Lesson: The gain you trade away by learning is the analytic one; the uncertainty you trade away is the covariance, and you can buy the uncertainty back deliberately. The gate is where the filter became flexible, and the probabilistic head is where it became honest again.
The from-scratch Kalman filter of Code 10.5.1 plus the hand GRU step of Code 10.5.2 ran about 35 lines of explicit recursion. A framework collapses each side. The learned filter is three lines: nn.GRU implements the gated carry-and-correct recurrence, the unrolling, the gate weights, and (with autograd) the backpropagation-through-time training that Code 10.5.3 used. The classical filter has a matching shortcut: filterpy and statsmodels implement the full Kalman recursion, covariance and steady-state gain included.
import torch.nn as nn
from filterpy.kalman import KalmanFilter
learned = nn.GRU(input_size=1, hidden_size=16, batch_first=True) # the learned filter
kf = KalmanFilter(dim_x=1, dim_z=1) # the analytic filter, covariance and all
kf.F, kf.H, kf.Q, kf.R = [[1.]], [[1.]], [[0.01]], [[0.25]]
kf.predict(); kf.update(z=meas[0]) # one predict-update cycle; kf.x is the mean, kf.P the covariance
nn.GRU is the learned, gated filter with no closed-form covariance; filterpy.KalmanFilter is the analytic filter that hands back the calibrated covariance kf.P. Choosing between them is choosing the column of Figure 10.5.2 your problem lives in.The bridge this section draws is an active research program, not a settled analogy. KalmanNet (Revach et al., 2022) and its successors learn only the Kalman gain with a small RNN while keeping the analytic predict-update structure, getting the robustness of learning with the interpretability of the filter, and recent 2024 to 2026 work extends this hybrid "model-based deep learning" line to non-Gaussian and partially known dynamics. The selective state-space models Mamba (Gu and Dao, 2023) and Mamba-2 (Dao and Gu, 2024) push the other direction, making the linear Kalman-style recurrence fully learnable and parallel, with input-dependent transition matrices that are the Kalman gain idea reborn as selectivity, the subject of Chapter 13. A third strand asks when a trained RNN provably recovers the optimal filter: results on linear-Gaussian identifiability and on the expressivity of gated cells as filters are sharpening the conditions under which the GRU of Code 10.5.3 is guaranteed to match the Kalman oracle rather than merely approach it. The practitioner's takeaway for 2026: the cleanest temporal models are increasingly explicit hybrids that keep the filter's structure and learn only its gain.
Six chapters ago, at the close of Part II, Section 7.6 made a promise: the Kalman filter is a linear recurrent cell, and the gated RNN you would meet in Part III is the same loop with a learned, data-dependent gain. This section delivered the rest of that promise. We restated the Kalman predict-update recurrence, aligned it term by term with the LSTM and GRU gates, identified the input and update gates as learned Kalman gains and the cell-state carry as the state propagation, drew the exact ledger of what learning gains (nonlinear dynamics) and loses (the calibrated covariance), and then trained a GRU to rediscover the optimal filter from data alone. The hidden state that opened this section's epigraph as a filter, and ends it as a network, is the same hidden state $\mathbf{h}_t$ that ran through every filter of Chapter 7 and every cell of Chapter 10. The wardrobe changed; the loop did not. Chapter 13 dresses it once more, as a model that is learnable and fast at once, and Chapter 19 hands back the covariance this section watched it lose.
Exercises
- Exercise 10.5.1 (Conceptual). The GRU update is $\mathbf{h}_t = (1 - \mathbf{z}_t)\odot\mathbf{h}_{t-1} + \mathbf{z}_t\odot\tilde{\mathbf{h}}_t$ and the scalar steady-state Kalman update is $\hat{m}_t = (1 - K)\hat{m}_{t-1} + K x_t$. State precisely which GRU quantity plays the Kalman gain $K$, and explain in two or three sentences why a vanilla RNN, with a constant input weight $\mathbf{U}$, cannot reproduce the data-dependent behavior of $K$, while the GRU can. Then describe one concrete situation (a regime change, a noisy outlier) where the constant-weight cell would behave visibly worse than the gated cell.
- Exercise 10.5.2 (Implementation). Extend Code 10.5.3 to extract and plot the GRU's learned behavior as a filter. After training, feed the network a step input (a measurement series that jumps from 0 to 10 at the midpoint) and plot its state estimate alongside the Kalman filter's estimate on the same jump. Then compute the GRU's effective per-step gain by measuring how much of each new measurement it admits, and compare it to the steady-state Kalman gain $K \approx 0.18$ (0.1817 in Output 10.5.1). Does the trained GRU raise its effective gain immediately after the jump the way subsection two predicts a good learned filter should?
- Exercise 10.5.3 (Open-ended). Code 10.5.3 trained on data from a linear-Gaussian system, where the Kalman filter is optimal and the GRU can at best tie it. Design and run an experiment on a system where the GRU should beat the Kalman filter: simulate a nonlinear or regime-switching latent process (for example a level that switches between two random-walk variances, or a state that evolves through a $\tanh$ nonlinearity), run the same linear Kalman filter and the same GRU on it, and report both MSEs. Explain the result in terms of the gain-and-loss ledger of Figure 10.5.2, and discuss what a probabilistic output head (Chapter 19) would add that neither bare estimator provides.