Простой рекуррентный нейрон

Строение рекуррентного нейрона

Здравствуйте и вновь добро пожаловать на занятия по теме «Рекуррентные нейронные сети на языке Python: глубокое обучение, часть 5».

В этой лекции мы поговорим о простом рекуррентном нейроне, также называемом нейроном Элмана.

Но прежде, чем переходить к самому рекуррентному нейрону, давайте поговорим о последовательностях. Зачем? Затем, что они несколько отличаются от данных, которые мы привыкли использовать. Напомним, что обычно наши входные данные X представлены матрицей размерностью NxD, то есть N примеров с D признаков. Таким образом, никаких последовательностей здесь нет.

Предположим теперь, что у нас есть последовательность. Какое количество размерностей нам понадобится? Пусть длина последовательности равна T. Если наблюдение представляет собой вектор размерности D и у нас их T штук, то одна последовательность наблюдений будет матрицей размерности TxD. Если при этом у нас N учебных примеров, то получится матрица размерностью NxTxD, то есть трёхмерный объект.

Иногда наши последовательности бывают разной длины. Такое случается, когда речь заходит о предложениях, которые, разумеется, могут иметь любую длину, или, скажем, о звуках, музыке, или чьей-нибудь кредитной истории. Что с этим делать? Впервые мы столкнулись с этой проблемой в курсе по скрытым марковским моделям. Решение заключается в том, чтобы сохранять каждое наблюдение в списке Python. Тогда вместо трёхмерной матрицы у нас будет список длиной N, каждый элемент которого является двухмерной последовательностью наблюдений, как массив Numpy. Поскольку списки Python могут содержать в качестве элементов любые объекты, то проблем не возникнет.

Вернёмся теперь к нашему простому рекуррентному нейрону. Работает он так.

Возьмём простую нейронную сеть прямого распространения с одним скрытым слоем. Входной и исходящий слои остаются совершенно прежними. В действительности они не являются частью самого рекуррентного нейрона, а включены они только для того, чтобы показать, что идёт перед нейроном и после него. Всё, что нужно, – это создать обратную связь от скрытого слоя к самому себе. На слайде изображён тот же нейрон, но с весовыми коэффициентами. Обратите внимание, что петля обратной связи предполагает задержку в один момент времени, поэтому один из входов в h(t) – это h(t-1).

 

Возникает вопрос: насколько большим должно быть Wh? Как и в остальных слоях, мы соединяем всё со всем. Поэтому если у нас M скрытых узлов, то первый скрытый узел соединяется со всеми M скрытых узлов. Второй скрытый узел также соединяется со всеми M скрытых узлов и так далее. Всего, таким образом, будет M2 скрытых весовых коэффициентов.

А вот математическое представление выхода рекуррентного нейрона:

Заметьте, что соединение с обратной связью включает момент времени минус единица. Следовательно, скрытый слой принимает как сам x, так и его предыдущее значение. Обратите также внимание на то, что функция f может быть любой из общепринятых нелинейных функций скрытого слоя – обычно это сигмоида, гиперболический тангенс или relu. Это гиперпараметр, такой же, как и в других типах нейронных сетей. Функция relu может помочь с проблемой исчезающего градиента, о которой мы поговорим в дальнейших лекциях.

Следует отметить, что тут нет марковского предположения. Почему? Потому что хотя h(t) определяется через своё предыдущее значение, это предыдущее значение определяется через значение перед предыдущим и так далее:

Это значит, что h(t) должно иметь некоторое начальное значение h(0). Иногда разработчики устанавливают его просто равным нулю, а иногда – рассматривают как параметр, который можно определить методом обратного распространения ошибки. Поскольку библиотека Theano позволяет всё это автоматически дифференцировать, мы будем рассматривать его как изменяемый параметр.

Иногда мне задают вопрос: а можно ли добавить ещё один рекуррентный слой в нейронную сеть? Ответ: да, можно. Количество рекуррентных слоёв – это такой же гиперпараметр, как и число скрытых слоёв для обычной нейронной сети прямого распространения. Вопрос их количества зависит от вашей конкретной задачи. Такого рода «наслоения» можно проводить для всех типов рекуррентных нейронов, с которыми вы встретитесь, включая управляемый рекуррентный нейрон и сети долгой краткосрочной памяти.

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

Прогнозирование и связь с марковскими моделями

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

Добавление фактора времени даёт нам несколько дополнительных вариантов с точки зрения цели или, другими словами, того, что мы пытаемся спрогнозировать. Прежде всего поговорим об уже известных вещах – функции софтмакс и мультиклассовой классификации. Предположим, мы взяли целую последовательность, использовали функцию argmax на софтмакс и результат использовали как определение класса. Какое предположение мы при этом сделали? Во-первых, мы забыли о факторе времени, но к этому мы сейчас вернёмся. Главное – предполагается, что вся последовательность соответствует одному классу. Это отлично подходит для множества задач. К примеру, если мы хотим провести классификацию между мужскими и женскими голосами, то у нас будут звуковые образцы голосов мужчин и женщин. Взяв один звуковой файл, где мужчина говорит, скажем, «Привет, мир!», мы классифицируем его как мужской, и этого достаточно: одна последовательность – одна метка.

