Concept units

A moving average of the weights (EMA)

Intermediate

Read these first if they are new to you

The idea in one paragraph

Each training step uses a small random batch, so the weights jitter from step to step around the values they are heading for. An exponential moving average (EMA) keeps a second copy of the weights that follows the trained weights slowly. After each step, it moves a small fraction of the way toward them. The jitter mostly cancels out in this copy. Training continues on the ordinary weights; the averaged copy is the one you evaluate and keep.

The averaged copy moves a small fraction of the way each step

Call a weight after step t wt, and its averaged copy et. Pick a number β (the Greek letter beta) a little below 1, such as 0.999. After every training step, update the copy:

et = β × et−1 + (1 − β) × wt

The copy keeps a share β of its old value and takes a share 1 − β from the new weight. A model has millions of weights; each one gets its own copy, updated by the same rule.

Worked example: four steps with β = 0.9

One weight takes the values 10, 12, 8 and 11 after four steps. The copy starts equal to the first value, e = 10.

  1. The weight becomes 12: e = 0.9 × 10 + 0.1 × 12 = 9 + 1.2 = 10.2.
  2. The weight becomes 8: e = 0.9 × 10.2 + 0.1 × 8 = 9.18 + 0.8 = 9.98.
  3. The weight becomes 11: e = 0.9 × 9.98 + 0.1 × 11 = 8.982 + 1.1 = 10.082.

The weight swung between 8 and 12. The copy stayed between 9.98 and 10.2.

Your turn. The next weight is 9. What is the new copy? (Answer: 0.9 × 10.082 + 0.1 × 9 = 9.9738.)

Old weights fade by a factor β each step

Unroll the rule and the copy is a weighted average of all past weights. The newest weight counts 1 − β. The one before counts (1 − β) × β, the one before that (1 − β) × β², and so on. With β = 0.9 the shares are 0.1, 0.09, 0.081, … . The shares shrink by the same factor at each step back; that is what "exponential" means here (see exponentials).

Two numbers describe how far back the average reaches:

  • It covers roughly the last 1 / (1 − β) steps: 10 steps for 0.9, 1,000 steps for 0.999.
  • A weight's share halves every ln 2 / (−ln β) steps, about 0.69 / (1 − β) when β is close to 1. For 0.999 that is about 693 steps.

A larger β smooths more but lags behind

The figure shows one weight over 1,500 steps of a made-up training run. The dashed line is where the weight is heading: it climbs at first, then levels off. The grey line is the trained weight, jittering around the dashed line. The orange line is the averaged copy.

Predict first. Is the largest β always the best? Then move the slider to find the β that keeps the orange line closest to the dashed line over the whole run. The readout gives the average distance; make it as small as you can.

One weight, its averaged copy, and where it is heading
trained weight averaged copy (EMA) where it is heading (dashed)

Average distance from the dashed line.

What to notice. A small β follows the jitter. A large β is smooth but trails behind while the weight is still climbing. At 0.999 it has not caught up by the end. The best β balances the two.

In this short run, the best β is near 0.95. Real training runs last hundreds of thousands of steps, and their weights drift slowly compared with their jitter. So values such as 0.999 or 0.9999 are common there.

In code, the copy is a second model

This PyTorch snippet trains a model with one weight and one bias (a linear layer) on minibatches of 4. After each step it updates the averaged copy. Then it compares how much each one jitters over the last 200 steps.

ema.py

import copy
import torch

torch.manual_seed(0)                          # set the seed of PyTorch's random numbers
x = torch.randn(1000, 1)                      # 1,000 random inputs, one per row
y = 3 * x + 0.5 * torch.randn(1000, 1)       # the best slope is close to 3

model = torch.nn.Linear(1, 1)                 # one weight and one bias
ema = copy.deepcopy(model)                    # the averaged copy; never trained
opt = torch.optim.SGD(model.parameters(), lr=0.1)  # gradient descent on the weights
beta, raw, avg = 0.95, [], []

for step in range(400):
    i = torch.randint(0, 1000, (4,))          # a minibatch of 4 examples
    loss = ((model(x[i]) - y[i]) ** 2).mean()
    opt.zero_grad(); loss.backward(); opt.step()  # zero the gradients, compute them, step
    with torch.no_grad():                     # the EMA update, for every weight
        for pe, p in zip(ema.parameters(), model.parameters()):
            pe.mul_(beta).add_(p, alpha=1 - beta)
    # Add each copy's weight to the end of its list, as a plain number from .item().
    raw.append(model.weight.item()); avg.append(ema.weight.item())

r, a = torch.tensor(raw[200:]), torch.tensor(avg[200:])   # after both settle
print(f"raw weight: mean {r.mean():.3f}, spread {r.std():.3f}")
print(f"EMA weight: mean {a.mean():.3f}, spread {a.std():.3f}")
raw weight: mean 3.005, spread 0.092
EMA weight: mean 3.002, spread 0.037

Both copies center on the same value, but the averaged copy jitters less than half as much. pe.mul_(beta).add_(p, alpha=1 - beta) is the update rule above, applied in place. Inside torch.no_grad(), PyTorch records no steps for automatic gradients, which lets the code change the averaged weights in place.

This is a simplification

The figure shows one weight with made-up noise. In a real model, weights move together on a curved loss, so averaging them does not always give a lower loss, though it often helps, especially for generative models. Two details are left out. A copy that starts from the random initial weights takes about 1 / (1 − β) steps to forget them, so libraries often start with a smaller β and raise it. And some layers keep running statistics besides their weights; those need copying too.

Check yourself

  1. With β = 0.8, the copy is 5.0 and the new weight is 6.0. What is the new copy?

    Answer

    0.8 × 5.0 + 0.2 × 6.0 = 4.0 + 1.2 = 5.2.

  2. Roughly how many recent steps does an EMA with β = 0.9999 average over?

    Answer

    About 1 / (1 − 0.9999) = 10,000 steps.

  3. A run lasts only 2,000 steps. Why is β = 0.9999 a poor choice for it?

    Answer

    That copy reaches back about 10,000 steps, five times longer than the run. At the end it still holds much of the early, untrained weights, so it lags far behind the trained weights.

Go further

Optional background, for readers who want more depth.

Where this shows up

Units that build on this one