Научитесь создавать прогрессивные нейронные сети с помощью PyTorch и библиотеки Doric.

Оглавление

Introduction & BackgroundTransfer LearningProgressive Neural NetworksDoricSimple ExampleComplex ExampleAutoencoders & VAEsExperimental DesignResultsAnalysisConclusionAcknowledgmentsLinks & References

Введение и история вопроса

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

Трансферное обучение - это концепция применения знаний, полученных в ходе выполнения одной задачи, к другой связанной задаче. Многие методы обучения передачи нейронной сети использовались с успехом. Однако топология сети обычно фиксированная, и параметры, используемые в старой задаче, могут быть перезаписаны деструктивно. Эти нежелательные качества устраняются прогрессивными нейронными сетями, которые могут расширять свою топологию, передавая с ней важные знания, сохраняя при этом исходные параметры для дальнейшего использования.

В этой статье мы обсудим трансферное обучение, катастрофическое забывание и прогрессивные нейронные сети. В нем мы научимся использовать библиотеку Doric вместе с PyTorch для создания проектов; а также пройтись по коду, стоящему за ним, и изучить, как он реализует функциональные возможности, обсуждаемые в статье о прогрессивных нейронных сетях. Наконец, мы проведем два прогнет-эксперимента с использованием Doric и проанализируем их результаты, чтобы лучше понять преимущества и недостатки использования прогрессивных нейронных сетей для трансферного обучения.

Передача обучения

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

Этот маневр называется «вилкой» и ставит черных в проигрышную ситуацию, когда они должны разменять одну из ладей на белого коня. Опытный шахматист поймет, что принуждение соперника к вилке может быть чрезвычайно выгодным.

Шахматы - не единственная игра, в которой появляется эта простая тактика. Connect-4 - это игра, в которой два игрока бросают плитки в вертикальную сетку 6 на 7 с целью завершить серию из 4 плиток до того, как это сделает их противник. Ниже представлена ​​схема платы подключения-4. Красный имеет возможность соединить три плитки таким образом, чтобы синий мог блокировать бег только с одной стороны, позволяя красному выиграть на следующем ходу.

Шахматы и Коннект-4 - очень разные игры, но каждая из них включает в себя одну и ту же стратегию нападения сразу с двух сторон, заставляя врага оказаться в безвыходной ситуации. Фактически, тактику вилки можно найти во множестве игр. Изучая концепцию форка и применяя эту концепцию через механику другой игры, мы можем передавать знания в разные области. Изучение шахмат может научить нас кое-чему о Connect-4.

И в этом, дорогие читатели, суть трансферного обучения.

Трансферное обучение - это процесс применения знаний, полученных при выполнении одной задачи, к отдельной, но связанной задаче. В последние годы трансферное обучение широко применяется в области обработки языка и изображений. При обработке естественного языка предварительно обученные модели встраивания, такие как ELMo, BERT и GPT-2, преуспели в изучении базовой структуры английского языка, так что ее можно было применять для решения множества задач. В этом случае модель часто замораживается, и оптимизация выполняется только на модели задачи, подключенной к головке устройства для внедрения. Хотя этот подход идеально подходит для языковых задач, он основан на последовательном вводе, соответствующем структуре языка, на котором он был обучен. В области обработки изображений полное обучение сверточной нейронной сети для выполнения сложной задачи стало редкостью. Вместо этого предварительно обученные универсальные сети, такие как ResNet-50, GoogLeNet и VGG-19, переобучаются на новых данных. Этот процесс известен как тонкая настройка. Хотя точная настройка является эффективным способом адаптации сети к новым данным, она не лишена недостатков.

Катастрофическое забывание - это опасное событие, которое может произойти в обученной нейронной сети во время точной настройки. Это происходит, когда важные параметры в сети изменяются для соответствия новым данным, что ставит под угрозу способность сети обрабатывать старые данные. В идеале сеть должна сохранять свою компетенцию в отношении предыдущих задач за счет кодирования новой информации в рамках редко используемых параметров или путем изменения важных параметров таким образом, чтобы они распространялись на другие задачи, вместо того, чтобы полностью переключаться на них. Используя стандартные методы обучения, основанные на градиентах, это не всегда возможно. Было предложено множество методов борьбы с катастрофическим забыванием, но обсуждаемый здесь алгоритм делает больше: он полностью удаляет его, при этом обеспечивая эффективное общее трансферное обучение.

Прогрессивные нейронные сети

Прогрессивная нейронная сеть (prognets) - это нейронный алгоритм, разработанный Deepmind в их статье Прогрессивные нейронные сети (Rusu et al., 2016). Prognets - это простое, мощное и креативное решение для передачи обучения - если цитировать бумажное резюме, они неуязвимы для забвения и могут использовать предыдущие знания через боковые связи с ранее изученными функциями.

