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

RNN, LSTM, GRU

🎯 Цель

После прочтения этой главы:

  • Знаете архитектуру NN для работы с sequence data (текст, time series, audio)
  • Знаете разницу между RNN, LSTM, GRU и когда какой использовать
  • Понимаете проблему vanishing gradient и решение LSTM
  • Пишете time series forecasting и text classification
  • Готовы к переходу на Transformers (в Месяце 4)

**Внимание:**Сейчас эра — Transformers(BERT, GPT, T5). RNN/LSTM во многих случаях устаревают. Но в time series всё ещё полезны и важны для истории/intuition NN.

Что нужно изучить

  • RNN — основы Recurrent Neural Network
  • Проблема Vanishing/Exploding Gradient
  • LSTM — Long Short-Term Memory
  • GRU — Gated Recurrent Unit
  • Bidirectional RNN/LSTM
  • Seq2Seq — encoder-decoder
  • Attention mechanism(мост к Transformers)
  • Time series forecasting — sliding window approach
  • Text classification with LSTM

Библиотеки

pip install torch torchtext pandas

Важные темы

RNN — Recurrent Neural Network

Sequence: [x₁, x₂, x₃, ...]

   x₁              x₂              x₃
    │               │               │
    ▼               ▼               ▼
  [RNN] ──h₁──> [RNN] ──h₂──> [RNN] ──h₃──>
                                              
h_t = tanh(W_h · h_{t-1} + W_x · x_t + b)

Основная идея: предыдущий hidden state (h_{t-1}) вместе с текущим input формирует новый state.

Проблема Vanishing Gradient

В длинных sequence gradient проходя через tanh многократно стремится к нулю — модель не может выучить дальние dependency.

**Решение — LSTM:**сохранение/удаление информации через специальные «gate».

LSTM — полная структура

                    cell state (C)
                    ──────────────►
                       ↑    ↑    ↑
                       │    │    │
                    [forget] [input] [output]
                       gate    gate    gate
                       │    │    │
                       └────┴────┘
                          ↑
                       h_t-1, x_t

3 gate:

  • **Forget gate (f):**что удалить из cell state
  • **Input gate (i):**что новое добавить
  • **Output gate (o):**каким будет следующий hidden state

GRU — упрощённый LSTM

  • 2 gate (reset, update)
  • Быстрее LSTM, меньше параметров
  • Точность равна или близка LSTM

Что когда?

Use caseРекомендация
Text classificationLSTM/GRU bidirectional, или BERT (Месяц 4)
Time series forecastingLSTM, или Prophet/N-BEATS
Sentiment analysisBERT (transformer)
TranslationTransformer (T5, MarianMT)
Sequence generationGPT-style transformer
Audio processingConv1D + LSTM или wav2vec

**Правило:**Начинайте новый проект с transformer. RNN/LSTM используйте только по реальной причине (маленький dataset, real-time inference, simple time series).

Примеры кода

Простая RNN

import torch
import torch.nn as nn

class SimpleRNN(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super().__init__()
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, num_classes)
    
    def forward(self, x):
        # x shape: (batch, seq_len, input_size)
        out, hidden = self.rnn(x)
        # out shape: (batch, seq_len, hidden_size)
        # берём последний timestep
        last_output = out[:, -1, :]
        logits = self.fc(last_output)
        return logits

model = SimpleRNN(input_size=10, hidden_size=64, num_classes=5)
x = torch.randn(32, 20, 10)  # batch=32, seq_len=20, features=10
print(model(x).shape)  # (32, 5)

LSTM — Time Series Forecasting

class LSTMForecaster(nn.Module):
    def __init__(self, input_size=1, hidden_size=64, num_layers=2, output_size=1):
        super().__init__()
        self.lstm = nn.LSTM(
            input_size, hidden_size,
            num_layers=num_layers,
            batch_first=True,
            dropout=0.2 if num_layers > 1 else 0,
        )
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        # x shape: (batch, seq_len, input_size)
        out, (h_n, c_n) = self.lstm(x)
        # Последний timestep
        last_output = out[:, -1, :]
        return self.fc(last_output)

Sliding window approach

def create_sequences(data, seq_length):
    """1D time series → (X, y) pairs."""
    X, y = [], []
    for i in range(len(data) - seq_length):
        X.append(data[i:i + seq_length])
        y.append(data[i + seq_length])
    return torch.tensor(X, dtype=torch.float32).unsqueeze(-1), torch.tensor(y, dtype=torch.float32)

# Пример — прогноз sin function
import numpy as np
data = np.sin(np.linspace(0, 100, 1000))
X, y = create_sequences(data, seq_length=20)
# X shape: (980, 20, 1), y shape: (980,)

Text classification with LSTM

class TextClassifierLSTM(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes, n_layers=2):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
        self.lstm = nn.LSTM(
            embed_dim, hidden_dim,
            num_layers=n_layers,
            batch_first=True,
            bidirectional=True,
            dropout=0.3,
        )
        # Bidirectional → hidden_dim * 2
        self.fc = nn.Linear(hidden_dim * 2, num_classes)
        self.dropout = nn.Dropout(0.5)
    
    def forward(self, x, lengths=None):
        # x shape: (batch, seq_len) — token IDs
        embedded = self.embedding(x)
        
        if lengths is not None:
            # Для variable length sequences
            packed = nn.utils.rnn.pack_padded_sequence(
                embedded, lengths.cpu(), batch_first=True, enforce_sorted=False
            )
            _, (hidden, _) = self.lstm(packed)
        else:
            _, (hidden, _) = self.lstm(embedded)
        
        # Bidirectional final hidden: forward + backward
        hidden = torch.cat([hidden[-2], hidden[-1]], dim=1)
        hidden = self.dropout(hidden)
        return self.fc(hidden)

