9.4. Рекуррентные нейронные сети

В разделе Section 9.3 мы описали марковские модели и n-граммы \(n\) для языкового моделирования, где условная вероятность токена \(x_t\) на шаге времени \(t\) зависит только от \(n-1\) предыдущих токенов. Если мы хотим учесть возможное влияние токенов, предшествующих шагу времени \(t-(n-1)\), на \(x_t\), нам необходимо увеличить \(n\). Однако количество параметров модели также будет расти экспоненциально, поскольку нам потребуется хранить \(|\mathcal{V}|^n\) чисел для словаря \(\mathcal{V}\). Следовательно, вместо моделирования \(P(x_t \mid x_{t-1}, \ldots, x_{t-n+1})\) предпочтительнее использовать модель со скрытыми переменными.

(9.4.1)\[P(x_t \mid x_{t-1}, \ldots, x_1) \approx P(x_t \mid h_{t-1}),\]

где \(h_{t-1}\) — это скрытое состояние, сохраняющее информацию о последовательности до шага времени \(t-1\). В общем случае скрытое состояние на любом шаге времени \(t\) может быть вычислено на основе как текущего входа \(x_{t}\), так и предыдущего скрытого состояния \(h_{t-1}\):

(9.4.2)\[h_t = f(x_{t}, h_{t-1}).\]

Для достаточно выразительной функции \(f\) в (9.4.2) модель латентных переменных не является приближением. В конце концов, \(h_t\) может просто хранить все данные, которые она наблюдала до сих пор. Однако это может сделать вычисления и хранение ресурсоемкими.

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

Рекуррентные нейронные сети (RNN) — это нейронные сети со скрытыми состояниями. Прежде чем перейти к модели RNN, мы ещё раз рассмотрим модель многослойного перцептрона (MLP), представленную в Section 5.1.

import torch
from d2l import torch as d2l

9.4.1. Нейронные сети без скрытых состояний

Рассмотрим многослойный перцептрон (MLP) с одним скрытым слоем. Пусть функцией активации скрытого слоя будет \(\phi\). Для мини-батча примеров \(\mathbf{X} \in \mathbb{R}^{n \times d}\) размером батча \(n\) и с \(d\) входами выход скрытого слоя \(\mathbf{H} \in \mathbb{R}^{n \times h}\) вычисляется как

(9.4.3)\[\mathbf{H} = \phi(\mathbf{X} \mathbf{W}_{\textrm{xh}} + \mathbf{b}_\textrm{h}).\]

В (9.4.3) указаны параметр весов \(\mathbf{W}_{\textrm{xh}} \in \mathbb{R}^{d \times h}\), параметр смещения \(\mathbf{b}_\textrm{h} \in \mathbb{R}^{1 \times h}\) и количество скрытых единиц \(h\) для скрытого слоя. Вооружившись этими данными, мы применяем транслирование (см. Section 2.1.4) при суммировании. Затем выход скрытого слоя \(\mathbf{H}\) используется в качестве входа выходного слоя, который задается формулой

(9.4.4)\[\mathbf{O} = \mathbf{H} \mathbf{W}_{\textrm{hq}} + \mathbf{b}_\textrm{q},\]

где \(\mathbf{O} \in \mathbb{R}^{n \times q}\) — выходная переменная, \(\mathbf{W}_{\textrm{hq}} \in \mathbb{R}^{h \times q}\) — весовой параметр, а \(\mathbf{b}_\textrm{q} \in \mathbb{R}^{1 \times q}\) — параметр смещения выходного слоя. Если это задача классификации, мы можем использовать \(\mathrm{softmax}(\mathbf{O})\) для вычисления распределения вероятностей выходных категорий.

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

9.4.2. Рекуррентные нейронные сети со скрытыми состояниями

Совершенно иная картина складывается, когда у нас есть скрытые состояния. Давайте рассмотрим структуру более подробно.