Прогнозы начинают свое существование с нейронной сети с одним столбцом, которая будет обучена исходной задаче. Каждый столбец состоит из L блоков, каждый из которых включает слой нейронов W, набор латеральных нейронов для каждого родительского столбца U и функция активации f. Поскольку исходный столбец не имеет входящих боковых соединений, он будет действовать так же, как и стандартная нейронная сеть. Этот первый столбец изучает задачу 1, а затем ее параметры замораживаются. Для задачи 2 создается новый столбец и добавляются боковые соединения. Эти боковые соединения принимают в качестве входных данных выходы предыдущего блока во всех предыдущих столбцах. Затем к основному слою добавляются боковые слои и, наконец, применяется активация.

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

Другой способ понимания прогнозов - рассматривать каждый блок как черный ящик вычислений, который должен решить, какие источники информации наиболее эффективны для выполнения своей задачи. Например, на рисунке выше output₃ имеет доступ к предыдущему скрытому слою h₂. Однако он также имеет доступ к слою h₂ для каждого его родительского столбца и может локально настраивать свои выходные данные с помощью своих боковых параметров. Если блок output₃ обнаруживает, что h₂¹ имеет всю необходимую информацию, он может «обнулить» другие входные данные. Если он обнаружит, что h₂² и h₂³ каждый предлагает часть необходимой информации, он может игнорировать h₂¹ и действовать как функция h₂² и h₂³. Устанавливая эти боковые связи, мы позволяем сети легко передавать информацию между задачами, а также позволяем ей игнорировать неважную информацию.

Авторы протестировали свой алгоритм на наборе сред обучения с подкреплением, включая варианты понга, варианты лабиринта и игры Atari. Они протестировали различные базовые модели и обнаружили, что прогнозы превзошли все базовые показатели. После публикации несколько других статей показали успешное использование программ в различных областях; например, Прогрессивные нейронные сети для передачи обучения при распознавании эмоций (Gideon et al., 2017) обнаружили, что прогрессивные нейронные сети могут использоваться для обнаружения эмоций при наличии набора стандартных задач. Точно так же Обучение роботов от симулятора к реальному на основе пикселей с прогрессивными сетями (Rusu et al., 2018) показало, что прогрессивные нейронные сети могут использоваться для передачи между несовершенными симуляциями роботов и реальным оборудованием.

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

Таким образом, прогрессивные нейронные сети невосприимчивы к катастрофическому забыванию, потому что они замораживают каждый законченный столбец, но они также позволяют эффективно передавать обучение через боковые подключения к новым столбцам. Для достижения этой цели они используют повышенную сложность модели (то есть большее количество параметров для обучения в каждом дополнительном столбце) и гибкость при изменении задач. Многие предыдущие публикации и эксперименты подтверждают надежность этого алгоритма как мощной платформы для трансферного обучения.

Дорик

Doric - это инструмент, который я разработал для реализации и расширения прогрессивных нейронных сетей. Это бесплатная библиотека с открытым исходным кодом, созданная на основе PyTorch. В оставшейся части статьи мы пройдемся по коду Doric, проведем с ним простой эксперимент для анализа прогнета во время его обучения и, наконец, протестируем Doric на сложном наборе задач обработки изображений.

Примечание: версия Дорика, показанная здесь, упрощена. Полная версия с примером кода находится здесь.

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

Расширяя этот класс и реализуя нереализованные методы, пользователь может легко создавать свои собственные блоки ProgBlocks для любого возможного использования. Добавим несколько простых.

Теперь у нас есть блоки для простых плотных слоев, плотных слоев с пакетной нормализацией и 2D сверточных слоев. Обратите внимание, что в нашем объекте любые списки, содержащие модули PyTorch, реализованы как nn.ModuleLists. Это потому, что он позволяет PyTorch правильно регистрировать эти подмодули. Также стоит отметить, что поскольку ProgBlocks является подклассом nn.Module, их можно запускать, как и любые другие модули PyTorch. Однако мы не реализовали для них метод пересылки, поэтому нам все равно нужно будет вызывать runActivation и runBlock.

tensor([
[0.0000, 0.0000, 1.8789, 0.0000, 0.0000],
[0.0138, 0.0000, 2.9942, 0.0000, 0.0000],
[1.5449, 0.0000, 2.8891, 0.0000, 0.0000]], 
grad_fn=<ReluBackward0>)

Теперь, когда у нас есть блочный объект, нам нужно организовать их в сети столбцов. Эти сети столбцов необходимо добавлять каждый раз, когда указывается новая задача. Однако, поскольку у нас есть хорошая расширяемая блочная структура, нам не нужно полагаться на пользователя при реализации отдельных столбцов - по крайней мере, не напрямую.

