Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

9.3 Глубокие рекуррентные нейронные сети

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

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

На рис. 9.3.1 показана глубокая рекуррентная нейронная сеть с скрытыми слоями: каждое скрытое состояние непрерывно передаётся на следующий временной шаг текущего слоя и на текущий временной шаг следующего слоя.

Рис. 9.3.1. Структура глубокой рекуррентной нейронной сети

9.3.1 Функциональные зависимости

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

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

где весовые параметры , и параметр смещения относятся к -му скрытому слою.

Наконец, выходной слой вычисляется только на основе скрытого состояния последнего, -го, скрытого слоя:

где весовой параметр и параметр смещения относятся к выходному слою.

Как и в многослойном перцептроне, число скрытых слоёв и число скрытых элементов — гиперпараметры. Иными словами, их можно настраивать. Кроме того, если в (9.3.1) заменить скрытое состояние на состояние управляемого рекуррентного блока или LSTM, легко получить глубокую управляемую рекуррентную сеть или глубокую LSTM.

9.3.2 Краткая реализация

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

MXNet:

from d2l import mxnet as d2l
from mxnet import npx
from mxnet.gluon import rnn
npx.set_np()

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

PyTorch:

from d2l import torch as d2l
import torch
from torch import nn

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

PaddlePaddle:

from d2l import paddle as d2l
import warnings
warnings.filterwarnings("ignore")
import paddle
from paddle import nn

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

Выбор архитектурных параметров тоже очень похож на тот, что мы делали в разделе 9.2. Поскольку словарь тот же, входы и выходы выбираем одного размера, то есть vocab_size. Число скрытых элементов по-прежнему равно . Единственное отличие: теперь число скрытых слоёв задаётся значением num_layers.

MXNet:

vocab_size, num_hiddens, num_layers = len(vocab), 256, 2
device = d2l.try_gpu()
lstm_layer = rnn.LSTM(num_hiddens, num_layers)
model = d2l.RNNModel(lstm_layer, len(vocab))

PyTorch:

vocab_size, num_hiddens, num_layers = len(vocab), 256, 2
num_inputs = vocab_size
device = d2l.try_gpu()
lstm_layer = nn.LSTM(num_inputs, num_hiddens, num_layers)
model = d2l.RNNModel(lstm_layer, len(vocab))
model = model.to(device)

PaddlePaddle:

vocab_size, num_hiddens, num_layers = len(vocab), 256, 2
num_inputs = vocab_size
device = d2l.try_gpu()
lstm_layer = nn.LSTM(num_inputs, num_hiddens, num_layers, time_major=True)
model = d2l.RNNModel(lstm_layer, len(vocab))

9.3.3 Обучение и предсказание

Поскольку мы использовали модель LSTM с двумя слоями, обучение заметно замедлилось.

num_epochs, lr = 500, 2
d2l.train_ch8(model, train_iter, vocab, lr*1.0, num_epochs, device)

Резюме

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

Упражнения

  1. Опираясь на реализацию одного слоя, которую мы рассмотрели в разделе 8.5, попробуйте реализовать с нуля двухслойную рекуррентную нейронную сеть.
  2. В модели, обученной в этом разделе, сравните точность и скорость обучения при замене LSTM на управляемый рекуррентный блок.
  3. Насколько низко можно снизить перплексию, если увеличить объём обучающих данных?
  4. При моделировании текста можно ли объединять исходные данные разных авторов? Каковы плюсы и минусы такого подхода?

MXNet: Обсуждение

PyTorch: Обсуждение

PaddlePaddle: Обсуждение