Глава 1.4 — Рекуррентные сети как языковые модели

Содержание

  1. Как исправить фиксированное окно: идея рекуррентности
  2. Обычные RNN и почему их трудно обучать
  3. LSTM: гейтинг как средство против затухающих градиентов
  4. GRU: более простой гейт, схожие результаты
  5. Модели seq2seq и узкое место кодировщика-декодировщика
  6. Взгляд с точки зрения собеседования
  7. Вопросы для самопроверки
  8. Источники

1. Как исправить фиксированное окно: идея рекуррентности

У всех моделей из двух предыдущих глав есть общее структурное ограничение: они обусловливаются на окне фиксированного размера из предыдущих токенов — будь то последние \(n-1\) слов в таблице n-грамм или фиксированные входные слоты в прямой сети Бенджио. Увеличение окна немного помогает, но не масштабируется — всё равно приходится выбирать жёсткую границу отсечения, и всё, что было до неё, невидимо для модели, сколь бы релевантным оно ни было.

Рекуррентные нейронные сети (recurrent neural networks, RNN) устраняют фиксированное окно, вводя скрытое состояние, которое обновляется на каждом временном шаге и переносится вперёд неограниченно долго. На каждой позиции \(i\) модель считывает текущий входной токен и объединяет его со скрытым состоянием, суммирующим всё, что было до этого, порождая новое скрытое состояние:

$$h_i = f(h_{i-1}, x_i)$$

Принципиально важно, что \(h_i\) — это вектор фиксированного размера, но при этом функция от всей последовательности до этого момента, а не только от нескольких последних токенов, — в принципе, информация с позиции 1 может по-прежнему влиять на скрытое состояние на позиции 1000. Это ключевой концептуальный скачок этой главы: вместо усечения контекста (n-граммы) или обработки фиксированного окна целиком за раз (модель Бенджио) RNN сжимает произвольно длинную историю в итоговый вектор фиксированного размера, обновляемый инкрементально, токен за токеном. Предсказание следующего токена, в точности как в главе 1.1, становится функцией этого скрытого состояния: \(P(x_{i+1} \mid x_1, \ldots, x_i) \approx g(h_i)\).

2. Обычные RNN и почему их трудно обучать

Простейшая версия функции \(f\) выше (иногда называемая сетью Элмана — по имени Джеффри Элмана (Jeffrey Elman), сформулировавшего эту идею для моделирования последовательностей в 1990 году) — это единственный слой, объединяющий предыдущее скрытое состояние и текущий вход через матрицу весов и нелинейность. Это элегантно и, в принципе, именно то, что требуется. На практике же это упирается в серьёзную проблему обучения.

Обучение RNN означает обратное распространение ошибки с некоторого позднего временного шага назад через каждый предыдущий временной шаг — процедуру, называемую обратным распространением ошибки во времени (backpropagation through time). Поскольку одна и та же матрица весов применяется многократно, по разу на каждом временном шаге, градиент на шаге \(i\) зависит от произведения множества копий этой матрицы (и производных нелинейности, применённой на каждом шаге):

