Найти тему
Жонглер данными

Знакомство с pytorch не для самых маленьких

По вполне объяснимой причине входной порог этого фреймворка очень высок. На читателя сразу сваливаются математические понятия тензоров, графов решений, градиентов и т.п., что для математически подготовленного человека терпимо, а вот все остальные вынуждены бросать изучение torch просто из-за непонимания про что тут пишут. При этом обучение не менее сложного tensorflow вполне обходится без этих сложных понятий, по крайней мере, на этапе обучения. Почему бы не попробовать пойти этой же дорогой в изучении pytorch?

Если вы не знаете, что такое нейронные сети, модели, слои, оптимизаторы, функции ошибок и т.д., то не стоит замахиваться на этот фреймворк, потому что он чрезвычайно сложен даже для тех, кто знает все эти понятия. Начните с keras, или придется запоминать этот материал как заклинания. В любом случае, здесь мы не будем останавливаться на объяснении основ нейронных сетей.

Начнем решать практическую задачу обучения нейронки на малюсеньком датасете ирисов из sklearn, в первую очередь, потому что его нет среди датасетов torch. Объявим необходимые библиотеки, которые будут объяснены по ходу действия нашей пьесы:

Объявление библиотек
Объявление библиотек

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

Загрузка датасета
Загрузка датасета

Следующим объявлением является собственно библиотека torch, без которой дальнейшее продолжение этой статьи бессмысленно. И вот тут мы уже вынуждены объяснять, что такое тензор. Это тот единственный способ работы с данными в pytorch. Откуда бы вы не получили данные (numpy, pandas или просто список), вам придется их конвертировать в тензор. Он очень похож на нумпаевский ndarray. Давайте знакомиться с тензором на практике:

Создание тензоров
Создание тензоров

Создаем два тензора x и y, причем второй будет целочисленным, а первый float (по умолчанию). В первом будут находится входные параметры, а во втором целевой параметр. Датасет у нас маленький, поэтому можем легко показать как выглядит часть тензора x:

Последняя часть тензора х
Последняя часть тензора х

Ну и до кучи покажем весь целочисленный целевой тензор:

Тензор y
Тензор y

Как вы можете видеть, оба тензора очень похожи на нумпаевские массивы. Если не знать, что это торческие тензоры, то работу с ними можно легко перепутать:

Разделение на обучающую и проверочную выборки
Разделение на обучающую и проверочную выборки

Надеюсь, вам понятен приведенный выше код, если нет, то прочитайте эту статью. Как и у ndarray у тензора есть свойство shape, с помощью которого вы можете посмотреть его размеры. Такая схожесть очень помогает изучать торч.

Но тут есть некоторые рамки, которые надо соблюдать, и только если вы хорошо знаете фреймворк, то можете обходить эти ограничения. Принято тензоры упаковывать в датасет, для этого есть специальная функция, но и это еще не все. Датасеты надо упаковывать в загрузчики данных, это что-то наподобие генераторов данных в tensorflow. Эти две функции находятся в третьей строке объявления. Сама же упаковка выглядит так:

Упаковка тензоров в датасеты, а их в загрузчики данных
Упаковка тензоров в датасеты, а их в загрузчики данных

Я не буду здесь останавливаться на разъяснении батча, на что влияет его размер, зачем нужно разбивать данные на участки, скажу только то что bs - сокращение от batch size. Во второй строке мы формируем обучающую выборку, в третьей - проверочную. Обучающий загрузчик создается с перемешиванием частей, а проверочный - без.

Мы готовы к созданию модели, не хватает только количество классов. Их можно получить из iris, но мы воспользуемся возможностями torch. Как и в numpy мы можем получить список уникальных объектов с помощью функции unique, а длина этого списка и будет искомым количеством классов:

Получение количества классов
Получение количества классов

Мы будем создавать последовательную модель, состоящую из входного, выходного и скрытого слоев. Для этого мы делали 4-ое объявление (nn - neural networks):

Создание модели
Создание модели

Обратите внимание, что слоя Dense, как в Keras, нет, вместо него используется Linear, которому передается количество входящих и выходящих параметров. Для входящего слоя число входящих параметров должно совпадать с числом входящих параметром в датасете, а вот выходящие должны совпадать с входящими следующего слоя. Кроме выходящего слоя, у которого число выходящих параметров должно равнять числу классов. Линейные слои соединяются функцией активации Relu.

Вы удивитесь, узнав, что в torch нет метрики точности accuracy, поэтому придется её писать самостоятельно:

Функции точности
Функции точности

Функции mean и argmax аналогичны numpy. Входной параметр input подразумевает тензор содержащий вероятности каждого класса. Получая индекс максимального значения по строке, мы получаем номер наиболее вероятного класса. Результатом сравнения полученного класса с фактическим будет булевый тензор, который мы преобразуем в тип вещественных чисел. Среднее от вещественного тензора (доля) умножим на 100 (процент).

А вот функции потерь и оптимизации имеются в наличии, поэтому просто присвоим их переменным:

Функции потерь, оптимизации и оценки
Функции потерь, оптимизации и оценки

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

Для такого маленького датасета потребуется большое количество проходов, чтобы получить хорошую точность. А для хранения истории обучения потребуются два пустых списка:

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

Теперь у нас все готово для обучения нашей модели:

Обучение модели
Обучение модели

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

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

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

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

Построение графика
Построение графика
Сам график обучения
Сам график обучения

По графику видно, что обучение работает, следовательно, мы сделали все правильно. В районе 400-500 эпох обучение можно было прекратить.

Pytorch несколько сложнее для работы с нейронными сетями, но раз его используют, то есть какие-то преимущества использования фрейиворка. Об этом мы здесь не говорили, потому как это уже другая тема. На самом деле чаще используют классы для построения модели, но классы сложнее понимать.

Наука
7 млн интересуются