Модуль: PyTorch. Теория: как обучается нейросеть


Цикл обучения и оптимизатор


Войдите на сайт, чтобы ответы сохранялись и баллы за тетрадь засчитывались.

Как вычисленная ошибка превращается в обучение

К этому моменту мы умеем получить предсказание, сравнить его с правильным ответом и найти градиенты. Однако после всех этих действий сеть остаётся прежней. Обучение начнётся только тогда, когда изменятся параметры. Правило их изменения реализует оптимизатор. Он получает параметры модели, смотрит на вычисленные для них градиенты и выполняет следующий шаг.

Начнём с простого правила градиентного спуска: новый параметр = старый параметр - lr · градиент. Число lr, или learning rate, задаёт размер шага. Знак минус нужен потому, что градиент направлен в сторону роста ошибки, а мы хотим двигаться в противоположную сторону. Можно представить поверхность ошибки как склон: производные сообщают местное направление подъёма, а размер шага определяет, насколько далеко мы уйдём вниз. Эта аналогия имеет предел: слишком большой шаг может перескочить удобную область и даже увеличить ошибку.

Вернёмся к знакомому примеру: x = 2, y = 1, вес 0.2 и смещение 0.1. Для ошибки с множителем 0.5 мы получили градиенты -1 и -0.5. При lr = 0.1 новый вес станет 0.2 - 0.1 · (-1) = 0.3, а смещение — 0.1 - 0.1 · (-0.5) = 0.15. Предсказание увеличится до 0.75 и приблизится к единице. Новая ошибка составит 0.5 · (0.75 - 1)² = 0.03125, то есть окажется меньше прежних 0.125.

В PyTorch это правило можно применить с помощью torch.optim.SGD. В следующем примере мы явно зададим исходные параметры, чтобы числа совпали с ручным расчётом. Затем очистим градиенты, выполним прямой и обратный проходы и вызовем optimizer.step(). Именно последняя команда изменит вес и смещение.



Вывод показывает переход от предсказания 0.5 к 0.75. Обратите внимание на два отдельных вычисления ошибки — до обновления и после него. Переменная loss хранит результат уже выполненного вычисления; она не пересчитывается сама, когда оптимизатор изменил параметры. Чтобы узнать качество новой версии модели, нужно снова вызвать модель и функцию ошибки. Этот простой факт особенно важен при построении графиков: иначе легко подписать прежнее значение как результат нового шага.

Также стоит заметить, что оптимизатору переданы model.parameters(), а не входные данные. Во время обучения меняется модель, тогда как правильные ответы служат ориентиром. Команда zero_grad() убирает прежнее накопление, backward() рассчитывает новые производные, а step() использует их. Если пропустить первый вызов, вклады разных шагов начнут складываться. Если пропустить последний, можно многократно вычислять ошибку, так и не научив сеть ничему новому.

Почему обучение повторяется много раз

Один шаг лишь немного изменяет параметры. После него зависимость ошибки от весов уже может быть другой, поэтому градиенты следует вычислить заново. Повторение этой последовательности и образует цикл обучения. Полный проход по обучающему набору называют эпохой. Пока в нашем наборе только один объект и мы используем его целиком за один шаг, одна эпоха совпадает с одним обновлением. Позже, когда появятся небольшие порции данных, внутри эпохи будет несколько обновлений.

Для следующего примера возьмём готовую функцию nn.MSELoss() — среднюю квадратичную ошибку. По умолчанию она усредняет квадраты отклонений по всем элементам и не добавляет множитель 0.5. Для одного и того же предсказания её значение и градиенты вдвое больше, чем у формулы, использованной выше. Поэтому при сравнении двух экспериментов важно сравнивать не только название «квадратичная ошибка», но и точную формулу. Здесь мы выберем небольшой шаг 0.01 и повторим обучение сто раз.



В конце ошибка вычислена заново для окончательных весов. Она становится очень маленькой, потому что модель подстроилась под единственный пример. Однако из этого не следует, что она восстановила какое-то единственно правильное правило. Равенство 2w + b = 1 выполняется для множества пар веса и смещения. Один обучающий пример ограничивает поведение модели лишь в одной точке. Чтобы судить о предсказаниях на других входах, нужны дополнительные данные и отдельная проверка.

Порядок в нашем шаблоне легко объяснить словами: убрать старые производные, получить текущий ответ, оценить ошибку, вычислить её производные, изменить параметры. Очистку можно выполнить и после прямого прохода, если она происходит до очередного backward(). Мы ставим её в начало, чтобы каждый шаг было проще читать. Саму модель и оптимизатор создаём до цикла: создание новой модели внутри каждой эпохи снова задавало бы начальные веса и стирало достигнутый результат.

Уменьшение ошибки не обязано быть строго равномерным на каждом шаге, особенно когда данные подаются порциями. Размер шага, начальные веса и устройство модели влияют на ход обучения. Поэтому мы не только запускаем цикл, но и наблюдаем за ним. В следующей главе расширим пример до нескольких объектов и разберём, как организовать их подачу.

Проверим понимание

Попробуйте объяснить назначение каждой команды цикла без названий функций. Если вы можете рассказать, что должно произойти с ответом, градиентами и весами, порядок вызовов становится понятным.


В обычном шаге SGD без накопления градиентов: