Статья Автор: Деникина Н.В., Деникин А.В.

Слои и модели

Как собрать сеть из готовых слоёв

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

Начнём с nn.Linear. Запись nn.Linear(3, 4) создаёт слой, который получает по три признака и вычисляет по четыре выхода. Для каждого выхода нужны три веса и одно смещение. Всего получается 3 · 4 + 4 = 16 обучаемых чисел. Слой сам создаёт их при инициализации, то есть при подготовке начального состояния. Это ещё не обучение: случайно выбранные начальные параметры только задают точку, с которой оно начнётся.

Есть отличие от нашей ручной записи, которое полезно понять сразу. В nn.Linear каждая строка weight содержит веса одного выходного нейрона. Поэтому форма весов равна (out_features, in_features), в данном случае (4, 3). Для таблицы входов вычисление можно записать как X @ layer.weight.T + layer.bias. Транспонирование согласует эту организацию весов с формой входа; сама взвешенная сумма остаётся прежней.



Пять объектов с тремя признаками превращаются в пять строк с четырьмя выходами. В выводе это видно по формам: weight имеет размер (4, 3), bias — (4,), а результат — (5, 4). Количество объектов определяют данные, количество входов и выходов — устройство слоя. Поэтому тот же слой может обработать и один объект, и сразу сто объектов, если у каждого по три признака.

Когда последовательность слоёв становится моделью

Теперь объединим два линейных слоя и ReLU между ними. Первый слой преобразует два признака в четыре скрытых значения. ReLU добавит нелинейность, после чего второй слой соберёт из четырёх значений один выход. Такое устройство кратко обозначают 2 → 4 → 1. Если каждый шаг получает результат предыдущего, удобно использовать nn.Sequential: он последовательно передаёт данные указанным модулям.



На вход поданы шесть объектов, поэтому форма ответа равна (6, 1). Количество параметров равно 17: первый слой содержит 2 · 4 + 4 = 12 чисел, второй — 4 · 1 + 1 = 5. У ReLU обучаемых параметров нет, но её присутствие меняет возможности сети. Считать только веса и забывать о функции активации было бы ошибкой: устройство вычисления определяется и параметрами, и самими операциями.

Выражение model.parameters() позволяет перебрать зарегистрированные параметры модели. Оно не создаёт новую копию сети и не выполняет обучение. Когда мы передадим эти параметры оптимизатору, он получит доступ к тем самым весам и смещениям, которые участвуют в прямом проходе. А метод numel(), использованный в примере, просто возвращает количество элементов тензора. Сумма таких количеств и дала нам 17.

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

Как описать собственный порядок вычислений

Для простой цепочки достаточно Sequential. Если мы хотим дать частям сети имена или явно записать логику прохождения данных, можно создать собственный класс на основе nn.Module. В методе __init__ создаются слои, а метод forward описывает прямой проход. Вызов super().__init__() подготавливает служебные механизмы базового модуля, благодаря которым вложенные слои и их параметры учитываются в модели.



Эта сеть устроена так же, как предыдущая: два входа, четыре скрытых нейрона с ReLU и один выход. Имена hidden и out помогают понять назначение её частей. Вызов net(X) запускает описанное в forward вычисление; обычно модель вызывают именно таким образом. Для трёх объектов форма ответа равна (3, 1). Сами ответы не обязаны совпадать с ответами отдельной сети Sequential, поскольку параметры создавались заново.

Обратный проход в классе описывать не пришлось: autograd видит операции внутри forward и строит нужные связи. Это и есть полезное разделение работы. Мы определяем, как получается предсказание, а библиотека вычисляет производные этой цепочки. Теперь у нас есть модель как единый объект, и можно перейти к недостающей части обучения — изменению её параметров.

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

При подсчёте параметров мысленно разберите каждый выходной нейрон: сколько входных весов и сколько смещений ему принадлежит? Затем проверьте, сохранилась ли ось объектов в результате.


У сети 3 → 5 → 2 оба линейных слоя имеют смещения. Сколько обучаемых чисел?


Вход имеет форму (7,3). Как вручную воспроизвести layer = nn.Linear(3,5)?

Печать