Предположим, что у нас есть мини-батч входов \(\mathbf{X}_t \in \mathbb{R}^{n \times d}\) на шаге времени \(t\). Иными словами, для мини-батча из \(n\) примеров последовательности каждая строка \(\mathbf{X}_t\) соответствует одному примеру на шаге времени \(t\). Далее обозначим через \(\mathbf{H}_t \in \mathbb{R}^{n \times h}\) выход скрытого слоя на шаге времени \(t\). В отличие от многослойного перцептрона, здесь мы сохраняем выход скрытого слоя \(\mathbf{H}_{t-1}\) с предыдущего шага времени и вводим новый параметр веса \(\mathbf{W}_{\textrm{hh}} \in \mathbb{R}^{h \times h}\), описывающий, как использовать выход скрытого слоя предыдущего шага времени на текущем шаге. В частности, вычисление выхода скрытого слоя текущего шага времени определяется входом текущего шага времени и выходом скрытого слоя предыдущего шага времени:

(9.4.5)\[\mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{\textrm{xh}} + \mathbf{H}_{t-1} \mathbf{W}_{\textrm{hh}} + \mathbf{b}_\textrm{h}).\]

В отличие от (9.4.3), в (9.4.5) добавлен ещё один член \(\mathbf{H}_{t-1} \mathbf{W}_{\textrm{hh}}\), что реализует (9.4.2). Исходя из взаимосвязи между выходами скрытого слоя \(\mathbf{H}_t\) и \(\mathbf{H}_{t-1}\) в соседних временных шагах, мы видим, что эти переменные захватывают и сохраняют историческую информацию последовательности вплоть до текущего момента, подобно состоянию или памяти нейронной сети на данном временном шаге. Поэтому такой выход скрытого слоя называется скрытым состоянием. Поскольку на текущем временном шаге скрытое состояние использует то же самое определение, что и на предыдущем, вычисление (9.4.5) является рекуррентным. Следовательно, как мы уже отмечали, нейронные сети, использующие скрытые состояния на основе рекуррентных вычислений, называются рекуррентными нейронными сетями. Слои, выполняющие вычисления (9.4.5) в RNN, называются рекуррентными слоями.

Существует множество различных способов построения RNN. Наиболее распространены те, у которых скрытое состояние определяется выражением (9.4.5). Для шага времени \(t\) выход выходного слоя аналогичен вычислениям в многослойном перцептроне (MLP):

(9.4.6)\[\mathbf{O}_t = \mathbf{H}_t \mathbf{W}_{\textrm{hq}} + \mathbf{b}_\textrm{q}.\]

Параметры RNN включают веса \(\mathbf{W}_{\textrm{xh}} \in \mathbb{R}^{d \times h}, \mathbf{W}_{\textrm{hh}} \in \mathbb{R}^{h \times h}\) и смещение \(\mathbf{b}_\textrm{h} \in \mathbb{R}^{1 \times h}\) скрытого слоя, а также веса \(\mathbf{W}_{\textrm{hq}} \in \mathbb{R}^{h \times q}\) и смещение \(\mathbf{b}_\textrm{q} \in \mathbb{R}^{1 \times q}\) выходного слоя. Следует отметить, что даже на разных временных шагах RNN всегда использует эти параметры модели. Поэтому сложность параметризации RNN не растет с увеличением количества временных шагов.

Fig. 9.4.1 иллюстрирует вычислительную логику рекуррентной нейронной сети на трёх соседних временных шагах. В любой момент времени \(t\) вычисление скрытого состояния можно рассматривать как: (i) конкатенацию входных данных \(\mathbf{X}_t\) текущего временного шага \(t\) и скрытого состояния \(\mathbf{H}_{t-1}\) предыдущего временного шага \(t-1\); (ii) подачу результата конкатенации в полносвязный слой с функцией активации \(\phi\). Выходом такого полносвязного слоя является скрытое состояние \(\mathbf{H}_t\) текущего временного шага \(t\). При этом параметрами модели являются конкатенация \(\mathbf{W}_{\textrm{xh}}\) и \(\mathbf{W}_{\textrm{hh}}\), а также смещение \(\mathbf{b}_\textrm{h}\), все из (9.4.5). Скрытое состояние текущего временного шага \(t\), \(\mathbf{H}_t\), участвует в вычислении скрытого состояния \(\mathbf{H}_{t+1}\) следующего временного шага \(t+1\). Кроме того, \(\mathbf{H}_t\) также подаётся в полносвязный выходной слой для вычисления выхода \(\mathbf{O}_t\) текущего временного шага \(t\).

