Тема 3 из 6

Производные сами собой

Обучение нейросети — это поиск таких чисел-параметров, при которых ошибка меньше. Чтобы понять, куда двигать каждый параметр, нужна производная ошибки по нему. PyTorch считает производные сам — это называется autograd.

requires_grad и backward

x = torch.tensor(3.0, requires_grad=True)
y = x ** 2 + 4 * x          # PyTorch запоминает, как получился y
y.backward()                # посчитать производные
print(x.grad)               # tensor(10.) — это 2x + 4 при x = 3
  • тензоры с requires_grad=True — те, по которым нужны производные (параметры сети);
  • backward() вызывают у одного числа (скаляра). Для тензора из многих чисел сначала берут .sum() или .mean();
  • результат лежит в поле .grad каждого параметра.

Градиент копится

backward() прибавляет новые производные к .grad. Если вызвать его дважды, градиент удвоится. Поэтому на каждом шаге обучения градиент обнуляют: w.grad.zero_() (или optimizer.zero_grad() — об этом в теме 4).

Шаг без записи

Сдвигать параметр нужно так, чтобы PyTorch не записывал этот шаг в граф вычислений:

with torch.no_grad():
    w -= lr * w.grad
w.grad.zero_()

torch.no_grad() выключает запись операций. Так же делают при проверке готовой сети — это быстрее и экономит память.

Градиентный спуск

Градиент показывает, куда ошибка растёт. Значит, параметр сдвигают в обратную сторону, на небольшую долю — шаг обучения lr (learning rate):

for _ in range(steps):
    loss = ((a * x + b - y) ** 2).mean()   # ошибка
    loss.backward()                         # производные
    with torch.no_grad():                   # шаг
        a -= lr * a.grad
        b -= lr * b.grad
    a.grad.zero_()                          # обнулить
    b.grad.zero_()

Слишком большой шаг — спуск «перепрыгивает» минимум и улетает; слишком маленький — учится годами. Ошибку берут средней (mean), а не суммарной: тогда размер шага не зависит от числа примеров.

Итог

  • requires_grad=True, backward() у скаляра, .grad;
  • градиент копится — обнуляйте его на каждом шаге;
  • шаг — внутри torch.no_grad(), против градиента;
  • MSE — ((pred - y) ** 2).mean().