Наконец, нам нужна вся прогрессивная нейронная сеть. Этот объект будет содержать список цепочек столбцов и будет запускать каждый необходимый столбец для получения результата для данной задачи.

Следует отметить несколько важных моментов. Во-первых, в addColumn Doric позволяет пользователю напрямую передавать цепочку столбцов, или пользователь может создать генератор столбцов, расширив класс ProgColumnGenerator.

Это позволяет пользователю определять архитектуру каждого столбца в методе generateColumn таким образом, чтобы прогнет мог добавлять столбцы, не передавая их. Аргумент msg также позволяет пользователю передавать информацию через метод addColumn в generateColumn.

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

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

Самый простой способ разработать прогрессивную систему для такой сети - разрешить использование многоканальных блоков. Эти блоки также должны обеспечивать прямой проход по определенным каналам, которые не являются боковыми.

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

Чтобы реализовать оба этих изменения, нам нужно только определить их и внести несколько небольших изменений в наш класс сети столбцов.

Вот и все. На момент публикации этой статьи это (упрощенная) основная функциональность Doric. Используя эти инструменты, мы можем реализовать большинство архитектур нейронных сетей как прогнет.

Простой пример

В этом первом примере Doric мы создадим прогнет с двумя задачами. В частности, мы научим наши столбцы имитировать функции xor и nand. Напомним, что таблицы истинности xor и nand приведены ниже.

В этом простом эксперименте первый столбец будет решать задачу xor, а второй - использовать первый для решения nand. Чтобы было понятно, это не тот набор задач, который требует трансферного обучения. Сеть с одним столбцом с такой архитектурой может решить любую задачу без особых проблем. Ценность этого эксперимента в том, что он позволит нам глубоко проанализировать эти сети и увидеть, как боковые связи передают знания. Это код, который мы будем использовать для эксперимента.

После запуска этой программы мы получаем эти результаты.

Testing Xor.
Input: [0, 0].  Target: 0.  Predicted: 0.  Error: 0.
Input: [0, 1].  Target: 1.  Predicted: 1.  Error: 0.
Input: [1, 0].  Target: 1.  Predicted: 1.  Error: 0.
Input: [1, 1].  Target: 0.  Predicted: 0.  Error: 0.
Testing Nand.
Input: [0, 0].  Target: 1.  Predicted: 1.  Error: 0.
Input: [0, 1].  Target: 1.  Predicted: 1.  Error: 0.
Input: [1, 0].  Target: 1.  Predicted: 1.  Error: 0.
Input: [1, 1].  Target: 0.  Predicted: 0.  Error: 0.

Xor and Nand params:
columns.0.blocks.0.module.weight
Parameter containing:
tensor([[-3.2474,  3.2474],
        [-2.0008,  2.0429],
        [-0.5795,  0.0801]])
columns.0.blocks.0.module.bias
Parameter containing:
tensor([-1.7442e-05,  2.0007e+00, -3.0051e-01])
columns.0.blocks.1.module.weight
Parameter containing:
tensor([[ 4.5644, -3.4079,  0.1078]])
columns.0.blocks.1.module.bias
Parameter containing:
tensor([3.0512])
columns.1.blocks.0.module.weight
Parameter containing:
tensor([[-0.2739, -0.1766],
        [ 0.5853,  2.8434],
        [-0.6689,  0.3338]], requires_grad=True)
columns.1.blocks.0.module.bias
Parameter containing:
tensor([-0.3227, -0.5853, -0.3339], requires_grad=True)
columns.1.blocks.1.module.weight
Parameter containing:
tensor([[-0.1367, -2.9894, -0.4210]], requires_grad=True)
columns.1.blocks.1.module.bias
Parameter containing:
tensor([1.9972], requires_grad=True)
columns.1.blocks.1.laterals.0.weight
Parameter containing:
tensor([[1.9479, 0.2868, 0.1236]], requires_grad=True)
columns.1.blocks.1.laterals.0.bias
Parameter containing:
tensor([1.8058], requires_grad=True)

Эти журналы показывают, что сеть изучила функции xor и nand, но нам нужно будет присмотреться, если мы хотим увидеть, как она это сделала. Начнем со столбца xor. После нанесения всех параметров на структуру графика мы можем построить следующую диаграмму.

Примечания:

  • Крайний правый нейрон всегда будет выводить 0. Это означает, что для обучения этой функции не было необходимости.
  • Если X1 и X2 совпадают, они будут отменены, и параметры смещения будут определять вывод скрытого слоя. Результат - сигмовидная (-3,6), или ~ 0,02.
  • Если X1 равно 1, а X2 равно 0, скрытый слой выводит [0, 0, 0]. Результат - сигмовидная (3.2), или ~ 0,96.
  • Если X1 равно 0, а X2 равно 1, скрытый слой выводит [3.2, 4, 0]. Результат ~ 0,98.

