Привет! На связи команда «БАРС Груп». Мы разработали и совершенствуем российскую BI-платформу Alpha BI. Это возможно благодаря таким фреймворкам, как PyTorch.
PyTorch активно развивается более пяти лет и представляет собой целую экосистему для создания моделей машинного обучения на основе глубоких нейронных сетей. У подобных ИТ-продуктов широкий спектр применения. В частности, они помогают научному и бизнес-сообществу проводить исследования, вести разведку данных и проверять гипотезы. Несмотря на то, что на сегодняшний день это один из самых популярных фреймворков машинного обучения в мире, в рунете пока довольно мало статей о его технических особенностях.
Мы решили восполнить этот пробел и перевели статью «Автоматическое дифференцирование в PyTorch» коллектива разработчиков PyTorch, дополнив ее комментариями из собственной практики.
По заверениям Адама Пашке (Adam Paszke), Сэма Гросса (Sam Gross) и их соавторов, PyTorch обеспечивает высокопроизводительную среду с легким доступом к автоматическому различению моделей, выполняемых на разных устройствах. Чтобы упростить прототипирование, PyTorch не следует символьному подходу, используемому во многих других средах глубокого обучения, а фокусируется на дифференцировании чистых императивных задач с упором на расширяемость и низкие накладные расходы.
1 Введение
PyTorch, как и большинство других библиотек глубокого обучения, поддерживает обратное автоматическое дифференцирование (также, АД) скалярных функций (или векторно-якобианских произведений функций с несколькими выходами). Это является наиболее важной формой автоматического дифференцирования для приложений глубокого обучения, которые обычно оптимизируют единственную функцию скалярной ошибки обучения. В этой области PyTorch поддерживает автоматическое дифференцирование, следуя идеям Chainer, HIPS autograd и twitter-autograd (twitter-autograd сам по себе был портом HIPS autograd на Lua).
У этих библиотек есть две отличительные особенности:
Динамическое пошаговое выполнение. Динамическая структура определяет функцию, которая должна быть дифференцирована, просто выполняя желаемое вычисление, в отличие от указания структуры статического графа, которая символически дифференцируется заранее, а затем выполняется много раз. Это позволяет пользователям использовать любые функции основного языка, которые они хотят (например, произвольные конструкции потока управления) за счет необходимости проведения дифференциации на каждой итерации. Динамическое выполнение отличает PyTorch от статических фреймворков, таких как TensorFlow, Caffe и т. д.
Немедленное выполнение. Фреймворк с немедленным выполнением запускает тензорные вычисления сразу по мере их обнаружения, он даже никогда не материализует граф выполнения, записывая только то, что необходимо для дифференцирования операций. Это контрастирует с DyNet, который использует отложенные вычисления, буквально перестраивая прямую и обратную диаграмму на каждой обучающей итерации (DyNet имеет режим немедленного выполнения для отладки, но он не включен по умолчанию). Немедленное выполнение позволяет делать конвейерными вычисления центрального и графического процессора, но лишает возможности оптимизации и пакетной обработки всей сети.
Хотя поддержка автоматического дифференцирования в PyTorch была в значительной степени обусловлена его предшественниками (особенно twitter-autograd и Chainer), он представляет некоторые новые варианты дизайна и реализации, которые делают его одним из самых быстрых среди библиотек автоматического дифференцирования, поддерживающих такое динамичное выполнение:
Операции на месте. Операции на месте представляют опасность для автоматического дифференцирования, поскольку операция «на месте» может сделать недействительными те данные, которые потребуются на этапе вычисления градиентов. Кроме того, они требуют нетривиальных преобразований последовательности вычислений. PyTorch реализует простые, но эффективные механизмы, решающие обе эти проблемы (более подробно они будут описаны во второй части статьи).
Без записи последовательности вычислений. Традиционная дифференциация в обратном режиме сохраняет последовательность вычислений (также известную как лента, список Венгерта, Wengert list), описывающую порядок, в котором операции были первоначально выполнены. PyTorch (и Chainer) избегают применения этой ленты. Вместо этого каждый промежуточный результат записывает только то подмножество графа вычислений, которое имело отношение к их вычислению. Это означает, что пользователи PyTorch могут смешивать и сопоставлять независимые графы, как им нравится, в любых потоках, которые им нравятся (без явной синхронизации). Дополнительное преимущество такого структурирования графов - то, что часть графа автоматически освобождается, когда становится неиспользуемой. Это важно, когда необходимо освободить большие фрагменты памяти как можно быстрее.
Базовая логика C++. PyTorch начал свою жизнь как библиотека Python, однако быстро стало ясно, что накладные расходы интерпретатора слишком высоки для базовой логики АД. На сегодняшний день большая часть переписана на C++, и мы находимся в процессе переноса определений основных операторов на C++. Тщательно настроенный код C++ — одна из основных причин, по которой PyTorch может обеспечить гораздо меньшие накладные расходы по сравнению с другими фреймворками.
Рисунок 1: Пример использования модуля автоматического дифференцирования PyTorch (torch.autograd).
2. Интерфейс
На рис.1 показан простой пример автоматического получения градиентов PyTorch. Вы пишете код так, как если бы выполняли тензорные операции напрямую, однако вместо работы с тензорами (эквивалент PyTorch массивам ndarray в Numpy) пользователь манипулирует переменными, в которых хранятся дополнительные метаданные, необходимые для АД. Переменные поддерживают метод backward(), который вычисляет градиент всех входных переменных, участвующих в вычислении этой величины. По умолчанию эти градиенты накапливаются в поле grad входных переменных, (такой дизайн унаследован от Chainer). Однако PyTorch также предоставляет функциональный интерфейс в стиле HIPS autograd для вычисления градиентов: функция torch.autograd.grad(f(x, y, z), (x, y)) вычисляет производную для f относительно только переменных x и y (для z градиент не вычисляется). В отличие от API в стиле Chainer, этот вызов не изменяет атрибуты .grad. Вместо этого он возвращает кортеж, содержащий градиент для каждой входной переменной.
Флаги переменных
Важно не вычислять производные, когда они не нужны. PyTorch, вслед за Chainer, предоставляет две возможности для исключения подграфов из производных вычислений: флаги «требует вычисления градиентов» («requires grad») и «изменяемый» («volatile»). Эти флаги подчиняются следующим правилам:
Если какая-либо входная переменная является изменяемой, то и выходная переменная является изменяемой. В противном случае, если для какой-либо входной переменной требуется вычисление градиентов, для вывода оно также требуется. Кроме того, установка флага «изменяемый» ведет к тому, что параметр «требует вычисления градиента» не устанавливается.
Если операция не требует вычисления градиентов, производное замыкание даже не создается. Флаг volatile позволяет полностью отключить автоматическое дифференцирование (например, при запуске модели для логического вывода не нужно устанавливать «requires grad» в false для каждого параметра).
Хуки
Недостаток автоматического дифференцирования - относительная непрозрачность для пользователей: в отличие от прямого прохода, который вызывается написанным пользователем кодом Python, дифференцирование выполняется из кода библиотеки, который пользователи почти не видят. Чтобы пользователи могли проверять градиенты, мы предоставляем механизм хуков, позволяющий пользователям наблюдать, когда вызывается обратная функция: x.register_hook (lambda grad: print(grad)). Этот код регистрирует хук для x, который печатает градиент x всякий раз, когда он вычисляется. Обратный вызов хука также может возвращать новый градиент, который используется вместо исходного градиента. Эта возможность оказалась полезной для метаобучения и обучения с подкреплением.
Расширения
Пользователи PyTorch могут создавать собственные автоматически дифференцируемые операции, указав пару прямой (forward) и обратной (backward) функций в Python. Прямая функция вычисляет операцию, а обратный метод расширяет векторно-якобианское произведение. Это можно использовать для того, чтобы сделать произвольные библиотеки Python (например, Scipy) дифференцируемыми (критически используя преимущества преобразования PyTorch с нулевым копированием NumPy).
Заключение
Завершая первую часть статьи про PyTorch, отметим, что подобные фреймворки заметно упрощают жизнь разработчикам и помогают ускоренными темпами развивать ИТ-технологии. В частности, создавать и совершенствовать прескриптивные BI-модели – основу помощников в принятии управленческих решений.