Записная книжка

Компьютерное зрение, машинное обучение, нейронные сети и т.п.

RNN, LSTM, GRU и другие рекуррентные нейронные сети.

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

Содержание

Recurrent neural network

Начнем с основ, а именно c простого RNN.

Весь смысл обработки связной последовательности данных заключается в том, чтобы кроме выделения отклика для каждого элемента, суметь учесть и связь элементов. Например, можно представить изображение в виде набора векторов, каждый из которых содержит пиксели одной колонки. Будем последовательно подавать такие вектора на вход сети, если мы хотим, чтобы сеть классифицировала изображение, необходимо сделать так, чтобы сеть умела “запоминать” информацию об уже просмотренных колонках. Аналогично, если мы хотим научить сеть классифицировать предложения естественного языка (например, определять эмоциональный окрас предложения), и подаём в сеть одно слово за другим, нам желательно, чтобы сеть “помнила” уже переданные слова. Если мы хотим, чтобы сеть переводила предложение с одного языка на другой, то тоже было бы не плохо учитывать начало предложение при переводе середины и конца.

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

Допустим у нас есть набор входных векторов $(X^{(1)}, X^{(2)},…, X^{(n)})$, мы их последовательно преобразуем:

\[\begin{align*} H^{(t)} &= \sigma\left(W^{hx} \cdot X^{(t)} + W^{hh} \cdot H^{(t-1)} + b_h\right)\\ Y^{(t)} &= W^{yh} \cdot H^{(t)} + b_y \end{align*}\]

При этом кроме выхода $Y^{(t)}$ (который нам возможно и не нужен на каждом шаге), мы имеем еще вектор $H^{(t)}$, описывающий текущее состояние. Таким образом сеть состоит из ячеек вида:

Ячейка RNN с обратной связью

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

Последовательность ячеек RNN

Можно выделить три варианта конфигурации такой сети.

Sequence to sequence

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

RNN sequence to sequence

Ещё один вариант, когда сеть преобразует последовательность в последовательность, может быть вот таким:

RNN sequence to sequence

На самом деле это сильно похоже на вариант encoder-decoder, когда входная последовательность кодируется (закодированые данные оказываются во внутреннем состоянии сети), а затем декодируется. Например, такая схема используется для переводов текстов. Чуть позже мы рассмотрим схему encoder-decoder для работы с видео.

Sequence to one

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

RNN sequence to one

One to sequence

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

RNN one to sequence

Backpropagation throught time

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

Long short-term memory

Проблема с обычными RNN ячейками, описанными выше, заключается в том, что они не могут “удержать в памяти” слишком длинные последовательности. Связано это с тем, что когда мы прокидываем градиенты через, достаточно, длинную последовательность, то мы сталкиваемся с одной из двух проблем: либо градиенты уменьшаются настолько, что ошибки на конце последовательности перестают влиять на ее начало, либо градиенты увеличиваются, и процесс расходится. Такая же проблема существует и в обычных сетях: добавляя слои мы начинаем испытывать затруднения с тренировкой (отсюда всякого рода ResNet и т.п. подходы).

Чтобы победить эту проблему было предложено заменить обычные RNN ячейки на более продвинутый вариант LSTM. В базовом варианте LSTM было добавлено еще одно внутренние состояние ячейки $S_t$, которое на каждом шаге преобразовывалось очень похоже на то как это делается в ResNet слоях (т.е. функцией вида $S_{t+1} = f(S_{t}) + S_{t}$) и за счет этого гасилась проблема исчезающих или взрывающихся градиентов.

Так же в LSTM ячейку были добавлены input и output gate, которые должны были воздействовать на то, что из входных данных окажет влияние на внутренние состояние ячейки и что из внутреннего состояния должно отправится на выход. Схематично можно представить это следующим образом:

Ячейка LSTM

А формально:

\[\begin{align*} g^{(t)} &= \phi\left(W^{gx} \cdot X^{(t)} + W^{gh} \cdot H^{(t-1)} + b_g\right)\\ i^{(t)} &= \sigma\left(W^{ix} \cdot X^{(t)} + W^{ih} \cdot H^{(t-1)} + b_i\right)\\ o^{(t)} &= \sigma\left(W^{ox} \cdot X^{(t)} + W^{oh} \cdot H^{(t-1)} + b_o\right)\\ S^{(t)} &= g^{(t)} \odot i^{(t)} + S^{(t-1)}\\ H^{(t)} &= \phi\left(S^{(t)}\right) \odot o^{(t)} \end{align*}\]

