PyTorch — ваш новый фреймворк глубокого обучения |
||
МЕНЮ Искусственный интеллект Поиск Регистрация на сайте Помощь проекту ТЕМЫ Новости ИИ Искусственный интеллект Разработка ИИГолосовой помощник Городские сумасшедшие ИИ в медицине ИИ проекты Искусственные нейросети Слежка за людьми Угроза ИИ ИИ теория Внедрение ИИКомпьютерные науки Машинное обуч. (Ошибки) Машинное обучение Машинный перевод Реализация ИИ Реализация нейросетей Создание беспилотных авто Трезво про ИИ Философия ИИ Big data Работа разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика
Генетические алгоритмы Капсульные нейросети Основы нейронных сетей Распознавание лиц Распознавание образов Распознавание речи Техническое зрение Чат-боты Авторизация |
2017-09-04 13:31 машинное обучение python, архитектура нейронных сетей, техническое зрение PyTorch — современная библиотека глубокого обучения, развивающаяся под крылом Facebook. Она не похожа на другие популярные библиотеки, такие как Caffe, Theano и TensorFlow. Она позволяет исследователям воплощать в жизнь свои самые смелые фантазии, а инженерам с лёгкостью эти фантазии имплементировать. Данная статья представляет собой лаконичное введение в PyTorch и предназначена для быстрого ознакомления с библиотекой и формирования понимания её основных особенностей и её местоположения среди остальных библиотек глубокого обучения. PyTorch является аналогом фреймворка Torch7 для языка Python. Разработка его началась в недрах Facebook ещё в 2012 году, всего на год позже появления самого Torch7, но открытым и доступным широкой публике PyTorch стал лишь в 2017 году. С этого момента фреймворк очень быстро набирает популярность и привлекает внимание всё большего числа исследователей. Что же делает его таким популярным? Место среди остальных фреймворков Для начала разберёмся, что же вообще такое фреймворк глубокого обучения. Под глубоким обучением как правило понимают обучение функции, представляющей собой композицию множества нелинейных преобразований. Такая сложная функция ещё называется потоком или графом вычислений. Фреймворк глубокого обучения должен уметь делать всего три вещи:
Чем быстрее ты умеешь вычислять свою функцию и чем гибче твои возможности для её определения, тем лучше. Сейчас, когда каждый фреймворк умеет использовать всю мощь видеокарт, первый критерий перестал играть значительную роль. Что нас действительно интересует, так это доступные возможности для определения потока вычислений. Все фреймворки здесь можно разделить на три крупные категории.
Уверен, многие из нас начинали разбираться с глубоким обучением, используя только NumPy. Прямой проход написать на нём тривиально, а формулу обновления весов можно посчитать на листочке или вообще получить готовые веса из астрала. Выглядеть такой первый код мог так:
Со временем архитектуры сетей становятся сложнее и глубже и возможностей NumPy, карандаша и бумаги уже перестаёт не хватать. Если ваше соединение с астралом к этому моменту ещё не закрылось и вам есть откуда брать веса, вам повезло. В противном же случае невольно начинаешь задумываться о двух вещах:
При этом не хочется менять привычный подход, хочется просто написать:
Ну так вот, угадайте что? PyTorch ровно это и делает! Вот совершенно правильный код:
Остаётся лишь применить уже посчитанные обновления параметров. В Theano и TensorFlow мы описываем граф на декларативном DSL, который затем компилируется в некоторый внутренний байткод и исполняется в монолитном ядре, написанном на C++, или же компилируется в код на C и исполняется как отдельный бинарный объект. Если в момент компиляции нам известен весь граф целиком, его с лёгкостью можно продифференцировать, например символьно. Однако так ли необходима стадия компиляции? Оказывается, нет. Ничто не мешает нам строить граф динамически одновременно с его вычислением! А благодаря технике автоматического дифференцирования (automatic differentiation, AD) мы можем взять и продифференцировать граф в любой момент времени в любом его состоянии. В компиляции графа нет совершенно никакой реальной необходимости. Что касается скорости, вызов лёгких нативных процедур из интерпретатора Python оказывается не медленнее, чем исполнение скомпилированного кода. Не ограниченные DSL и компиляцией, мы можем использовать все возможности Python и делать код по-настоящему динамическим. Например, применять различные функции активации по чётным и нечётным дням:
Или мы можем создать слой, который каждый раз прибавляет к тензору только что введённое пользователем значение:
Более полезный пример я покажу в конце статьи. Резюмируя, всё сказанное выше можно выразить следующей формулой: Тензорные вычисления Начнём с NumPy части. Тензорные вычисления — основа PyTorch, каркас, вокруг которого наращивается вся остальная функциональность. К сожалению, нельзя сказать, что мощь и выразительность библиотеки в данном аспекте совпадает с таковой у NumPy. Во всём, что касается работы с тензорами, PyTorch руководствуется принципом максимальной простоты и прозрачности, предоставляя тонкую обёртку над вызовами BLAS. Тензоры Тип данных, хранимых тензором, отражается в имени его конструктора. Конструктор без параметров вернёт специальное значение — тензор без размерности, который нельзя использовать ни в каких операциях.
Все возможные типы:
Никакого автоматического определения типа или типа по-умолчанию не существует. Автоматического приведения типов как в NumPy также не осуществляется:
В этом плане
Любое приведение тензора к своему собственному типу не копирует его. Если передать конструктору тензора в качестве параметра список, будет построен тензор соответствующей размерности и с соответствующими данными.
Неправильно сформированные списки не допускаются так же, как и в NumPy.
Допускается построение тензора из значения любого типа последовательности, что весьма интуитивно и соответствует поведению NumPy. Другой возможный набор аргументов конструктора тензора — его размер. Количество аргументов при этом определяет размерность. Построенный таким методом тензор содержит мусор — случайные значения.
Индексирование Поддерживается стандартное индексирование Python: обращение по индексу и срезы.
Также в качестве индексов могут выступать другие тензоры. Однако, возможности здесь всего две:
Всю доступную информацию о тензоре помогут узнать функции
Операции над тензорами Соглашение о именовании в PyTorch гласит, что любая функция вида xxx возвращает новый тензор, т.е. является immutable функцией. В противоположность ей функция вида xxx_ изменяет изначальный тензор, т.е. является mutable функцией. Последние ещё носят название inplace функций. Почти для любой immutable функции в PyTorch существует её менее чистый собрат. Однако бывает и так, что функция существует лишь в каком-то одном варианте. По понятным причинам, функции, изменяющие размер тензора всегда являются immutable. Перечислять все доступные операции над тензорами я не буду, остановлюсь лишь на самых важных и разделю их на категории. Функции инициализации Как правило, они используются для инициализации при создании новых тензоров заданного размера
Так как mutable функции возвращают ссылку на объект, удобнее записывать объявление и инициализацию в одну строчку.
Доступны также экспоненциальное и геометрическое распределения, распределение Коши, логарифм нормального распределения и ещё несколько более сложных вариантов инициализации тензора. Не стесняйтесь смотреть в документацию! Математические операции Самая часто используемая группа. Если операция здесь не изменяет размер и тип тензора, то у неё существует inplace вариант.
Естественно, присутствуют все основные тригонометрические операции в том виде, в каком вы ожидаете их увидеть. Перейдём теперь к менее тривиальным функциям.
Существуют также полные аналоги BLAS функций со сложными сигнатурами, такие как Операции редукции похожи друг на друга по сигнатуре. Почти все они своим последним необязательным аргументом принимают
Всевозможные операции сравнения ( Операторы У PyTorch в запасе ещё много интересных функций вроде сортировки или поэлементного применения функции, но все они крайне редко используются в глубоком обучении. Если же вы захотите использовать PyTorch в качестве библиотеки тензорных вычислений, не забудьте перед этим заглянуть в документацию. Broadcasting Broadcasting — сложная тема. На мой взгляд, лучше бы его не было. Но он есть, хотя и появился лишь в одном из последних релизов. Многие операции в PyTorch теперь поддерживают broadcasting в привычном NumPy стиле. Если говорить в общем, то два непустых тензора называются bradcastable, если, начиная с последнего измерения, размеры обоих тензоров в этом измерении либо равны, либо размер одного из них равен единице, либо измерений в тензоре больше не существует. Легче понять на примерах.
Тензор размерности Размер тензора, получившегося в результате broadcasting, расчитывается следующим образом:
В примере размерность второго тензора была дополнена единицей в начале, а затем поэлементный максимум определил размерность результирующего тензора. Подвох кроется в inplace операциях. Broadcasting для них разрешён лишь в том случае, когда размер исходного тензора не изменится.
Во втором случае тензоры очевидно broadcastable, однако inplace операция не разрешена, поскольку Из NumPy и обратно Функции Тензоры при этом используют одно и то же внутренне хранилище, копирования данных не происходит.
На этом все основные моменты тензорной библиотеки PyTorch предлагаю считать рассмотренными. Надеюсь, теперь читателю понятно, что реализовать прямой проход для произвольной функции на PyTorch ничуть не сложнее, чем сделать это с помощью NumPy. Нужно лишь свыкнуться с inplace операциями и запомнить имена основных функций. Для примера, линейный слой с функцией активации softmax:
CUDA Здесь всё просто: тензоры могут жить либо "на процессоре", либо "на видеокарте". Правда, они весьма привередливы и живут только на видеокартах от Nvidia, причём не на самых старых. По-умолчанию тензор создаётся на CPU.
Память видеокарты при этом пуста.
Одним вызовом мы можем переместить тензор на GPU.
При этом
Свойство
На самом деле Когда исчезнут все ссылки на тензор, находящийся в видеопамяти, PyTorch не удалит его моментально. Вместо этого при следующем выделении он либо переиспользует этот участок видеопамяти, либо очистит её. Если у вас несколько видеокарт, функция Естественно, мы не можем производить никакие операции с тензорами, находящимеся на разных устройствах. Вот, например, как можно перемножить два тензора на видеокарте и вернуть результат обратно в оперативную память:
И всё это доступно прямо из интерпретатора! Представьте аналогичный код на TensorFlow, где вам придётся создать граф, сессию, скомпилировать граф, инициализировать переменные и запустить граф на сессии. С помощью PyTorch я могу даже отсортировать тензор на видеокарте одной строчкой кода! Тензоры можно не только копировать на видеокарту, но и создавать их прямо на ней. Для этого используется модуль В Контекстный менеджер
Функция Параметр Как видите, адаптация произвольного вашего кода для вычислений на видеокарте требует лишь копирования всех тензоров в видеопамять. Большинству операций без разницы, где находятся ваши тензоры, если они находятся на одном устройстве. Автоматическое дифференцирование Механизм автоматического дифференцирования, заключённый в модуле Вычисление градиента функции в заданной точке — центральная операция методов оптимизации, на которых, в свою очередь, держится всё глубокое обучение. Обучение здесь — синоним оптимизации. Существует три основных способа вычислить градиент функции в точке:
Первым методом пользуются лишь для проверки результатов из-за его низкой точности. Символьное вычисление производной эквивалентно тому, что вы делаете вручную, используя бумагу и карандаш, и заключается в применении списка правил к дереву символов. Про автоматическое же дифференцирование я расскажу в следующих параграфах. Библиотеки вроде Caffe и CNTK используют заранее предпосчитанную производную функции в символьном виде. Theano и TensorFlow используют комбинацию методов 2 и 3. Автоматическое дифференцирование (AD) — достаточно простая и весьма очевидная техника вычисления градиента функции. Если вы, не используя интернет, попытаетесь решить задачу дифференцирования функции в заданной точке, вы совершенно точно придёте к AD. Вот как AD работает. Любую из интересующих нас функций можно выразить как композицию некоторых элементарных функций, производные которых нам известны. Затем, используя правило дифференцирования сложной функции, мы можем подниматься всё выше и выше, пока не придём к искомой производной. Например, рассмотрим фукнцию двух переменных Переобозначим Каждая из получившихся функций является элементарной функцией — мы можем с лёгкостью вычислить её производную. Допустим, нас инетересует По правилу дифференцирования сложной функции можем записать Все компоненты этого уравнения — производные элементарных функций и начальные значения — нам известны. Осталось лишь подставить их и вычислить результат. Однако подстановку мы будем совершать не в произвольном порядке, а начнём с начала — самых вложенных элементов. Вот так за один проход мы можем вычислить производную функции в заданной точке. И необходимо для этого ровно такое же количество операций, как и для вычисления самой функции. Обратите внимание, что икс со звёздочкой — не символ, а некоторое числовое значение. Поэтому на каждом шаге рассмотренного алгоритма мы сохраняем не символьное выражение, а одно единственное число — текущее значение производной. Чтобы лучше понять AD, мы можем реализовать простейший его вариант всего в 20 строчек кода на чистом Python! Будем вычислять значение функции и её производную в одной и той же точке одновременно. Первым делом запомним значение переменной в точке и её производную.
При сложении двух переменных мы сконструируем новую переменную, значение которой будет равняться сумме исходных переменных, а производная будет вычисляться по правилу вычисления производной суммы двух функций.
Аналогично для умножения и возведения в степень
Теперь мы с лёгкостью можем одновременно вычислить и значение нашей функции и её частную производную по переменной
Ровно таким же поведением обладает класс
И снова нам никак не нужно изменять наш код для вычислений: достаточно лишь обернуть тензоры в В операциях нельзя смешивать обычные тензоры и тензоры, обёрнутые в Свойство Что стоит обсудить отдельно, так это inplace операции. Далеко не все такие операции, доступные для тензора, доступны для Пример В качестве примера мы не будем обучать однослойный перцептрон и даже не будем строить Допустим, вы прочитали статью Deep Function Machines: Generalized Neural Networks for Topological Layer Expression и решили попробовать реализовать её на практике. Статья обобщает слой нейронной сети как отображение между произвольными Хаусдорфовыми пространствами. Таким образом мы можем построить слой нейронной сети с непрерывным ядром, отображающий одно функциональное пространство в другое. Автор доказывает теорему, о том, что произвольному такому слою соответствует дискретный слой стандартной нейронной сети, причём единственный. Отношение это определяется следующим образом: Без вывода и доказательства результат кажется достаточно простым.
Быстро реализуем простейшую схему интегрирования.
Теперь так же быстро накидаем всё остальное.
Инициализируем наши веса.
Подготовим данные.
И обучим наш прототип.
Запустим и увидим в терминале стройную колонну убывающих значений. Ниже показано как ядро меняется в процессе обучения. Я даже не вспотел. Я разбирался с 3d графиками в matplotlib дольше, чем писал этот код. Да, он не идеален, но это работающий прототип, созданный за 15 минут. Считать вручную все эти производные нереально. TensorFlow… Я не представляю, как реализовать это на TensorFlow. PyTorch же наплевал на сложность и безумность вашей идеи. Если функция дифференцируема, PyTorch её дифференцирует. Если вам нужно перемножить тензоры на видеокарте, PyTorch не заставит вас определять граф, он позволит вам просто перемножить тензоры на видеокарте. Рассмотрим код обучения поближе.
Этот код оборачивает наши тензоры в переменные, чтобы все дальнейшие вычисления строили за собой граф. Каждый раз это необходимо делать из-за того что наш граф динамический и, следовательно, одноразовый: совершить обратный проход по нему можно лишь единожды.
Этот вызов, как вам уже должно быть известно, считает обратный проход по графу.
Здесь мы берём тензор из посчитанного градиента (переменной), домножаем его на некоторое маленькое по модулю отрицательное число и прибавляем к тензору исходных весов на месте. Таким образом совершается один шаг градиентного спуска.
Вызов Естественно, вам не придётся каждый раз вручную писать весь этот код: для стандартных задач в PyTorch предусмотрены все те же высокоуровневые абстракции, что и, например, в Keras. Заключение В данной статье мы рассмотрели основы PyTorch: тензорные вычисления, cuda вычисления и автоматическое дифференцирование. Тепреь вы с лёгкостью можете взять PyTorch и начать заниматься глубоким обучением с его помощью. Читателю может показаться, что PyTorch слишком низкоуровневый. Кажется, что для продуктивной работы над всем этим ещё предстоит написать удобную обёртку. С одной стороны это так: никто не запрещает вам написать поверх PyTorch ту обёртку, которая удобна лично вам. Вы можете написать её в модульном стиле, как в Caffe, а можете описать свой собственный декларативный DSL как в TensorFlow. PyTorch низкоуровневый настолько, насколько это только возможно и позволяет вам создавать поверх него те инструменты, с которыми будет максимально удобно работать лично вам. С другой стороны, существуют официальные реализации таких высокоуровневых обёрток, заключённые в модулях PyTorch великолепен. Он предоставляет вам различные уровни абстракции и позволяет с лёгкостью реализовывать свои. Он не загоняет вас в рамки какой-то одной парадигмы. Он быстрый, эффективный и распределённый. Попробуйте его и он вам непременно понравится. Спасибо за прочтение! Ставьте лайки, подписывайтесь на профиль, оставляйте комментарии, не тратьте время на борьбу с инструментами. Дополнения приветствуются. Источник: habrahabr.ru Комментарии: |
|