Q and R

The two numbers you actually choose, what they mean, and how to measure them.

U6.4State estimationUniversity30 min

Do this lesson in the simulator

Two numbers, and they are the whole of tuning a Kalman filter.

R: how noisy the measurement is

R is the variance of the measurement noise, in squared units of whatever you are measuring. It is measurable, and you have already measured it: U4.1 was exactly this.

sigma = 5 cm  ->  R = 25

Measure it, do not guess it. Stand the robot still, take a hundred readings, take the variance. If the sensor's noise changes with range or with surface, R can change per reading, and a filter that adjusts R when the measurement is known to be worse is doing something genuinely useful.

A hundred distance readings from a robot standing still, scattered about their mean, with a band one sigma either side024681040506070time, secondsdistance(), cmmean 59.5sigma 5.38R = sigma²= 29.0
The robot never moves, so every reading should be the same. They are not: they scatter about the mean, 66 of the 100 inside the band one sigma either side. The sigma, 5.38 cm, squared is R, the variance of the measurement, the number the filter needs.

Q: how wrong the model can be

Q is the process noise: how much the state can change between ticks in ways your prediction does not know about. Slip, a bump, a gust, an unmodelled acceleration.

It is not directly measurable, and it is the number people fiddle with. Some ways to think about it:

  • If the model were perfect, Q would be zero, and the filter would eventually stop listening to measurements entirely. That is why Q = 0 is always wrong on a real machine.
  • Roughly, Q is the square of how far the state can drift in one tick without you knowing. A robot that can slip 0.5 cm in a tick: Q about 0.25.
  • Only the ratio Q/R matters for the gain. Doubling both changes nothing.
from bugbot import *
connect()

readings = []
for i in range(100):
    readings.append(distance())
    wait(0.1)
mean = sum(readings) / len(readings)
R = sum((v - mean) ** 2 for v in readings) / len(readings)
print("measured R:", round(R, 1), "(sigma", round(R ** 0.5, 2), "cm)")
print("mean reading:", round(mean, 1))

for Q in (0.01, 1.0, 100.0):
    est, p = readings[0], 100.0
    for v in readings:
        p += Q
        k = p / (p + R)
        est += k * (v - est)
        p = (1 - k) * p
    print("Q", Q, "-> gain", round(k, 3), "estimate", round(est, 1))

Run this in the simulator

The sensor was set up with 5 cm of noise, and a hundred readings measure R as 29.0, a sigma of 5.38 cm: close, and a hundred readings is enough to find it. The gains come out at 0.019, 0.169 and 0.81. With the robot standing still the true reading never changes, so the small Q is right here: its estimate ends at 59.7, beside the mean of 59.5, while Q = 100 ends at 65.6, wherever the last few readings happened to fall.

The filter's estimate over the same hundred readings, for a small, a middling and a large Q4075Q = 0.01gain 0.019ends 59.74075Q = 1gain 0.169ends 61.64075Q = 100gain 0.810ends 65.60246810time, seconds (dots: the readings, cm)
The same readings each time. With Q = 0.01 the filter believes almost nothing changes, and its line is nearly flat. With Q = 100 it takes 81 percent of every reading, so it copies the noise and has gained nothing. Here the robot is still, so the flat line is the right one.

The two failure modes

Q too small, or R too large. The filter trusts its model and ignores measurements. It is smooth, confident and wrong, and it stays wrong: the estimate wanders off and the filter reports a small variance the whole time. This is called filter divergence and it is the dangerous failure, because the filter's own statement of its uncertainty is the thing that is broken.

Q too large, or R too small. The filter trusts every measurement. The output is as noisy as the raw sensor and you have gained nothing but complexity.

Between them: the estimate should look smoother than the measurement and should still track the truth when the truth moves.

Checking a filter honestly

The test is the innovation, again. The filter predicts that measured - est should have variance p + R. So:

normalised = (measured - est) ** 2 / (p + R)

should average about 1. Much less than 1 means the filter is claiming more uncertainty than it has, and could be tuned tighter. Much more than 1 means it is claiming less uncertainty than it has, which is the dangerous direction. This ratio has a name, the normalised innovation squared, and printing its running average is the single most useful diagnostic a filter can carry.

Task: tune Q and R

Standing still, measure R and print it as measured r:. Then run the filter at two or three values of Q and print what each does.

from bugbot import *
connect()

readings = []

Challenges

  1. Compute the normalised innovation squared for your filter and check it is near 1.
  2. Set Q = 0 and drive the robot. Watch the estimate stop listening.
  3. Make R depend on range: double it beyond 100 cm. Does the estimate improve?