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


Backward на двухслойной сети


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

Обратный проход: проследим путь ошибки через сеть

В предыдущей главе предсказание зависело всего от одного веса и одного смещения. Настоящая сеть содержит больше связей, но принцип остаётся тем же: нужно выяснить, как каждый параметр повлиял на общую ошибку. Возьмём настолько маленькую сеть, чтобы все вычисления можно было проследить вручную. На входе будут два числа, в скрытом слое — два нейрона, а на выходе — одно предсказание. Скрытым слой называют потому, что его значения являются промежуточными: мы не задаём для каждого из них отдельный правильный ответ.

Оба скрытых нейрона сначала вычислят взвешенные суммы входов. Затем применим функцию ReLU, которая сохраняет положительные числа, а отрицательные заменяет нулём: ReLU(z) = max(0, z). Так в сети появляется нелинейность. Без неё последовательность линейных слоёв можно было бы свести к одному линейному преобразованию, а значит, простое добавление слоёв не давало бы ожидаемой гибкости.

Вход нашей сети равен x = [2, 1]. Запишем первый слой вручную как z = x @ W1 + b1. Здесь строки W1 относятся к входам, а столбцы — к скрытым нейронам. Первый столбец содержит веса 0.5 и 0, смещение первого нейрона равно -0.5. Поэтому его сумма равна 2 · 0.5 + 1 · 0 - 0.5 = 0.5. У второго нейрона веса -0.5 и 0.2, а смещение нулевое; получаем 2 · (-0.5) + 1 · 0.2 = -0.8.

После ReLU значения [0.5, -0.8] превращаются в [0.5, 0]. Выходной слой умножает их соответственно на 1 и 0.5 и прибавляет нулевое смещение. Предсказание равно 0.5. Правильный ответ мы выбрали равным 1, поэтому ошибка 0.5 · (pred - y)² равна 0.125. В коде стоит также mean(), но здесь всего один объект и один выход, поэтому усреднять фактически нечего.



Полученные значения совпадают с расчётом. Число -0.8 может отображаться с небольшим хвостом после запятой: многие десятичные дроби представляются в двоичной памяти приближённо. Это обычная особенность вычислений с вещественными числами. Теперь, зная весь прямой проход, можно двигаться обратно — от ошибки к выходному слою, а затем к скрытому.

Что каждый слой сообщает предыдущему

Производная ошибки по предсказанию равна pred - y = -0.5. Она говорит, что небольшое увеличение предсказания сейчас уменьшило бы ошибку. Предсказание имеет вид pred = a1 · v1 + a2 · v2 + b2, где a1 = 0.5 и a2 = 0 — выходы скрытых нейронов, а v1 = 1 и v2 = 0.5 — веса выходного слоя. По цепному правилу градиент первого выходного веса равен -0.5 · 0.5 = -0.25. У второго веса он нулевой, поскольку его вход a2 равен нулю. Для выходного смещения производная равна -0.5.

Но обратный проход должен сообщить и то, как ошибка зависит от самих скрытых значений. По a1 производная равна -0.5 · v1 = -0.5, по a2 — -0.5 · v2 = -0.25. Обратите внимание: по второму скрытому значению производная пока не нулевая. Нулевой вклад в параметры второго скрытого нейрона появится на следующем участке пути, когда мы пройдём через ReLU.

На положительном входе ReLU просто возвращает вход, поэтому её производная равна 1. На отрицательном входе результат остаётся нулевым при небольших изменениях, поэтому производная равна 0. Первый нейрон получает сигнал -0.5 · 1 = -0.5, а второй — -0.25 · 0 = 0. В самой точке нуля обычная производная ReLU не определена; для обратного прохода PyTorch использует там значение 0. В нашем примере оба входа отличаются от нуля, поэтому этот особый случай на расчёт не влияет.

Осталось пройти через взвешенные суммы. Производная по весу — это пришедший к нейрону сигнал, умноженный на соответствующий вход. Поэтому у веса W1[0, 0] получаем -0.5 · 2 = -1, а у W1[1, 0] — -0.5 · 1 = -0.5. Смещение первого нейрона получает производную -0.5. Во втором столбце W1 и у второго смещения производные нулевые: на этом объекте ReLU перекрыл обратный путь. Теперь сравним весь расчёт с autograd.



Для W1 программа выводит строки [-1, 0] и [-0.5, 0], для b1 — [-0.5, 0]. Градиенты W2 равны [[-0.25], [0]], а b2 — [-0.5]. Так одна ошибка на выходе превратилась в разные указания для разных параметров. В большой сети происходит тот же процесс, только путей гораздо больше. Если величина влияет на ошибку по нескольким путям, их вклады складываются.

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

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

Мысленно поменяйте второй вход ReLU с отрицательного на положительный. Какой участок обратного пути перестанет обнулять производную?


Почему градиент по W2[1,0] равен нулю в этом примере?