Здесь $i^{(t)}$ и $o^{(t)}$ это соответственно, входные и выходные ворота, а $\odot$ обозначает операцию поэлементного умножения. Т.е. $i^{(t)}$ и $o^{(t)}$ предполагаются векторами из единиц и нулей, которые пропускают или не пропускают какие-то компоненты вектора через себя.

LSTM with forget gate

Концепция ворот так всем понравилась, что в дальнейшем добавили еще одни: forget gate, которые позволяли дополнительно занулять некоторые компоненты внутреннего состояния $S^{(t-1)}$ прежде чем передавать их дальше.

Ячейка LSTM с forget gate

Формально:

\[\begin{align*} g^{(t)} &= \phi\left(W^{gx} \cdot X^{(t)} + W^{gh} \cdot H^{(t-1)} + b_g\right)\\ i^{(t)} &= \sigma\left(W^{ix} \cdot X^{(t)} + W^{ih} \cdot H^{(t-1)} + b_i\right)\\ o^{(t)} &= \sigma\left(W^{ox} \cdot X^{(t)} + W^{oh} \cdot H^{(t-1)} + b_o\right)\\ f^{(t)} &= \sigma\left(W^{fx} \cdot X^{(t)} + W^{fh} \cdot H^{(t-1)} + b_f\right)\\ S^{(t)} &= g^{(t)} \odot i^{(t)} + S^{(t-1)} \odot f^{(t)}\\ H^{(t)} &= \phi\left(S^{(t)}\right) \odot o^{(t)} \end{align*}\]

LSTM with peephole conection

Peephole connection еще одно дополнение к базовой LSTM ячейке, которое позволяет учесть текущее состояние $S^{(t-1)}$ в вычислении всевозможных ворот:

Ячейка LSTM с peephole connection

Формально:

\[\begin{align*} g^{(t)} &= \phi\left(W^{gx} \cdot X^{(t)} + W^{gh} \cdot H^{(t-1)} + b_g\right)\\ i^{(t)} &= \sigma\left(W^{ix} \cdot X^{(t)} + W^{ih} \cdot H^{(t-1)} + P_i \odot S^{(t-1)} + b_i\right)\\ o^{(t)} &= \sigma\left(W^{ox} \cdot X^{(t)} + W^{oh} \cdot H^{(t-1)} + P_o \odot S^{(t-1)} + b_o\right)\\ f^{(t)} &= \sigma\left(W^{fx} \cdot X^{(t)} + W^{fh} \cdot H^{(t-1)} + P_f \odot S^{(t-1)} + b_f\right)\\ S^{(t)} &= g^{(t)} \odot i^{(t)} + S^{(t-1)} \odot f^{(t)}\\ H^{(t)} &= \phi\left(S^{(t)}\right) \odot o^{(t)} \end{align*}\]

Т.е. мы дополнительно тренируем три набора весов $P_i, P_o, P_f$, которые фильтруют текущее состояние, для использования в соответствующих воротах. Заметим, что peephole connection можно использовать и в ячейках без forget gate, просто здесь расписаны формулы максимально общего LSTM.

Gated Recurrent Unit

Еще один вариант ячейки, очень похожий на LSTM, но с меньшим количеством “ворот” (двумя), а значит и параметров (качество при этом обещают сравнимое с LSTM с таким же количеством параметров):

Ячейка GRU

Формально:

\[\begin{align*} z^{(t)} &= \sigma\left(W^{zx} \cdot X^{(t)} + W^{zh} \cdot H^{(t-1)} + b_z\right)\\ r^{(t)} &= \sigma\left(W^{rx} \cdot X^{(t)} + W^{rh} \cdot H^{(t-1)} + b_r\right)\\ \tilde{H}^{(t)} &= \phi\left(W^{\tilde{h}x} \cdot X^{(t)} + W^{\tilde{h}h} \cdot \left(r^{(t)} \odot H^{(t-1)}\right) + b_{\tilde{h}}\right)\\ H^{(t)} &= (1 - z^{(t)}) \odot H^{(t - 1)} + z^{(t)} \odot \tilde{H}^{(t)} \end{align*}\]

