PythiloLesson 16 of 30 · progress kept in this browser

Writing an optimizer

An optimizer keeps param_groups, lists of parameters with their settings such as lr, and state, a dictionary of buffers for each parameter. step() reads each gradient and changes the parameter in place, under torch.no_grad() so the update itself is not recorded.

import torch

class PlainSGD(torch.optim.Optimizer):
    def __init__(self, params, lr):
        super().__init__(params, {"lr": lr})

    @torch.no_grad()
    def step(self):
        for g in self.param_groups:
            for p in g["params"]:
                if p.grad is not None:
                    p -= g["lr"] * p.grad

w = torch.tensor([3.0], requires_grad=True)
opt = PlainSGD([w], lr=0.1)
for _ in range(50):
    opt.zero_grad()
    (w ** 2).sum().backward()
    opt.step()
print(w)

Momentum keeps a velocity per parameter: v = momentum * v + grad, then p -= lr * v. Steps that agree build up speed, steps that alternate cancel out. The velocity must live in self.state[p], which survives from one step to the next and is saved with the optimizer's state_dict.

Your task

Write Momentum(params, lr, momentum=0.9), a torch.optim.Optimizer subclass whose step() keeps each parameter's velocity in self.state[p]["velocity"] (starting from zero), updates it to momentum * v + grad and subtracts lr * v, skipping parameters without a gradient. It must match torch.optim.SGD(params, lr, momentum).

This lesson uses torch, which Python in this browser does not include. Read it here, and run and check it in the Pythilo app on iPhone or iPad.