Training loop (для sequence data)

def train_sequence_model(model, train_loader, val_loader, epochs=20, lr=1e-3):
    device = next(model.parameters()).device
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    criterion = nn.MSELoss()  # для forecasting; для classification CrossEntropy
    
    for epoch in range(epochs):
        model.train()
        train_loss = 0
        for X, y in train_loader:
            X, y = X.to(device), y.to(device)
            optimizer.zero_grad()
            
            pred = model(X)
            loss = criterion(pred.squeeze(), y)
            loss.backward()
            
            # ВАЖНО: gradient clipping для RNN
            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
            
            optimizer.step()
            train_loss += loss.item()
        
        # Eval
        model.eval()
        val_loss = 0
        with torch.no_grad():
            for X, y in val_loader:
                X, y = X.to(device), y.to(device)
                pred = model(X)
                val_loss += criterion(pred.squeeze(), y).item()
        
        print(f"Epoch {epoch+1}: train={train_loss/len(train_loader):.4f}  "
              f"val={val_loss/len(val_loader):.4f}")

Encoder-Decoder (Seq2Seq) preview

class Encoder(nn.Module):
    def __init__(self, input_size, hidden_size):
        super().__init__()
        self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True)
    
    def forward(self, x):
        _, (h, c) = self.lstm(x)
        return h, c  # context

class Decoder(nn.Module):
    def __init__(self, output_size, hidden_size):
        super().__init__()
        self.lstm = nn.LSTM(output_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x, h, c):
        out, (h, c) = self.lstm(x, (h, c))
        return self.fc(out), h, c

# Seq2Seq:
# encoder(input) → context
# decoder(<START>, context) → output_1
# decoder(output_1, context) → output_2
# ...

Интеграция с backend

Time series forecasting API

from fastapi import FastAPI
from pydantic import BaseModel
import torch
import numpy as np

app = FastAPI()
model = LSTMForecaster()
model.load_state_dict(torch.load("forecaster.pt"))
model.eval()

class ForecastInput(BaseModel):
    historical_values: list[float]
    forecast_steps: int = 7

class ForecastOutput(BaseModel):
    predictions: list[float]

@app.post("/forecast", response_model=ForecastOutput)
@torch.no_grad()
def forecast(data: ForecastInput):
    # Last 20 values as input
    history = torch.tensor(data.historical_values[-20:], dtype=torch.float32)
    history = history.unsqueeze(0).unsqueeze(-1)  # (1, 20, 1)
    
    predictions = []
    for _ in range(data.forecast_steps):
        pred = model(history).item()
        predictions.append(pred)
        # Slide window: drop first, append prediction
        history = torch.cat([history[:, 1:, :], torch.tensor([[[pred]]])], dim=1)
    
    return ForecastOutput(predictions=predictions)

Text sentiment API (LSTM)

@app.post("/sentiment")
@torch.no_grad()
def predict_sentiment(text: str):
    tokens = tokenizer(text, max_length=200, padding="max_length", truncation=True)
    X = torch.tensor([tokens]).long()
    
    logits = model(X)
    probs = torch.softmax(logits, dim=1).squeeze()
    
    labels = ["negative", "neutral", "positive"]
    return {
        "sentiment": labels[probs.argmax().item()],
        "scores": {label: float(p) for label, p in zip(labels, probs)},
    }

**Внимание:**Для production sentiment использование HuggingFace BERTмногократно даёт лучший результат. LSTM здесь для примера.

Ресурсы

  • Andrej Karpathy — “The Unreasonable Effectiveness of RNNs”(blog)
  • Colah’s blog — Understanding LSTMs(colah.github.io/posts/2015-08-Understanding-LSTMs)
  • PyTorch Sequence tutorials
  • “Deep Learning for Time Series Forecasting” — Jason Brownlee
  • fast.ai NLP course(RNN и beyond)

🏋️ Упражнения

🟢 Easy

  1. Сравните число параметров nn.RNN, nn.LSTM, nn.GRU.
  2. Next-step forecasting для sinusoidal data через LSTM.
  3. Сравните Bidirectional LSTM и unidirectional результат.

🟡 Medium

  1. Time series: forecasting на 30 дней по реальным данным stock price (yfinance).
  2. Text classification: binary sentiment на IMDB reviews dataset через LSTM (80%+).
  3. Char-level RNN: character-level text generation в стиле Karpathy.

🔴 Hard

  1. Seq2Seq translation — запустите на маленьком датасете (English ↔ German маленький dataset).
  2. Attention mechanism — добавьте attention над LSTM (введение в transformer).
  3. Time series API — сравните Prophet vs LSTM, лучшую модель deploy через FastAPI.

Capstone

notebooks/month-03/06_rnn_timeseries.ipynb:

  • Загрузите цену какой-нибудь акции через Yfinance(за 5 лет)
  • Классический baseline: Prophet, ARIMA
  • Ваша LSTM модель
  • Сравнение forecasting accuracy на test set
  • FastAPI endpoint

✅ Чек-лист

  • Знаю разницу между RNN, LSTM, GRU
  • Понимаю проблему vanishing gradient
  • Знаю функции gate’ов LSTM
  • Разница между Bidirectional и unidirectional
  • Умею готовить данные для time series через sliding window approach
  • Знаю, почему gradient clipping важен в RNN
  • Text classification через LSTM
  • Знаю, почему Transformers (Месяц 4) превосходят RNN и причины

Месяц 3 завершён! Изучите Упражнения и переходите к Месяц 4 — CV + NLP.