Ответ
Преимущества RNN:
- Естественная работа с последовательностями: Архитектура с циклом позволяет обрабатывать данные переменной длины (предложения, временные ряды), сохраняя "память" о предыдущих элементах через скрытое состояние (hidden state).
- Разделение весов во времени (weight sharing): Одна и та же матрица параметров применяется на каждом временном шаге, что уменьшает количество параметров по сравнению с полносвязной сетью, развёрнутой во времени.
- Теоретическая способность улавливать долгосрочные зависимости: В идеале, скрытое состояние может нести информацию из начала последовательности.
Недостатки и практические проблемы "ванильных" RNN (SimpleRNN):
- Проблема исчезающих/взрывающихся градиентов: При обратном распространении ошибки через много временных шагов градиенты могут экспоненциально затухать или расти, что делает обучение длинных последовательностей практически невозможным.
- Последовательная природа вычислений: Обработка идёт шаг за шагом, что не позволяет эффективно параллелизовать вычисления на GPU, в отличие от CNN или Transformer. Это приводит к долгому обучению.
- Слабая реальная "память": На практике SimpleRNN плохо запоминает информацию из далёкого прошлого.
Решение: Gated RNN (LSTM, GRU) Для борьбы с затуханием градиентов были разработаны архитектуры с вентирными механизмами:
- LSTM (Long Short-Term Memory): Имеет ячейку (cell state), которая действует как "конвейерная лента", и три гейта (input, forget, output), управляющие потоком информации.
- GRU (Gated Recurrent Unit): Упрощённая версия LSTM с двумя гейтами (update и reset).
Пример реализации LSTM для прогнозирования временных рядов на PyTorch:
import torch.nn as nn
class LSTMModel(nn.Module):
def __init__(self, input_size=1, hidden_size=50, num_layers=2, output_size=1):
super().__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# x shape: (batch_size, seq_length, input_size)
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
out, _ = self.lstm(x, (h0, c0)) # out: (batch_size, seq_length, hidden_size)
# Берём выход только с последнего временного шага для прогноза
out = self.fc(out[:, -1, :])
return out
Вывод: Хотя LSTM/GRU решили ключевые проблемы и широко применялись для NLP и временных рядов, сегодня для многих задач (особенно NLP) они вытеснены Transformer-архитектурами, которые лучше улавливают глобальные зависимости и идеально параллелятся.