Однако не забывайте, что для каждого h(t) есть своё y(t). y(t) – это просто значение последнего слоя, рассчитанное из h(t). Поэтому формально у нас могут быть метки для каждого момента времени, и очень скоро на примерах с кодом вы увидите, как это можно использовать. Сейчас же давайте просто попробуем представить ситуации, в которых это обстоятельство может оказаться полезным.

Представим нейрокомпьютерный интерфейс. Это система, которая постоянно считывает электрические сигналы из мозга. Предположим, целью нашего нейрокомпьютерного интерфейса является управление инвалидным креслом. Учитывая, что инвалидное кресло является средством передвижения, нам понадобится точная настойка устройства: оно должно двигаться вперёд, когда мы хотим двигаться вперёд, останавливаться, когда мы хотим остановиться, поворачивать влево, когда мы хотим повернуть влево. Всё это будет различными исходящими классами. В идеале управление движением должно быть в режиме реального времени, то есть чтобы действие происходило в тот же момент, когда мы об этом подумаем. В таком случае нам понадобится y(t) для каждого x(t). Это именно та ситуация, когда нам нужна не одна метка для одной последовательности, а одна метка для каждого момента времени.

Рассмотрим теперь третью ситуацию. Предположим, мы рассматриваем последовательность слов, или, иначе говоря, предложения. Как правило, нам нужно предсказать следующее слово с учётом всех предыдущих. Любопытно, что по сути это относится к обучению без учителя, поскольку тут нет целевых меток, целевая переменная является просто входом, как в автокодировщиках. Другими словами, мы пытаемся смоделировать вероятность

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

Рассмотрим, что получится, если мы попытаемся всю входящую последовательность сделать целевой. В этом случае нам придётся смоделировать вероятность

Что получится, если мы объединим первые два члена? Используя теорему Байеса, мы получим совместную вероятность p(x(2), x(1)). Опять объединив первые два члена, получим совместную вероятность p(x(3), x(2), x(1)). Напомним, это называется цепным правилом исчисления вероятностей. В конечном итоге мы получим совместную вероятность всей последовательности.

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

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

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

Развёртка рекуррентной сети

Поскольку время является своего рода «невидимым» фактором в строении рекуррентной нейронной сети, эта лекция вся посвящена визуализации рекуррентной сети в понятиях сети прямого распространения.

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

Представьте, что у нас есть последовательность длиной 5. Если мы «развернём» рекуррентную сеть во времени, так что рекуррентных связей не будет вовсе, у нас получится нейронная сеть прямого распространения с пятью скрытыми слоями. Это как если бы h(0) было входными данными, а каждое x(t) – просто некоторым дополнительным управляющим сигналом на каждом этапе.

Можно видеть, что весовые коэффициенты между скрытыми слоями, то есть wh, повторяются при каждом слое. Таким образом, это похоже на глубокую сеть с одинаковыми общими весовыми коэффициентами между слоями. Аналогично wx являются общими для каждого из пяти x, входящих в скрытые слои.

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

Метод обратного распространения ошибки во времени

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

Если вы помните «Глубокое обучение, часть 1», то знаете, что обратное распространение ошибки – это на самом деле лишь причудливое название градиентного спуска. Оно имеет некоторые любопытные свойства, но лежащий в его основе метод в точности идентичен тому, что мы делали в случае логистической регрессии, – простое вычисление градиента и движение в его направлении. Аналогично метод обратного распространения ошибки во времени, обычно сокращённо обозначаемый BPTT, является лишь причудливым названием метода обратного распространения ошибки, который сам по себе является причудливым названием градиентного спуска.

Что это значит для нас? Это значит, что в коде обновления всех весовых коэффициентов происходят точно так же, что резко облегчает нам задачу.

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

Как видим, wh появляется неоднократно, поэтому надо очень осторожно брать производную. Полагаю, вы уже знаете, как брать производную кросс-энтропийной функции и софтмакс (мы делали это в «Глубоком обучении, часть 1»), поэтому для нас сейчас важнее сосредоточиться на том, что происходит после этого.

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

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

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

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

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

Другой модификацией метода обратного распространения ошибки во времени является метод усечённого распространения ошибки во времени. Поскольку производные относительно wh и wx зависят от каждого момента времени в последовательности, то их вычисление занимает очень времени для очень длинных последовательностей. Общий метод приближения – просто остановиться после определённого количества моментов. Его недостатком является то обстоятельство, что он не будет включать ошибку для более длительных отрезков времени. Но если вас не волнует зависимость далее, скажем, трёх моментов времени, то вы можете отсечь её после этих трёх моментов.

Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: