Skip to the lesson
FixTheRobot Explore Robotics / Probability for Robotics / M9 Particle Filters and Monte Carlo Localization / M9-T3

Probability for Robotics · Theory · M9

M9-T3 — Resampling, Degeneracy and Impoverishment

Without resampling the weights collapse onto a few particles. With it, the particles collapse onto a few ancestors.

  • Lesson 3 of 4
  • Intermediate
  • Module 9: Particle Filters and Monte Carlo Localization
  • Theory
  • About 12 minutes to read

The particle filter has two opposite failures. Without resampling, the weights become so uneven that a handful of particles carry all the information (degeneracy). With resampling, copies of a handful of particles fill the whole set and the cloud forgets the diversity that expressed its uncertainty (impoverishment). This lesson shows both on measured runs, compares the resampling schemes and gives the practical remedies.

Learning objectives

After this lesson, you should be able to:

  • Explain weight degeneracy and why it is unavoidable without resampling.
  • Compare multinomial, stratified and systematic resampling by the variance they add.
  • Explain sample impoverishment and why it makes the cloud overconfident.
  • Apply the remedies: a resampling threshold, jitter (roughening), and a sufficient number of particles.

The intuition

The weights are products of likelihoods. After a few readings from an accurate sensor, a particle that is a little off the truth has a likelihood several times smaller at every step, so its weight shrinks geometrically. In the experiment of the left panel the effective sample size of 200 particles falls from 29 at step 1 to 1.8 at step 10: after ten readings, two particles are doing the work of 200. This is degeneracy, and it is unavoidable: it follows from the product form of the weights.

Weight degeneracy without resampling, and sample impoverishment with itLeft: the effective sample size of 200 particles tracking a robot with an accurate sensor and no resampling, on a log scale. It falls from about 29 at step 1 to about 2 by step 10 and stays near 2. Right: for a robot at rest, three bars pairs for jitter 0, 0.01 and 0.05 metres: the real error of the mean in orange (0.075, 0.057, 0.094) against the spread the cloud reports in blue (0.035, 0.057, 0.126). Without jitter the cloud claims less than half its real error; with 0.01 the two agree; with 0.05 the cloud is too spread out.061218243000.61.21.82.4steplog10 of N_effresample threshold, N/2 = 100N_eff ≈ 2 of 200 012300.0350.070.1050.14jitter added at each resampling (m)metres00.0750.0350.010.0570.0570.050.0940.126orange: real error of the meanblue: spread the cloud reports
Left: the effective sample size of 200 particles without resampling collapses to about 2 within ten steps. Right: for a robot at rest, jitter at each resampling restores the agreement between the error of the filter and the spread of its cloud.

