AI Hints
Home›Deep Learning Tutorial›Optimisers and Learning Rates
Deep Learning · Lesson 14 of 92

Optimisers

SGD, momentum, and Adam.

⏱ 2 min read▶ Video at 29:32🎓 Free
Watch this lesson in the Deep Learning courseWatch on YouTube ↗Full Deep Learning course

Backpropagation tells each weight which way is downhill. But how big a step should it take? Should it remember past steps? The optimiser answers these questions, and a good one can make the difference between a model that trains in an hour, and one that never trains at all.

Optimisers
From the video: Optimisers
  • Gradients say which way
  • How big a step?
  • The optimiser decides

Rolling downhill

Plain gradient descent is like walking downhill with no memory, one careful step at a time. In a long narrow valley, it zigzags from wall to wall, wasting time. Momentum is like a heavy ball. It builds up speed in directions that stay consistent, and it smooths out the zigzags. And Adam gives every single weight its own step size, adapting as it goes. That makes it a great default choice.

Rolling downhill
From the video: Rolling downhill

The optimiser contest

Let's have a contest, on the fashion images, with two passes through the data. Plain S G D, with momentum, Adam, and Adam W, which handles weight decay more correctly. Only one line changes. Run it. Plain S G D falls behind, in this short race. Momentum helps a lot. Adam and Adam W are at the top. With much longer training and careful tuning, S G D can sometimes catch up, but Adam W is what most people start with.

optimisers.pypython
import torch
from torch import nn
from torchvision import datasets, transforms

root = "data"
tf = transforms.ToTensor()
train = datasets.FashionMNIST(root, train=True, transform=tf)
test = datasets.FashionMNIST(root, train=False, transform=tf)
X_test = test.data.float().unsqueeze(1) / 255

def run(make_opt, epochs=2, seed=0, net=None, scheduler=None):
    torch.manual_seed(seed)
    model = net() if net else nn.Sequential(nn.Flatten(), nn.Linear(784, 256),
                                            nn.ReLU(), nn.Linear(256, 10))
    opt = make_opt(model.parameters())
    dl = torch.utils.data.DataLoader(train, batch_size=128, shuffle=True)
    for _ in range(epochs):
        model.train()
        for x, y in dl:
            loss = nn.functional.cross_entropy(model(x), y)
            opt.zero_grad(); loss.backward(); opt.step()
    model.eval()
    with torch.no_grad():
        return (model(X_test).argmax(1) == test.targets).float().mean().item()

from torch.optim import SGD, Adam, AdamW

for name, make in [("SGD", lambda p: SGD(p, lr=0.01)),
                   ("Momentum", lambda p: SGD(p, lr=0.01, momentum=0.9)),
                   ("Adam", lambda p: Adam(p, lr=0.001)),
                   ("AdamW", lambda p: AdamW(p, lr=0.001))]:
    print(f"{name:<9} {run(make):.3f}")
Output
SGD       0.738
Momentum  0.829
Adam      0.851
AdamW     0.850

Summary

S G D is the simple walker, momentum is the heavy ball, and Adam adapts for every weight. Use Adam W as your default. But one setting matters more than any other. Get it wrong, and nothing works at all.

Key takeaways
  • Momentum builds speed
  • Adam adapts per weight
  • AdamW is a strong default
Next up: The Learning Rate — the most important dial