$$\frac{\partial h_t}{\partial h_k} = \prod_{j=k+1}^{t} \frac{\partial h_j}{\partial h_{j-1}} = \prod_{j=k+1}^{t} W^\top \operatorname{diag}\bigl(f'(h_{j-1})\bigr)$$

Если соответствующие значения стабильно меньше единицы, это произведение экспоненциально быстро стремится к нулю по мере углубления назад — это проблема затухающего градиента (vanishing gradient problem). Если они стабильно больше единицы, произведение вместо этого экспоненциально растёт — это взрывающийся градиент (exploding gradients). На практике обычные RNN страдают преимущественно от затухания: градиенты от ошибок, возникших на много шагов вперёд, по сути не доходят до тех частей сети, которые отвечали за информацию на много шагов назад, и не обновляют их.

Практическое следствие — это ровно то свойство, которое рекуррентность и должна была обеспечить: моделирование дальних зависимостей. Обычная RNN в теории способна помнить что-то со 100 шагов назад, но на практике проблема затухающего градиента означает, что обучить её действительно это делать очень трудно, потому что обучающий сигнал, который научил бы её сохранять эту информацию, никогда надёжно не доходит. Взрывающиеся градиенты — более разрешимая из двух проблем: их обычно можно контролировать с помощью обрезки градиента (gradient clipping), ограничивающей норму градиента во время обучения, — но затухающие градиенты потребовали более фундаментального архитектурного решения.

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

Ещё одно ограничение стоит назвать отдельно от проблемы градиентов: обычная RNN, обрабатывающая последовательность слева направо, на любой позиции имеет доступ только к прошлому контексту, но никогда — к будущему. Двунаправленные RNN (bidirectional RNN) решают это, запуская две отдельные RNN по одной и той же последовательности — одну слева направо, другую справа налево, — и конкатенируя их скрытые состояния на каждой позиции, так что представление каждой позиции включает контекст из обоих направлений сразу. Это имеет смысл только тогда, когда весь вход уже доступен ещё до начала обработки, — а именно в такой ситуации находится кодировщик seq2seq из главы 1.5: его задача — обобщить полную, уже целиком известную входную последовательность, так что нет причин скрывать от него будущий контекст так, как приходится скрывать его от настоящего левого-направо генератора, производящего вывод по одному шагу за раз.

3. LSTM: гейтинг как средство против затухающих градиентов

Сети долгой краткосрочной памяти (Long Short-Term Memory, LSTM), предложенные Зеппом Хохрайтером (Sepp Hochreiter) и Юргеном Шмидхубером (Jürgen Schmidhuber) в 1997 году, решают проблему затухающего градиента архитектурным изменением, специально спроектированным так, чтобы позволить градиентам течь назад через множество временных шагов практически без препятствий.

Ключевая идея — поддерживать отдельное состояние ячейки (cell state) наряду со скрытым состоянием, которое обновляется преимущественно через сложение, а не через повторное умножение на матрицу весов. Три выученных гейта (gates) управляют этим состоянием ячейки на каждом шаге: гейт забывания (forget gate) решает, какую долю существующего состояния ячейки сохранить, входной гейт (input gate) решает, какую новую информацию записать, а выходной гейт (output gate) решает, какую часть (обновлённого) состояния ячейки предоставить в качестве скрытого состояния, используемого для предсказания. Каждый гейт сам по себе — небольшая нейронная сеть (линейный слой с сигмоидной активацией), выдающая значения от 0 до 1 и действующая как мягкий, обучаемый переключатель.

Почему это исправляет затухающие градиенты? Потому что обновление состояния ячейки определяется в основном аддитивным членом — грубо говоря, «сохрани часть старого состояния ячейки и добавь немного новой информации», — а не повторяющимся на каждом шаге матричным умножением:

$$c_t = f_t \odot c_{t-1} + i_t \odot \tilde c_t, \qquad f_t = \sigma(W_f[h_{t-1}, x_t] + b_f)$$

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

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

4. GRU: более простой гейт, схожие результаты

Управляемые рекуррентные блоки (Gated Recurrent Units, GRU), предложенные Кёнхюном Чо (Kyunghyun Cho) и коллегами в 2014 году (в той же статье, что ввела рассматриваемую ниже структуру кодировщик-декодировщик RNN) и далее популяризированные эмпирическим сравнением Чунён Чуна (Junyoung Chung) с коллегами позднее в том же году, упрощают трёхгейтовую конструкцию LSTM до двух гейтов — обновляющего (update gate) и сбрасывающего (reset gate) — и отказываются от отдельного состояния ячейки, сворачивая всё в единое скрытое состояние:

$$z_t = \sigma(W_z[h_{t-1}, x_t]), \quad r_t = \sigma(W_r[h_{t-1}, x_t]), \quad h_t = (1 - z_t)\odot h_{t-1} + z_t \odot \tilde h_t$$

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

5. Модели seq2seq и узкое место кодировщика-декодировщика

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

Статья Ильи Суцкевера (Ilya Sutskever), Ориола Виньялса (Oriol Vinyals) и Куока Ле (Quoc Le) 2014 года о моделях «последовательность-в-последовательность» (sequence-to-sequence, seq2seq) решила эту задачу элегантной конструкцией из двух сетей: RNN-кодировщик (encoder) считывает всю входную последовательность (скажем, предложение на французском) токен за токеном и порождает финальное скрытое состояние, суммирующее всё предложение; этот единственный вектор фиксированного размера затем передаётся отдельному RNN-декодировщику (decoder), который генерирует выходную последовательность (перевод на английский) токен за токеном, обусловливая каждое предсказание на суммирующем векторе кодировщика и на всём, что он уже сгенерировал.

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

6. Взгляд с точки зрения собеседования

RNN постоянно всплывают на собеседованиях — и как история, и как способ проверить, понимаете ли вы, почему от них в итоге отказались для большинства крупномасштабных задач языкового моделирования:

  • «Почему обычные RNN плохо справляются с длинными последовательностями?» — ожидаемый ответ конкретно про затухающие градиенты при обратном распространении ошибки во времени, а не расплывчатое «они забывают вещи».
  • «Как LSTM решает проблему затухающего градиента, механически?» — сильный ответ называет аддитивный путь обновления состояния ячейки и гейты забывания/входа/выхода и объясняет, почему аддитивные обновления лучше сохраняют величину градиента, чем повторяющиеся мультипликативные обновления.
  • «Что такое узкое место кодировщика-декодировщика и как его в итоге решили?» — это часто используется как мостик к вниманию и трансформерам; ожидаемый ответ определяет единственный вектор фиксированного размера как узкое место и называет внимание в качестве решения.
  • «Почему область перешла от RNN к трансформерам, если RNN в принципе способны моделировать сколь угодно длинные зависимости?» — проверяет, понимаете ли вы, что проблема была не только в емкости моделирования, но и в параллелизме: RNN обрабатывают токены последовательно, по одному, что медленно при обучении в большом масштабе — тема, явно поднимаемая в главе 2.1.

7. Вопросы для самопроверки

  1. Запишите общее рекуррентное соотношение для обновления скрытого состояния RNN и объясните, какое свойство оно даёт, недоступное моделям с фиксированным окном (n-граммы, прямая языковая модель Бенджио).
  2. Объясните своими словами проблему затухающего градиента: что многократно перемножается во время обратного распространения ошибки во времени и почему это приводит к уменьшению дальних градиентов?
  3. Опишите три гейта в LSTM и то, что каждый из них контролирует. Какая часть конструкции LSTM конкретно решает проблему затухающих градиентов и почему?
  4. Чем GRU структурно отличается от LSTM и что мотивировало это упрощение?
  5. Опишите архитектуру кодировщика-декодировщика seq2seq и точно объясните, где находится «узкое место»: что конкретно должно произойти для очень длинного входного предложения, чего не должно происходить для короткого?
  6. Почему решение узкого места кодировщика-декодировщика, а не просто построение более глубоких или крупных RNN, оказалось более продуктивным направлением для развития области? (Это должно подготовить вас к чтению главы 1.5.)

8. Источники

  • Elman, J. L. (1990). Finding Structure in Time. Cognitive Science, 14(2), 179–211. gwern.net PDF
  • Hochreiter, S., & Schmidhuber, J. (1997). Long Short-Term Memory. Neural Computation, 9(8), 1735–1780. MIT Press
  • Cho, K., van Merriënboer, B., Gulcehre, C., Bahdanau, D., Bougares, F., Schwenk, H., & Bengio, Y. (2014). Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation. arXiv:1406.1078
  • Sutskever, I., Vinyals, O., & Le, Q. V. (2014). Sequence to Sequence Learning with Neural Networks. arXiv:1409.3215
  • Chung, J., Gulcehre, C., Cho, K., & Bengio, Y. (2014). Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling. arXiv:1412.3555

Тест для самопроверки

Проверьте понимание главы с помощью короткого теста — вопросы по теории и небольшие расчёты.

Чем в RNN скрытое состояние $h_i$ отличается от фиксированного контекста, используемого моделями n-грамм или прямой сетью Бенджио?

Объяснение: h_i = f(h_{i-1}, x_i) сжимает сколь угодно длинную историю в вектор фиксированного размера, обновляемый по одному токену за раз — в отличие от жёсткого отсечения по окну.

Что вызывает проблему затухающего градиента в обычных RNN при обратном распространении ошибки во времени?

Объяснение: Поскольку одна и та же матрица весов применяется на каждом шаге, градиент включает произведение множества её копий — если соответствующие значения стабильно меньше 1, это произведение экспоненциально уменьшается по мере углубления назад во времени.

Механически, почему состояние ячейки LSTM помогает градиентам сохраняться на протяжении многих временных шагов, в отличие от скрытого состояния обычной RNN?

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

В чём заключается узкое место энкодера-декодера в seq2seq-модели на RNN?

Объяснение: Для длинных предложений детали из начала входа должны «выжить», будучи сжатыми через тот же вектор фиксированного размера, что и всё остальное — именно эту проблему призвано решить внимание (глава 1.5).

Пусть обратно распространяемый градиент обычной RNN уменьшается в 0.9 раза на каждом временном шаге. Какая доля исходной величины градиента останется после 50 шагов? (Округлите до 4 знаков после запятой.)

Объяснение: 0.9^50 ≈ 0.00515 — градиент уменьшился примерно до половины процента от исходного размера, что наглядно иллюстрирует проблему затухающего градиента.

Сколько всего обучаемых гейтов управляет состоянием ячейки в LSTM (забывания, входной и выходной вместе)?

Объяснение: Гейт забывания, входной гейт и выходной гейт — три гейта в сумме.

GRU упрощает трёхгейтовую конструкцию LSTM до скольких гейтов?

Объяснение: GRU использует гейт обновления и гейт сброса — два гейта — и полностью отказывается от отдельного состояния ячейки.

Пусть у другой RNN обратно распространяемый градиент растёт в 1.5 раза на каждом временном шаге (взрывающийся градиент). Во сколько раз вырастет величина градиента относительно исходной после 20 шагов? (Округлите до целого числа.)

Объяснение: 1.5^20 ≈ 3325 — градиент вырос более чем в три тысячи раз, что иллюстрирует, почему взрывающиеся градиенты нужно контролировать (например, обрезкой градиента).

Почему при обучении на длинных последовательностях используется усечённый BPTT вместо полного обратного распространения во времени?

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

Когда уместно использовать двунаправленную RNN вместо однонаправленной (слева направо)?

Объяснение: Двунаправленная RNN запускает одну RNN слева направо, а другую справа налево по одной и той же последовательности, конкатенируя их скрытые состояния так, что каждая позиция видит контекст в обоих направлениях — это имеет смысл только тогда, когда весь вход уже известен, как у энкодера seq2seq, а не у настоящего пошагового генератора.