The bug it fixes
L2 regularisation and weight decay are the same thing for plain SGD, and they are not the same thing for Adam.
L2 adds the penalty to the loss, so the decay term enters as part of the gradient.
In Adam that gradient is then divided by the running root mean square of past gradients, which divides the decay term too.
A parameter with large historical gradients gets a large denominator, so its decay is scaled down. A parameter with tiny gradients is decayed hard.
The amount of regularisation a weight receives therefore depends on its gradient history, which is not what anybody asking for weight decay meant.
Decoupling it
AdamW applies the decay to the parameters directly, outside the adaptive rescaling.
- : the bias-corrected first moment, the mean of recent gradients.
- : the bias-corrected second moment, the mean of their squares.
- : the decay rate, now meaning the same thing for every parameter.
Every weight is now pulled towards zero by the same proportion per step, which is what weight decay is supposed to be.
One step, in order
- Compute the gradient of the batch.
- Update the first and second moments as in Adam.
- Apply bias correction to both.
- Take the adaptive step from those moments.
- Separately, multiply the parameters by .
Steps four and five are independent, and keeping them independent is the entire contribution of the method.
What it changed in practice
Decoupling made the decay rate tunable again. Under Adam with L2, changing the learning rate silently changed the effective regularisation, so the two could not be tuned separately.
AdamW is now the default optimiser for transformers, and every current large language model is trained with it.
What is usually excluded from the decay
Biases, and the gain and shift parameters of normalisation layers, are normally left out of the decay entirely.
They are few, they are not a capacity risk, and shrinking a normalisation gain towards zero attacks the layer's ability to scale rather than its ability to overfit.
Most frameworks do not do this by default, so it is a parameter group that has to be written by hand, and it is worth a few tenths of a point.