Minimal Gated Unit

Наконец, был предложен вариант ячейки, с минимальным числом параметров при это качеством не уступающий GRU. Он содержит только одни “ворота”, а именно “forget gate”:

Ячейка MGU

Формально:

\[\begin{align*} f^{(t)} &= \sigma\left(W^{fx} \cdot X^{(t)} + W^{fh} \cdot H^{(t-1)} + b_f\right)\\ \tilde{H}^{(t)} &= \phi\left(W^{\tilde{h}x} \cdot X^{(t)} + W^{\tilde{h}h} \cdot \left(f^{(t)} \odot H^{(t-1)}\right) + b_{\tilde{h}}\right)\\ H^{(t)} &= (1 - f^{(t)}) \odot H^{(t - 1)} + f^{(t)} \odot \tilde{H}^{(t)} \end{align*}\]

Bidirectional recurrent neural network

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

Ячейка BRNN

Формально:

\[\begin{align*} H^{(t)} &= \phi\left(W^{hx} \cdot X^{(t)} + W^{hh} \cdot H^{(t-1)} + b_h\right)\\ Z^{(t)} &= \phi\left(W^{zx} \cdot X^{(t)} + W^{zz} \cdot Z^{(t+1)} + b_z\right)\\ Y^{(t)} &= \sigma\left(W^{yh} \cdot H^{(t)} + W^{yz} \cdot Z^{(t)} + b_y\right) \end{align*}\]

Нейронная машины Тьюринга

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

схема, Нейронная машины Тьюринга

Статью про нейронную машину Тьюринга я разбирал. Если коротко, эта архитектура позволяет обучить сеть некоторым простым алгоритмам (копирование, сортировка и т.п). В данном случае появляется именно “алгоритмическое обобщение”, т.е. сеть, например, учится копировать на последовательностях длины 20, а потом применяет этот навык на последовательностях большей длины.

Многослойный LSTM

Для увеличения “приёмистости” сети, обычно набирают несколько слоёв LSTM:

Многослойный LSTM

Свёрточный LSTM

Когда речь заходит о картинках, свёрточные слои побеждают обычные в силу того, что сохраняют информацию о пространственной связи пикселей изображения, содержат меньше параметров, а значит требуют меньше тренировочных данных. В LSTM при работе с картинками также рекомендуется заменить обычные слои на свёрточные. Для этого просто во всех преобразованиях в LSTM заменяем умножение матрицы весов на вектор - свёрткой (формулы приведены для простейшего случая LSTM без forget gate и без peephole):

\[\begin{align*} g^{(t)} &= \phi\left(W^{gx} \ast X^{(t)} + W^{gh} \ast H^{(t-1)} + b_g\right)\\ i^{(t)} &= \sigma\left(W^{ix} \ast X^{(t)} + W^{ih} \ast H^{(t-1)} + b_i\right)\\ o^{(t)} &= \sigma\left(W^{ox} \ast X^{(t)} + W^{oh} \ast H^{(t-1)} + b_o\right)\\ S^{(t)} &= g^{(t)} \odot i^{(t)} + S^{(t-1)}\\ H^{(t)} &= \phi\left(S^{(t)}\right) \odot o^{(t)} \end{align*}\]

здесь $\ast$ обозначает операцию свёртки, при этом вектора ($X^{(t)}$, $H^{(t)}$ и т.д.) превращаются в трехмерные тензоры. Как и для обычных LSTM можно набирать такие ячейки слоями, и аналогично, тому как мы обычно при работе с картинками используем свёртки с некоторым шагом (stride), чтобы уменьшать пространственные размеры тензоров, можно и здесь пользоваться таким подходом.

Batch normalization

В обычных сетях применение batch normalization практически стало обязательным.

В RNN, а тем более в LSTM достаточно широкий выбор вариантов какой именно выход и каким образом нормализовывать. Например, в одной из статей ([8]) для RNN предлагается применять batch normalization только на проекции входных данных $X^{(t)}$ в $H^{(t)}$, т.е. формулы для RNN будут выглядеть так:

\[\begin{align*} H^{(t)} &= \sigma\left(\textrm{BN}(W^{hx} \cdot X^{(t)}) + W^{hh} \cdot H^{(t-1)} + b_h\right)\\ Y^{(t)} &= W^{yh} \cdot H^{(t)} + b_y \end{align*}\]

