Рекуррентные сети (RNN и LSTM)

Урок объясняет, как сети помнят прошлое при обработке последовательностей.

Рекуррентная сеть (RNN) обрабатывает последовательность по одному элементу, перенося из шага в шаг скрытое состояние — свою «память» о прошлом.

Зачем особая архитектура для последовательностей

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

Формула одного шага словами

На каждом шаге t: h_t = активация(Wx · x_t + Wh · h_{t-1} + b). Новое состояние зависит и от текущего входа x_t, и от прошлого состояния h_{t-1}. Те же веса Wx, Wh используются на всех шагах — сеть «прокручивается» по последовательности.

Игрушечная RNN: память в действии

import math
def tanh(x): return math.tanh(x)

Wx, Wh, b = 0.8, 0.9, 0.0
h = 0.0                       # начальное состояние
sequence = [1, 0, 0, 1, 0]

print("шаг  вход  состояние h")
for t, x in enumerate(sequence):
    h = tanh(Wx * x + Wh * h + b)
    print(f"{t:>3}  {x:>4}   {h:>8.4f}")
print("Итоговое h помнит о прошлых входах:", round(h, 4))

Вывод:

шаг  вход  состояние h
  0     1     0.6640
  1     0     0.5354
  2     0     0.4477
  3     1     0.8345
  4     0     0.6358
Итоговое h помнит о прошлых входах: 0.6358

Смотрите: на шагах 1 и 2 вход равен 0, но состояние h не падает в ноль — оно помнит про единицу на шаге 0 и плавно затухает. А когда на шаге 3 снова приходит 1, состояние подскакивает. Это и есть память: выход зависит не только от текущего входа, но и от истории.

Проблема короткой памяти и решение — LSTM

У простой RNN память «дырявая»: из-за затухания градиента (раздел 4) она быстро забывает давние элементы и плохо ловит длинные зависимости. LSTM (Long Short-Term Memory) решает это специальными воротами (gates) — обучаемыми вентилями, которые решают, что в памяти сохранить, что забыть, а что выдать наружу. Благодаря воротам LSTM держит важную информацию на сотни шагов. Похожая, но более простая идея — GRU.

АрхитектураОсобенность
RNNпростое состояние, короткая память
LSTMворота сохраняют долгую память
GRUупрощённый вариант LSTM

Как RNN обучают

Обучение рекуррентной сети — это всё тот же backprop, но применённый к «развёрнутой» во времени сети: каждый шаг последовательности рассматривают как отдельный слой с общими весами, и градиент течёт назад через все шаги. Этот приём называют backpropagation through time (BPTT). Отсюда и беда простой RNN: чем длиннее последовательность, тем больше слоёв проходит градиент, тем сильнее он затухает или взрывается — ровно проблема из четвёртого раздела, только вдоль времени. Ворота LSTM как раз дают градиенту «гладкий коридор» сквозь шаги, поэтому память держится дольше.

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

Итог

  • RNN читает последовательность по элементам, перенося скрытое состояние-память.
  • Те же веса применяются на каждом шаге; выход зависит от истории.
  • Простая RNN быстро забывает; LSTM с воротами держит долгие зависимости.
Проверьте себя
1. Что переносит RNN из шага в шаг при обработке последовательности?
AФункцию потерь
BСкрытое состояние — память о предыдущих элементах
CLearning rate
DНомер эпохи
2. Зачем придумали LSTM вместо простой RNN?
AЧтобы ускорить вывод
BЧтобы держать долгую память: ворота борются с забыванием давних элементов
CЧтобы убрать состояние
DЧтобы работать с картинками
3. Почему в примере состояние h не обнулялось при нулевых входах?
AОшибка в tanh
BЧлен Wh·h_{t-1} переносит часть прошлого состояния в текущее
CВход всегда был 1
DТак работает пулинг
Поддержать проект