../_images/rnn.svg

Fig. 9.4.1 Рекуррентная нейронная сеть со скрытым состоянием.

Мы уже упоминали, что вычисление \(\mathbf{X}_t \mathbf{W}_{\textrm{xh}} + \mathbf{H}_{t-1} \mathbf{W}_{\textrm{hh}}\) для скрытого состояния эквивалентно матричному умножению конкатенации \(\mathbf{X}_t\) и \(\mathbf{H}_{t-1}\) на конкатенацию \(\mathbf{W}_{\textrm{xh}}\) и \(\mathbf{W}_{\textrm{hh}}\). Хотя это можно доказать математически, далее мы просто продемонстрируем это с помощью небольшого фрагмента кода. Сначала определим матрицы X, W_xh, H и W_hh, формы которых равны (3, 1), (1, 4), (3, 4) и (4, 4) соответственно. Перемножив X на W_xh, а H на W_hh, и сложив полученные произведения, мы получим матрицу формы (3, 4).

X, W_xh = torch.randn(3, 1), torch.randn(1, 4)
H, W_hh = torch.randn(3, 4), torch.randn(4, 4)
torch.matmul(X, W_xh) + torch.matmul(H, W_hh)

Теперь мы конкатенируем матрицы X и H по столбцам (ось 1), а матрицы W_xh и W_hh — по строкам (ось 0). В результате этих конкатенаций получаются матрицы формы (3, 5) и формы (5, 4) соответственно. Перемножая эти конкатенированные матрицы, мы получаем ту же выходную матрицу формы (3, 4), что и выше.

torch.matmul(torch.cat((X, H), 1), torch.cat((W_xh, W_hh), 0))

9.4.3. Языковые модели на уровне символов на основе RNN

Вспомним, что в контексте языковой модели в Section 9.3 мы стремимся предсказать следующий токен на основе текущего и предыдущих токенов; поэтому исходная последовательность сдвигается на один токен, чтобы получить целевые значения (метки). Bengio et al. (2003) впервые предложил использовать нейронную сеть для языковой модели. Далее мы покажем, как рекуррентные нейронные сети (RNN) могут быть использованы для построения языковой модели. Пусть размер мини-батча равен единице, а последовательность текста — «machine». Чтобы упростить обучение в последующих разделах, мы будем токенизировать текст по символам, а не по словам, и рассмотрим символьную языковую модель. Fig. 9.4.2 демонстрирует, как предсказывать следующий символ на основе текущего и предыдущих символов с помощью RNN для символьного языкового моделирования.

../_images/rnn-train.svg

Fig. 9.4.2 Языковая модель на уровне символов, основанная на RNN. Входная и целевая последовательности — «machin» и «achine» соответственно.

В процессе обучения мы применяем операцию софтмакс к выходу выходного слоя на каждом временном шаге, а затем используем перекрёстную энтропию для вычисления ошибки между выходом модели и целевой переменной. В результате рекуррентных вычислений скрытого состояния в скрытом слое выход \(\mathbf{O}_3\) временного шага 3 в Fig. 9.4.2 определяется текстовой последовательностью «m», «a» и «c». Поскольку следующим символом в последовательности обучающих данных является «h», потери на временном шаге 3 будут зависеть от распределения вероятностей следующего символа, сгенерированного на основе последовательности признаков «m», «a», «c», и целевой переменной «h» этого временного шага.

На практике каждый токен представляется \(d\)-мерным вектором, а размер батча равен \(n>1\). Следовательно, вход \(\mathbf X_t\) на шаге времени \(t\) будет представлять собой матрицу размера \(n\times d\), что соответствует обсуждавшемуся в Section 9.4.2.

В следующих разделах мы реализуем RNN для языковых моделей на уровне символов.

9.4.4. Итоги

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

9.4.5. Упражнения

  1. Если мы используем RNN для предсказания следующего символа в текстовой последовательности, какой должна быть размерность любого вывода?

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

  3. Что происходит с градиентом, если выполнять обратное распространение ошибки по длинной последовательности?

  4. Какие проблемы связаны с языковой моделью, описанной в этом разделе?

Обсуждения