Однако, для LSTM в другой статье ([9]) предлагается добавлять $\textrm{BN}$ существенно к большему количеству данных:

\[\begin{align*} g^{(t)} &= \phi\left(\textrm{BN}_{gx}(W^{gx} \cdot X^{(t)}) + \textrm{BN}_{gh}(W^{gh} \cdot H^{(t-1)}) + b_g\right)\\ i^{(t)} &= \sigma\left(\textrm{BN}_{ix}(W^{ix} \cdot X^{(t)}) + \textrm{BN}_{ih}(W^{ih} \cdot H^{(t-1)}) + b_i\right)\\ o^{(t)} &= \sigma\left(\textrm{BN}_{ox}(W^{ox} \cdot X^{(t)}) + \textrm{BN}_{oh}(W^{oh} \cdot H^{(t-1)}) + b_o\right)\\ f^{(t)} &= \sigma\left(\textrm{BN}_{fx}(W^{fx} \cdot X^{(t)}) + \textrm{BN}_{fh}(W^{fh} \cdot H^{(t-1)}) + b_f\right)\\ S^{(t)} &= g^{(t)} \odot i^{(t)} + S^{(t-1)} \odot f^{(t)}\\ H^{(t)} &= \phi\left(\textrm{BN}_{S}(S^{(t)})\right) \odot o^{(t)} \end{align*}\]

В данном случае надо заострить внимание на трех аспектах.

Во-первых, $\textrm{BN}$ применяется раздельно к проекциям $H^{(t-1)}$ и $X^{(t)}$ при этом параметры $\gamma$ и $\beta$, обучаемые в $\textrm{BN}$, естественно различные.

Во-вторых, параметры $\beta$ для всех преобразований кроме $\textrm{BN}_{S}$, выставляются равными нулю, роль $\beta$ в этом случае берут на себя bias-ы: $b_g$, $b_i$, $b_o$ и $b_f$.

И, наконец, третье. Во время тренировки среднее и дисперсия для $\textrm{BN}$ считаются по минибатчам, на протяжении тренировки эти параметры постоянно пересчитываются, и скользящее среднее сохрняется для использования на этапе применения сети. Авторы статьи [9] пишут, что необходимо сохранять отдельно среднее и дисперсию, для каждого шага $t$, а не общее усреднение для всех шагов. Т.е. для каждого преобразования $\textrm{BN}$ мы тренируем параметры $\gamma$ и $\beta$ единые для всех $t$, но для каждого $t$ сохраняем свои $E^{(t)}(\cdot)$ и $Var^{(t)}(\cdot)$, которые затем применяем при выводе сети.

Я пробовал, описанный в [9] вариант $\textrm{BN}$ на своих задачах, действительно он даёт лучший результат в сравнении с другими вариантами.

Encoder-Decoder

В статье [10] можно посмотреть на пример использования LSTM в encoder-decoder модели, которая решает задачу предсказания следующего кадра видео последовательности.

Замечание. Есть отличный модельный датасет для последовательности фреймов, называется Moving MNIST самое прекрасное в нём то, что данные можно, в принципе, генерировать “на лету”. Каждый элемент датасета это набор из 20 фреймов размера $64 \times 64$. Чтобы сгенерировать новый элемент, случайным образом выбираются два изображения цифр из классического датасета MNIST, эти изображения размещаются в случайной позиции на первом фрейме. Так же случайно, каждой из цифр приписывается вектор скорости. Следующий фрейм генерируется простым сдвигом изображения цифр, с использованием приписанных им векторов скорости. За пределы фрейма цифры улететь на могут, они “отскакивают” от краёв, меняя скорость как при абсолютно упругом ударе.

Задача может быть сформулирована, например, так: по первым 10 фреймам предсказать остальные.

Еncoder-decoder модель на последовательности схематично можно изобразить следующим образом:

LSTM encoder-decoder model

Вначале последовательность прогоняется через encoder часть рекуррентной сети, в результате получаем некое “сжатое” представление (в случае LSTM сжатое представление содержится в состоянии последней ячейке), затем это представление прогоняется через decoder часть и на выходе имеем новую последовательность, например, предсказанных фреймов.

LSTM Autoencoder

