Производные сами собой
Обучение нейросети — это поиск таких чисел-параметров, при которых ошибка меньше. Чтобы понять, куда двигать каждый параметр, нужна производная ошибки по нему. 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().