Все это полезно знать, но неудивительно. Посмотрим, что произойдет, когда мы добавим столбец nand.

Теперь перечислим все входные комбинации.

На заметку:

  • На этот раз обнуляются два нейрона скрытого слоя. Это потому, что nand - намного более простая задача, чем xor (nand линейно разделим).
  • Все еще активный нейрон в скрытом слое выдаст 0, 2,8 или 2,2.
  • Когда активный скрытый нейрон выдает 2,2, латеральный слой поднимается, чтобы компенсировать значение -4,6 внелатерального входа.
  • Если бы боковой слой был обнулен, ответ был бы неверным при вводе [0, 1].
  • Когда задан вход [0, 0] или [1, 1], отводы выводят то же значение. Для этих выходов боковые стороны не имеют большого значения.

Сложный пример

Автоэнкодеры и VAE

Автоэнкодеры - это мощная архитектура, которую можно применять для множества задач с хорошими результатами (особенно для задач обработки изображений). Они также идеально подходят для демонстрации прогрессивных сетей. Это по двум причинам:

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

Вариационные автокодеры (VAE) - это вариант автокодировщика, в котором сеть изучает абстракцию среднего и стандартного отклонения данных вместо самих данных. Это дает секции декодера мощные генеративные способности! Эта статья достаточно длинная без подробного объяснения автокодировщиков и VAE, поэтому я рекомендую прочитать эту статью Irhum Shafket На пути к науке о данных.

Экспериментальный дизайн

Или в этом эксперименте мы построим прогрессивный вариационный автокодировщик с использованием Doric. Эта сеть сможет выполнять четыре различные задачи в области обработки изображений. Эти операции перечислены ниже.

  1. Простая реконструкция - реконструкция входа как выхода с сохранением важной информации через слой узких мест.
  2. Denoising - удаление шума соли и перца со входа.
  3. Раскрашивание - добавление цвета к серой шкале ввода.
  4. Inpainting - закрашивание части изображения, которая была закрыта случайным блужданием черных пикселей.

Мы будем использовать набор данных CelebA для проведения эксперимента, массивный набор изображений лиц знаменитостей. Этот набор данных подходит для параметров нашего эксперимента из-за своего размера, сложности и легкости выявления несоответствий на человеческом лице.

Полный код для запуска этого эксперимента можно найти в репозитории Doric в каталоге примеров.

Полученные результаты

Ниже приведены результаты трех экспериментальных столбцов прогрессивного VAE. Все они имеют формат исходный / ввод / вывод. Столбец 0 (простая реконструкция) не показан, так как он не содержит отводов.

Анализ

Чтобы начать наш анализ результатов, я думаю, что важно сначала обратить внимание на странность автокодировщика - в то время как одни изображения переводятся очень хорошо, другие - нет. Однако когда VAE выходит из строя, он редко делает это, создавая искаженное или нереалистичное человеческое лицо. Вместо этого он чрезмерно обобщает и создает новое человеческое лицо, более похожее на то, что он видел раньше. Ошибки такого рода и другие странные не связаны с прогнетом и все равно будут возникать в автономном VAE.

При этом мы можем приступить к анализу результатов в отношении прогнета. Начиная с столбца шумоподавления, мы видим то, что можно было бы ожидать от любого обычного шумоподавителя VAE. Сеть хорошо справилась с удалением шума и поддержанием качества изображения (следует ожидать нечеткости, поскольку узкое место имеет только размер 128). Что примечательно, так это время тренировки; если вы запустите код, вы, скорее всего, обнаружите, что сеть учится шумоподавлению намного быстрее как прогнет, чем как отдельная сеть.

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

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

Эти результаты показывают, что в столбцах 1 и 2 большая часть вычислений выполняется боковыми стволами. В последнем столбце почти вся работа выполняется отводами. Это убедительный индикатор того, что происходит трансферное обучение.

Заключение

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

Точно так же Doric - полезная облегченная библиотека для разработки и экспериментирования с проектами. Благодаря блокам, созданным пользователем, и включению многоканальных блоков и инертных блоков, практически любая стандартная архитектура может быть реализована как прогрессивная сеть.

Благодарности

Спасибо Кейсу Райту за помощь в разработке Doric и написанию всех наших тестов автоэнкодера, Дэвиду Стакеру за помощь в редактировании, доктору Густаво Родригес-Ривере за руководство и академическую поддержку, а также Университету Пердью.

Ссылки и справочные материалы

Все изображения и другие материалы, защищенные авторским правом, были созданы автором, если не указано иное.