Чтобы научить сетку выделять “хорошее” представление последовательности, можно воспользоваться autoencoder моделью тренировки:

LSTM autoencoder model

Т.е. на выходе декодера должна получится таже последовательность фреймов/данных, которая подавалась на вход энкодера (только обычно в обратном порядке). Рассматриваются два варианта такой модели: условная - в этом случае после получения $X’_T$ с выхода первой ячейки декодера, этот фрейм подаётся на вход второй ячейки, и безусловная, когда декодеры передают на следующий шаг только своё внутреннее состояние (на схеме представлены оба варианта, но *условный” выделен пунктирными связями и меньшей яркостью).

Тренируя такую модель мы расчитываем, что представление, сгенерированное энкодером, будет содержать в себе информацию о подложке фреймов, а так же об объектах и их движении (это необходимо, чтобы декодер смог восстановить последовательность). Поскольку мы ограничиваем размер вектора состояния LSTM ячеек энкодера, есть надежда, что сеть научится генерировать нетривиальное представление набора фреймов. Однако, если использовать достаточно большой объем памяти под вектор состояния, то сеть может попытаться просто “запомнить” фреймы поданные на вход декодера, а не сгенерировать хорошее представление входной последовательности.

LSTM предсказатель

Второй вариант сети используется для предсказания фреймов последовательности по имеющимся:

LSTM feature predictor model

В данном случае мы опять имеем два варианта: условный и безусловный.

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

Композитная модель

Чтобы избежать минусов и autoencoder-а и предсказательной модели и научить энкодер генерировать хорошее представление последовательности, можно для тренировки объединить обе эти модели:

LSTM composite model

Такой подход с одной стороны не даёт энкодеру просто сохранять поступившие на вход фреймы, чтобы потом вернуть их в autoencoder, даже если состояние передаваемое между LSTM ячейками достаточно объёмно по памяти, потому что это не позволит предсказывать новые кадры, а значит не позволит нормально натренировать вторую часть сети. С другой стороны, не получится и сохранять информацию только о нескольких последних фреймах последовательности к чему склонна предсказательная модель. А значит есть надежда, что таким образом натренированный энкодер сумеет генерировать хорошее представление для последовательности.

Заключение

Подводя некий итог.

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

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

Для работы с картинками лучше использовать свёрточные варианты LSTM (это показывается, например, в [7]).

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

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


Литература

  1. Sepp Hochreiter , Jürgen Schmidhuber. “Long short-term memory”, Neural Computation (1997) 9 (8): 1735–1780. doi:neco.1997.9.8.1735

  2. Kyunghyun Cho, Bart van Merrienboer, Dzmitry Bahdanau, Yoshua Bengio. “On the Properties of Neural Machine Translation: Encoder-Decoder Approaches”, arXiv:1409.1259

  3. Junyoung Chung, Caglar Gulcehre, KyungHyun Cho, Yoshua Bengio, “Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling”, arXiv:1412.3555

  4. Zachary C. Lipton, John Berkowitz, Charles Elkan, “A Critical Review of Recurrent Neural Networks for Sequence Learning”, arXiv:1506.00019v4, 2015

  5. Guo-Bing Zhou, Jianxin Wu, Chen-Lin Zhang, Zhi-Hua Zhou, “Minimal Gated Unit for Recurrent Neural Networks”, arXiv:1603.09420

  6. Alex Graves, Greg Wayne, Ivo Danihelka, “Neural Turing Machines”, arXiv:1410.5401

  7. Xingjian Shi, Zhourong Chen, Hao Wang, Dit-Yan Yeung, Wai-kin Wong, Wang-chun Woo, “Convolutional LSTM Network: A Machine Learning Approach for Precipitation Nowcasting”, arXiv:1506.04214v2

  8. César Laurent, Gabriel Pereyra, Philémon Brakel, Ying Zhang, Yoshua Bengio, “Batch Normalized Recurrent Neural Networks”, arXiv:1510.01378

  9. Tim Cooijmans, Nicolas Ballas, César Laurent, Çağlar Gülçehre, Aaron Courville, “Recurrent Batch Normalization”, arXiv:1603.09025

  10. Nitish Srivastava, Elman Mansimov, Ruslan Salakhutdinov, “Unsupervised Learning of Video Representations using LSTMs”, arXiv:1502.04681