Resampling is the cure: replace the particles by copies of the heavy ones. But copies are identical. After each resampling the set contains fewer distinct ancestors, and if the motion model adds little noise the copies stay almost identical. The right panel tests this on a robot at rest (the filter's motion model has no noise): after 40 resamplings only 3.2 of the 100 original particles are ancestors of the cloud, the cloud is a few clumps, and its spread, 3.5 cm, is less than half the real error of its mean (7.5 cm; the exact posterior has a standard deviation of 4.7 cm). The filter is overconfident. This is sample impoverishment.

Adding a small random jitter to each copy (roughening) restores diversity. With 1 cm of jitter the 100 particles are all distinct, the cloud's spread, 5.7 cm, matches its real error, 5.7 cm, near the exact 4.7 cm. With 5 cm the cloud is wider than the truth (12.6 cm): the jitter inflates it. The jitter must be small compared with the posterior width.

Resampling trades one kind of waste for another: it spends particles where the probability is, and it spends diversity. Resample only when you need to, and keep a little noise in the motion model.

Definitions

Degeneracy
The weights become so uneven that NeffN_\mathrm{eff} falls to about 1. Without resampling the variance of the weights increases with time.
Impoverishment
After repeated resampling few distinct particles remain: the cloud has the number of particles of a good filter and the diversity of a bad one.
Number of distinct ancestors
The number of initial particles that have a descendant in the current set: a direct measure of diversity.
Resampling threshold
Resample only when Neff<θNN_\mathrm{eff}<\theta N, typically θ=12\theta=\tfrac12. Fewer resamplings mean less impoverishment.
Roughening (jitter)
Add small Gaussian noise to the copies after resampling, with a standard deviation scaled to the spread of the cloud and to N−1/dN^{-1/d} for a dd-dimensional state.
Variance added by resampling
Var(copies of a particle of weight w)={Nw(1−w)multinomial≤14stratified, systematic\mathrm{Var}(\text{copies of a particle of weight } w)=\begin{cases}Nw(1-w)&\text{multinomial}\\ \le\tfrac14&\text{stratified, systematic}\end{cases}
Systematic and stratified resampling copy a particle ⌊Nw⌋ or ⌈Nw⌉ times, so the spread of the number of copies is at most that of a single Bernoulli draw.
Roughening rule of thumb
σjitter=K σcloud N−1/d\sigma_\mathrm{jitter}=K\,\sigma_\mathrm{cloud}\,N^{-1/d}
Gordon et al.: K around 0.2; the jitter is a fraction of the cloud’s own spread.

Derivation: why the weights degenerate and why copies lose diversity

Degeneracy

Without resampling the weight of particle ii after tt steps is the product of its likelihoods, wt[i]∝∏s≤tp(zs∣xs[i])w_t^{[i]}\propto\prod_{s\le t} p(z_s\mid x_s^{[i]}).

  1. log⁡wt[i]=∑s≤tlog⁡p(zs∣xs[i])+const\log w_t^{[i]}=\sum_{s\le t}\log p\bigl(z_s\mid x_s^{[i]}\bigr)+\text{const}

    The log-weight is a sum of one term per step.

  2. Var(log⁡wt[i])≈t σℓ2\mathrm{Var}\bigl(\log w^{[i]}_t\bigr)\approx t\,\sigma^2_\ell

    The terms of different particles differ randomly (each particle explores a different path), with variance σℓ2\sigma_\ell^2 per step, so the spread of the log-weights grows linearly with time.

  3. ∑wmax⁡w∼eσℓt⋅c⇒Neff→1\frac{\sum w}{\max w}\sim e^{\sigma_\ell\sqrt t\cdot c}\quad\Rightarrow\quad N_\mathrm{eff}\to1

    The ratio of the best weight to the mean grows exponentially in √t; the weights become a heavy-tailed (log-normal) set dominated by the maximum.

The sharper the sensor, the larger σℓ\sigma_\ell, and the faster the collapse: here 5 steps to lose 97% of the sample size.

Impoverishment as genetic drift

Resampling is a branching process: each particle has a random number of children. Without any added noise the number of distinct ancestors is non-increasing, and for a fixed number of particles it falls to 1 eventually, as in the Wright–Fisher model of population genetics: with NN individuals the expected time until all descend from one ancestor is of order NN generations. The only things that restore diversity are noise in the motion model and jitter.

  1. ancestors(t+1)≤ancestors(t)\text{ancestors}(t+1)\le\text{ancestors}(t)

    Copying cannot create new ancestors.

  2. σcloud2 shrinks by a factor (1−1N) per resampling\sigma_\mathrm{cloud}^2\ \text{shrinks by a factor}\ \bigl(1-\tfrac1N\bigr)\ \text{per resampling}

    The sample variance of N draws with replacement from a set is, on average, (1−1/N)(1-1/N) times that of the set (the resampling adds the noise of a bootstrap, which is a contraction without jitter).

  3. with jitter σj:σcloud2→(1−1N)σcloud2+σj2\text{with jitter }\sigma_j:\quad \sigma^2_\mathrm{cloud}\to\bigl(1-\tfrac1N\bigr)\sigma^2_\mathrm{cloud}+\sigma_j^2

    Jitter balances the contraction at a spread σjN\sigma_j\sqrt N if nothing else acted; in a filter the readings shrink the cloud as well, so the final spread is smaller. Too much jitter still leaves the cloud wider than the posterior, as the 5 cm run shows.

Degeneracy is a property of the weights, impoverishment of the copies. Resampling converts the first into the second, so use it sparingly and give the particles a reason to differ.

A worked example

Three programs.

  1. Degeneracy. 200 particles track a robot that moves 20 cm per step (5 cm of noise) with a position sensor of 10 cm noise, with no resampling. The effective sample size is 28.9, 20.5, 6.4, 1.8, 3.0 and 2.0 at steps 1, 2, 5, 10, 20 and 30. The error of the estimate stays small for a few steps and is 22 cm at step 30, since two particles cannot follow the robot.
  2. Resampling schemes. One fixed set of 100 weighted particles is resampled 4 000 times. The standard deviation of the resampled mean around the weighted mean is 0.066 for multinomial, 0.038 for stratified and 0.030 for systematic resampling: the latter two add half as much noise, or less.
  3. Impoverishment. A robot at rest is observed 40 times with a 30 cm sensor, resampling each time. With no jitter 3.2 ancestors remain of 100, the cloud's spread is 3.5 cm and the error of the mean 7.5 cm. With 1 cm jitter all 100 particles are distinct, the spread is 5.7 cm and the error 5.7 cm (the exact posterior standard deviation is 4.7 cm). With 5 cm the spread is 12.6 cm and the error 9.4 cm.
import math
from robotprob import (Rng, gaussian_pdf, normalize_weights, effective_sample_size, resample_multinomial,
                       resample_systematic, resample_stratified, mean)

# 1. degeneracy: track a robot with a sharp position sensor, never resampling
def track(seed, n=200, steps=30, scheme='none', thr=0.5, sig_m=0.05, sig_z=0.1, jitter=0.0):
    rng = Rng(seed)
    x = 0.0
    ps = [rng.gauss(0.0, 1.0) for _ in range(n)]
    ws = [1.0 / n] * n
    ids = list(range(n))                                  # the ancestor of each particle
    log = []
    for t in range(1, steps + 1):
        x += 0.2 + rng.gauss(0.0, 0.05)
        z = x + rng.gauss(0.0, sig_z)
        ps = [p + 0.2 + rng.gauss(0.0, sig_m) for p in ps]
        ws = normalize_weights([w * gaussian_pdf(z, p, sig_z) for w, p in zip(ws, ps)])
        neff = effective_sample_size(ws)
        if scheme != 'none' and (scheme == 'always' or neff < thr * n):
            idx = (resample_multinomial if scheme == 'always' else resample_systematic)(rng, ws)
            ps = [ps[i] + (rng.gauss(0.0, jitter) if jitter else 0.0) for i in idx]
            ids = [ids[i] for i in idx]
            ws = [1.0 / n] * n
        est = sum(w * p for w, p in zip(ws, ps))
        log.append((t, x, est, neff, len(set(ids))))
    return log

print('no resampling, 200 particles: step  N_eff  error')
lg = track(2)
for t, x, est, neff, u in lg:
    if t in (1, 2, 5, 10, 20, 30):
        print(' ', str(t).rjust(4), str(round(neff, 1)).rjust(7), str(round(est - x, 3)).rjust(8))

# 2. the three resampling schemes: the estimate of the mean of a fixed weighted set, repeated
rng = Rng(11)
base = [rng.gauss(0.0, 1.0) for _ in range(100)]
w0 = normalize_weights([gaussian_pdf(1.0, p, 0.7) for p in base])
exact = sum(w * p for w, p in zip(w0, base))
print('resampling a fixed set of 100 weighted particles, 4000 times: spread of the estimated mean')
for name, fn in (('multinomial', resample_multinomial), ('stratified', resample_stratified), ('systematic', resample_systematic)):
    r = Rng(5)
    est = [mean([base[i] for i in fn(r, w0)]) for _ in range(4000)]
    print(' ', name.ljust(12), 'std', round(math.sqrt(mean([(e - exact) ** 2 for e in est])), 4))

# 3. impoverishment: a robot standing still, a filter that trusts its motion model (no motion noise)
def still(seed, jitter, n=100, steps=40):
    rng = Rng(seed)
    x = 2.0
    ps = [rng.gauss(0.0, 2.0) for _ in range(n)]
    ws = [1.0 / n] * n
    ids = list(range(n))
    for t in range(steps):
        z = x + rng.gauss(0.0, 0.3)
        ws = normalize_weights([w * gaussian_pdf(z, p, 0.3) for w, p in zip(ws, ps)])
        idx = resample_systematic(rng, ws)
        ps = [ps[i] + (rng.gauss(0.0, jitter) if jitter else 0.0) for i in idx]
        ids = [ids[i] for i in idx]
        ws = [1.0 / n] * n
    mu = mean(ps)
    sd = math.sqrt(mean([(p - mu) ** 2 for p in ps]))
    return len(set(ids)), mu, sd, len(set([round(p, 9) for p in ps]))
print('a robot at rest, 100 particles resampled every step for 40 steps, 200 runs each:')
print('  jitter   ancestors left   distinct particles   rms error of the mean   mean spread of the cloud   exact posterior std')
true_sd = 0.3 / math.sqrt(40)
for jit in (0.0, 0.01, 0.05):
    R = [still(seed, jit) for seed in range(1, 201)]
    print(' ', str(jit).rjust(6), str(round(mean([r[0] for r in R]), 1)).rjust(14), str(round(mean([r[3] for r in R]), 1)).rjust(19),
          str(round(math.sqrt(mean([(r[1] - 2.0) ** 2 for r in R])), 3)).rjust(22), str(round(mean([r[2] for r in R]), 3)).rjust(24), str(round(true_sd, 3)).rjust(21))
no resampling, 200 particles: step  N_eff  error
     1    28.9   -0.043
     2    20.5    -0.01
     5     6.4    0.085
    10     1.8    0.017
    20     3.0   -0.063
    30     2.0   -0.219
resampling a fixed set of 100 weighted particles, 4000 times: spread of the estimated mean
  multinomial  std 0.0656
  stratified   std 0.0379
  systematic   std 0.0295
a robot at rest, 100 particles resampled every step for 40 steps, 200 runs each:
  jitter   ancestors left   distinct particles   rms error of the mean   mean spread of the cloud   exact posterior std
     0.0            3.2                 3.2                  0.075                    0.035                 0.047
    0.01            3.8               100.0                  0.057                    0.057                 0.047
    0.05            5.7               100.0                  0.094                    0.126                 0.047
The three resampling functions of robotprob all take the weights and return indices. The number of ancestors is tracked by carrying an id with each particle through the resampling.

In robotics

A practical resampling policy

  • Resample when Neff<N/2N_\mathrm{eff}<N/2 (not every step).
  • Use systematic or stratified resampling.
  • Keep a small noise in the motion model even when the robot is still (a floor on the process noise), or add roughening of a fraction of the cloud's spread.
  • Monitor the number of distinct particles or the cloud's spread against the error in tests.

Sharp sensors are the hard case

An accurate sensor (a lidar, a tag reader) gives a likelihood much narrower than the prior cloud, so most particles are discarded at every step and Neff collapses at once. Remedies: inflate the sensor noise in the likelihood (a modest tempering, treating the sensor as less sharp than it is, which also covers model error), use more particles, or use a better proposal that moves particles towards the reading before weighting (next lesson and the optimal proposal).

Why the number of particles must grow with the dimension

In a high-dimensional state the posterior occupies a tiny part of the prior's volume and the weights degenerate at once. This is why the plain particle filter is used for 3-D poses and not for full maps: SLAM filters factor the problem (the map is conditioned on the path, Module 10).

Reporting an estimate

Use the weighted mean and the covariance of the cloud only if it is unimodal; check the number of distinct particles and the fraction of resamplings. A cloud made of a handful of identical particles looks confident and is not.

Common misconceptions

“Resample at every step to be safe.”

Every resampling removes diversity. At every step, the multinomial scheme (0.066) injects more than twice the noise of the systematic one (0.030). Resample when Neff says it is needed.

“If Neff is high the filter is healthy.”

Neff only measures the evenness of the weights. A cloud of 100 identical copies right after resampling has Neff = 100 and no diversity.

“Jitter is a hack that biases the filter.”

It does bias the spread slightly, as the 5 cm run shows, but without it the cloud is overconfident (3.5 cm claimed for a 7.5 cm error). A small jitter is a deliberate bias–variance trade, like the motion noise it stands for.

“More particles remove the need for any of this.”

They delay it. Degeneracy and impoverishment follow from the algorithm and appear with any N; more particles only buy time and make the effect smaller.

Key takeaways

  1. Without resampling the weights degenerate: log-weights spread linearly in time, and Neff fell from 29 to 2 in ten steps with a sharp sensor.
  2. Resampling cures degeneracy but removes diversity: copies are identical, and the number of distinct ancestors only decreases.
  3. Systematic and stratified resampling add much less noise than multinomial (0.030 and 0.038 against 0.066).
  4. An impoverished cloud is overconfident: 3.5 cm claimed for a 7.5 cm error. A small jitter (1 cm) restored agreement; a large one (5 cm) over-spreads the cloud.
  5. Resample on Neff < N/2, keep noise in the motion model, use enough particles, and watch the number of distinct particles.

References and further reading

Each source is marked free or paid. Free to read does not mean free to reuse: the course resources page lists every source with its licence.

Knowledge check

Three questions. Write an answer, then reveal the explanation. Your answers stay in your browser.

Question 1

A particle filter with 1 000 particles has Neff = 40 after a sensor update. Do you resample at a threshold of N/2? What happens if you do not, and what if you resample at every step?

Explanation

Yes: 40 is below 500, so resample. If you do not, the weights will keep concentrating and Neff will fall towards 1, so the filter is effectively tracking with a handful of particles and the estimate becomes noisy and fragile. Resampling at every step avoids degeneracy, but each resampling reduces the number of distinct ancestors and adds sampling noise, so the cloud impoverishes sooner and becomes overconfident, particularly when the motion noise is small. The threshold rule gives the middle path.

Question 2

In the experiment with a robot at rest, the cloud without jitter has a spread of 3.5 cm while the real error of its mean is 7.5 cm. Explain why the spread underestimates the error, and what number to watch to detect it.

Explanation

Resampling without jitter produces copies of a few particles: 3.2 ancestors out of 100 remain, so the cloud is a few clumps whose spread understates the uncertainty (each resampling shrinks the sample variance by a factor of about 1 − 1/N, and nothing restores it). The mean of the cloud inherits the sampling error of the few ancestors, which is the 7.5 cm. The number to watch is the number of distinct particles (or ancestors): a small value, here 3 of 100, shows that the spread cannot be trusted.

Question 3

Which of multinomial, stratified and systematic resampling copies a particle of weight 0.0123 in a set of N = 100 a random number of times with a spread comparable to Nw(1 − w), and which keeps the number of copies at 1 or 2 (rounded from Nw = 1.23)?

Explanation

Multinomial resampling draws every index independently, so the number of copies of this particle is binomial with mean 1.23 and variance Nw(1 − w) ≈ 1.21: it can be 0, 1, 2, 3 or more. Stratified and systematic resampling use pointers spaced about 1/N apart, so the particle gets ⌊1.23⌋ = 1 or ⌈1.23⌉ = 2 copies (2 with probability 0.23). That is why they add less noise (0.038 and 0.030 against 0.066 in the experiment), and systematic resampling, needing a single random number, is also the cheapest.

What’s next

With the algorithm and its failure modes in hand, the last lesson of the module puts it to work for Monte Carlo localisation of a robot in a map: with a laser-like sensor, global localisation, and recovery from kidnapping by injecting random particles.

Practice this lesson

Monte Carlo Localization

Rover is placed in a known room and must find itself from range readings to four beacons. Implement the weights and the resampler, then make the filter survive a kidnapping.

Next lesson

M9-T4 — Monte Carlo Localization in Practice

Explain adaptive sample sizes, random-particle injection and the kidnapped-robot test, and read the parameters of an AMCL-style localizer.

Nothing below is required to finish this lesson.

  • Before
    M9-T2: The Particle Filter Algorithm

    Derive the sequential importance resampling filter from the Bayes filter and implement its three steps.

  • Lab
    M9-L1: Monte Carlo Localization

    Rover is placed in a known room and must find itself from range readings to four beacons. Implement the weights and the resampler, then make the filter survive a kidnapping.

Other courses on this site treat neighbouring topics from their own angle: