.. _sec_rnn: Рекуррентные нейронные сети =========================== В разделе :numref:`sec_language-model` мы описали марковские модели и n-граммы :math:`n` для языкового моделирования, где условная вероятность токена :math:`x_t` на шаге времени :math:`t` зависит только от :math:`n-1` предыдущих токенов. Если мы хотим учесть возможное влияние токенов, предшествующих шагу времени :math:`t-(n-1)`, на :math:`x_t`, нам необходимо увеличить :math:`n`. Однако количество параметров модели также будет расти экспоненциально, поскольку нам потребуется хранить :math:`|\mathcal{V}|^n` чисел для словаря :math:`\mathcal{V}`. Следовательно, вместо моделирования :math:`P(x_t \mid x_{t-1}, \ldots, x_{t-n+1})` предпочтительнее использовать модель со скрытыми переменными. .. math:: P(x_t \mid x_{t-1}, \ldots, x_1) \approx P(x_t \mid h_{t-1}), где :math:`h_{t-1}` — это *скрытое состояние*, сохраняющее информацию о последовательности до шага времени :math:`t-1`. В общем случае скрытое состояние на любом шаге времени :math:`t` может быть вычислено на основе как текущего входа :math:`x_{t}`, так и предыдущего скрытого состояния :math:`h_{t-1}`: .. math:: h_t = f(x_{t}, h_{t-1}). :label: eq_ht_xt Для достаточно выразительной функции :math:`f` в :eq:`eq_ht_xt` модель латентных переменных не является приближением. В конце концов, :math:`h_t` может просто хранить все данные, которые она наблюдала до сих пор. Однако это может сделать вычисления и хранение ресурсоемкими. Напомним, что мы уже обсуждали скрытые слои со скрытыми нейронами в :numref:`chap_perceptrons`. Важно отметить, что скрытые слои и скрытые состояния — это два совершенно разных понятия. Скрытые слои, как уже объяснялось, — это слои, которые скрыты от наблюдателя на пути от входа к выходу. Скрытые состояния же технически являются *входами* для любых операций, выполняемых на данном шаге, и их можно вычислить, только обращаясь к данным предыдущих временных шагов. **Рекуррентные нейронные сети** (RNN) — это нейронные сети со скрытыми состояниями. Прежде чем перейти к модели RNN, мы ещё раз рассмотрим модель многослойного перцептрона (MLP), представленную в :numref:`sec_mlp`. .. raw:: latex \diilbookstyleinputcell .. code:: python import torch from d2l import torch as d2l Нейронные сети без скрытых состояний ------------------------------------ Рассмотрим многослойный перцептрон (MLP) с одним скрытым слоем. Пусть функцией активации скрытого слоя будет :math:`\phi`. Для мини-батча примеров :math:`\mathbf{X} \in \mathbb{R}^{n \times d}` размером батча :math:`n` и с :math:`d` входами выход скрытого слоя :math:`\mathbf{H} \in \mathbb{R}^{n \times h}` вычисляется как .. math:: \mathbf{H} = \phi(\mathbf{X} \mathbf{W}_{\textrm{xh}} + \mathbf{b}_\textrm{h}). :label: rnn_h_without_state В :eq:`rnn_h_without_state` указаны параметр весов :math:`\mathbf{W}_{\textrm{xh}} \in \mathbb{R}^{d \times h}`, параметр смещения :math:`\mathbf{b}_\textrm{h} \in \mathbb{R}^{1 \times h}` и количество скрытых единиц :math:`h` для скрытого слоя. Вооружившись этими данными, мы применяем транслирование (см. :numref:`subsec_broadcasting`) при суммировании. Затем выход скрытого слоя :math:`\mathbf{H}` используется в качестве входа выходного слоя, который задается формулой .. math:: \mathbf{O} = \mathbf{H} \mathbf{W}_{\textrm{hq}} + \mathbf{b}_\textrm{q}, где :math:`\mathbf{O} \in \mathbb{R}^{n \times q}` — выходная переменная, :math:`\mathbf{W}_{\textrm{hq}} \in \mathbb{R}^{h \times q}` — весовой параметр, а :math:`\mathbf{b}_\textrm{q} \in \mathbb{R}^{1 \times q}` — параметр смещения выходного слоя. Если это задача классификации, мы можем использовать :math:`\mathrm{softmax}(\mathbf{O})` для вычисления распределения вероятностей выходных категорий. Это полностью аналогично задаче регрессии, которую мы решали ранее в :numref:`sec_sequence`, поэтому мы опустим детали. Достаточно сказать, что мы можем случайным образом выбирать пары «признак–метка» и обучать параметры нашей сети с помощью автоматического дифференцирования и стохастического градиентного спуска. .. _subsec_rnn_w_hidden_states: Рекуррентные нейронные сети со скрытыми состояниями --------------------------------------------------- Совершенно иная картина складывается, когда у нас есть скрытые состояния. Давайте рассмотрим структуру более подробно. Предположим, что у нас есть мини-батч входов :math:`\mathbf{X}_t \in \mathbb{R}^{n \times d}` на шаге времени :math:`t`. Иными словами, для мини-батча из :math:`n` примеров последовательности каждая строка :math:`\mathbf{X}_t` соответствует одному примеру на шаге времени :math:`t`. Далее обозначим через :math:`\mathbf{H}_t \in \mathbb{R}^{n \times h}` выход скрытого слоя на шаге времени :math:`t`. В отличие от многослойного перцептрона, здесь мы сохраняем выход скрытого слоя :math:`\mathbf{H}_{t-1}` с предыдущего шага времени и вводим новый параметр веса :math:`\mathbf{W}_{\textrm{hh}} \in \mathbb{R}^{h \times h}`, описывающий, как использовать выход скрытого слоя предыдущего шага времени на текущем шаге. В частности, вычисление выхода скрытого слоя текущего шага времени определяется входом текущего шага времени и выходом скрытого слоя предыдущего шага времени: .. math:: \mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{\textrm{xh}} + \mathbf{H}_{t-1} \mathbf{W}_{\textrm{hh}} + \mathbf{b}_\textrm{h}). :label: rnn_h_with_state В отличие от :eq:`rnn_h_without_state`, в :eq:`rnn_h_with_state` добавлен ещё один член :math:`\mathbf{H}_{t-1} \mathbf{W}_{\textrm{hh}}`, что реализует :eq:`eq_ht_xt`. Исходя из взаимосвязи между выходами скрытого слоя :math:`\mathbf{H}_t` и :math:`\mathbf{H}_{t-1}` в соседних временных шагах, мы видим, что эти переменные захватывают и сохраняют историческую информацию последовательности вплоть до текущего момента, подобно состоянию или памяти нейронной сети на данном временном шаге. Поэтому такой выход скрытого слоя называется *скрытым состоянием*. Поскольку на текущем временном шаге скрытое состояние использует то же самое определение, что и на предыдущем, вычисление :eq:`rnn_h_with_state` является *рекуррентным*. Следовательно, как мы уже отмечали, нейронные сети, использующие скрытые состояния на основе рекуррентных вычислений, называются *рекуррентными нейронными сетями*. Слои, выполняющие вычисления :eq:`rnn_h_with_state` в RNN, называются *рекуррентными слоями*. Существует множество различных способов построения RNN. Наиболее распространены те, у которых скрытое состояние определяется выражением :eq:`rnn_h_with_state`. Для шага времени :math:`t` выход выходного слоя аналогичен вычислениям в многослойном перцептроне (MLP): .. math:: \mathbf{O}_t = \mathbf{H}_t \mathbf{W}_{\textrm{hq}} + \mathbf{b}_\textrm{q}. Параметры RNN включают веса :math:`\mathbf{W}_{\textrm{xh}} \in \mathbb{R}^{d \times h}, \mathbf{W}_{\textrm{hh}} \in \mathbb{R}^{h \times h}` и смещение :math:`\mathbf{b}_\textrm{h} \in \mathbb{R}^{1 \times h}` скрытого слоя, а также веса :math:`\mathbf{W}_{\textrm{hq}} \in \mathbb{R}^{h \times q}` и смещение :math:`\mathbf{b}_\textrm{q} \in \mathbb{R}^{1 \times q}` выходного слоя. Следует отметить, что даже на разных временных шагах RNN всегда использует эти параметры модели. Поэтому сложность параметризации RNN не растет с увеличением количества временных шагов. :numref:`fig_rnn` иллюстрирует вычислительную логику рекуррентной нейронной сети на трёх соседних временных шагах. В любой момент времени :math:`t` вычисление скрытого состояния можно рассматривать как: (i) конкатенацию входных данных :math:`\mathbf{X}_t` текущего временного шага :math:`t` и скрытого состояния :math:`\mathbf{H}_{t-1}` предыдущего временного шага :math:`t-1`; (ii) подачу результата конкатенации в полносвязный слой с функцией активации :math:`\phi`. Выходом такого полносвязного слоя является скрытое состояние :math:`\mathbf{H}_t` текущего временного шага :math:`t`. При этом параметрами модели являются конкатенация :math:`\mathbf{W}_{\textrm{xh}}` и :math:`\mathbf{W}_{\textrm{hh}}`, а также смещение :math:`\mathbf{b}_\textrm{h}`, все из :eq:`rnn_h_with_state`. Скрытое состояние текущего временного шага :math:`t`, :math:`\mathbf{H}_t`, участвует в вычислении скрытого состояния :math:`\mathbf{H}_{t+1}` следующего временного шага :math:`t+1`. Кроме того, :math:`\mathbf{H}_t` также подаётся в полносвязный выходной слой для вычисления выхода :math:`\mathbf{O}_t` текущего временного шага :math:`t`. .. _fig_rnn: .. figure:: ../img/rnn.svg Рекуррентная нейронная сеть со скрытым состоянием. Мы уже упоминали, что вычисление :math:`\mathbf{X}_t \mathbf{W}_{\textrm{xh}} + \mathbf{H}_{t-1} \mathbf{W}_{\textrm{hh}}` для скрытого состояния эквивалентно матричному умножению конкатенации :math:`\mathbf{X}_t` и :math:`\mathbf{H}_{t-1}` на конкатенацию :math:`\mathbf{W}_{\textrm{xh}}` и :math:`\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). .. raw:: latex \diilbookstyleinputcell .. code:: python 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), что и выше. .. raw:: latex \diilbookstyleinputcell .. code:: python torch.matmul(torch.cat((X, H), 1), torch.cat((W_xh, W_hh), 0)) Языковые модели на уровне символов на основе RNN ------------------------------------------------ Вспомним, что в контексте языковой модели в :numref:`sec_language-model` мы стремимся предсказать следующий токен на основе текущего и предыдущих токенов; поэтому исходная последовательность сдвигается на один токен, чтобы получить целевые значения (метки). :cite:t:`Bengio.Ducharme.Vincent.ea.2003` впервые предложил использовать нейронную сеть для языковой модели. Далее мы покажем, как рекуррентные нейронные сети (RNN) могут быть использованы для построения языковой модели. Пусть размер мини-батча равен единице, а последовательность текста — «machine». Чтобы упростить обучение в последующих разделах, мы будем токенизировать текст по символам, а не по словам, и рассмотрим *символьную языковую модель*. :numref:`fig_rnn_train` демонстрирует, как предсказывать следующий символ на основе текущего и предыдущих символов с помощью RNN для символьного языкового моделирования. .. _fig_rnn_train: .. figure:: ../img/rnn-train.svg Языковая модель на уровне символов, основанная на RNN. Входная и целевая последовательности — «machin» и «achine» соответственно. В процессе обучения мы применяем операцию софтмакс к выходу выходного слоя на каждом временном шаге, а затем используем перекрёстную энтропию для вычисления ошибки между выходом модели и целевой переменной. В результате рекуррентных вычислений скрытого состояния в скрытом слое выход :math:`\mathbf{O}_3` временного шага 3 в :numref:`fig_rnn_train` определяется текстовой последовательностью «m», «a» и «c». Поскольку следующим символом в последовательности обучающих данных является «h», потери на временном шаге 3 будут зависеть от распределения вероятностей следующего символа, сгенерированного на основе последовательности признаков «m», «a», «c», и целевой переменной «h» этого временного шага. На практике каждый токен представляется :math:`d`-мерным вектором, а размер батча равен :math:`n>1`. Следовательно, вход :math:`\mathbf X_t` на шаге времени :math:`t` будет представлять собой матрицу размера :math:`n\times d`, что соответствует обсуждавшемуся в :numref:`subsec_rnn_w_hidden_states`. В следующих разделах мы реализуем RNN для языковых моделей на уровне символов. Итоги ----- Нейронная сеть, использующая рекуррентные вычисления для скрытых состояний, называется рекуррентной нейронной сетью (RNN). Скрытое состояние RNN может сохранять историческую информацию о последовательности вплоть до текущего временного шага. Благодаря рекуррентным вычислениям количество параметров модели RNN не растет с увеличением числа временных шагов. Что касается применений, RNN можно использовать для создания языковых моделей на уровне символов. Упражнения ---------- 1. Если мы используем RNN для предсказания следующего символа в текстовой последовательности, какой должна быть размерность любого вывода? 2. Почему рекуррентные нейронные сети способны моделировать условную вероятность токена на заданном временном шаге при условии всех предыдущих токенов в текстовой последовательности? 3. Что происходит с градиентом, если выполнять обратное распространение ошибки по длинной последовательности? 4. Какие проблемы связаны с языковой моделью, описанной в этом разделе? `Обсуждения `__