Представьте себе: вы пытаетесь предсказать будущее, как современный Нострадамус, но вместо хрустальных шаров у вас есть блоки с управляемыми входами. Не переживайте, если ваше последнее предсказание касалось завтрашней погоды (спойлер: шёл дождь… снова), мы собираемся сделать так, чтобы вы выглядели компетентным!

1. Почему GRUs — ваш новый лучший друг

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

  • Обучение на 18% быстрее, чем у LSTM без потери точности ([PDF-колдовство]);
  • Идеально подходят для последовательных данных, таких как цены на акции, потребление энергии или ваш недельный цикл кофейной зависимости;
  • Меньше параметров = меньше драмы с переобучением.

2. Подготовка данных: поваренная книга путешественника во времени

Мы будем использовать набор данных об энергии PJM East — потому что прогнозирование потребления энергии — это всё равно что быть электрическим экстрасенсом. Совет: никогда не доверяйте временным рядам, которые выглядят чище, чем ваша воскресная обувь.

import pandas as pd
from sklearn.preprocessing import MinMaxScaler
# Загрузка данных с помощью pandas — швейцарский армейский нож для работы с данными
data = pd.read_csv('pjme_hourly.csv', index_col=, parse_dates=)
data = data.resample('D').mean().ffill()  # Ежедневное повторное усреднение, потому что кому нужна 60-минутная тревога?
# Нормализация между 0 и 1, как ваши ожидания после 2020 года
scaler = MinMaxScaler()
scaled_data = scaler.fit_transform(data.values)
# Создание последовательностей — здесь происходит настоящее волшебство
def create_sequences(data, seq_length):
    xs, ys = [], []
    for i in range(len(data)-seq_length-1):
        x = data[i:(i+seq_length)]
        y = data[i+seq_length]
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)
X, y = create_sequences(scaled_data, seq_length=7)

3. Создание модели GRU: версия PyTorch

Время собрать наших нейронных мстителей! Мы будем использовать PyTorch, потому что это как LEGO для глубокого обучения.

import torch
import torch.nn as nn
class GRUProphet(nn.Module):
    def __init__(self, input_size=1, hidden_size=50, num_layers=2):
        super().__init__()
        self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
        self.linear = nn.Linear(hidden_size, 1)
    def forward(self, x):
        # GRU возвращает: вывод, скрытое состояние
        gru_out, _ = self.gru(x)  # Мы игнорируем скрытое состояние, как неловкий зрительный контакт
        last_time_step = gru_out[:, -1, :]
        return self.linear(last_time_step)
model = GRUProphet()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
loss_fn = nn.MSELoss()

4. Обучение: где происходит магия (и переобучение)

Разделите свои данные, как мастер карате, и обучите эту модель. Помните: потеря валидации — вот кто говорит правду.

from sklearn.model_selection import train_test_split
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, shuffle=False)
# Преобразование в тензоры PyTorch
train_X = torch.Tensor(X_train).unsqueeze(-1)
train_y = torch.Tensor(y_train)
val_X = torch.Tensor(X_val).unsqueeze(-1)
val_y = torch.Tensor(y_val)
# Цикл обучения — идеальное время для перерыва на кофе ☕
for epoch in range(100):
    model.train()
    preds = model(train_X)
    loss = loss_fn(preds, train_y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    # Проверка валидации
    model.eval()
    with torch.no_grad():
        val_preds = model(val_X)
        val_loss = loss_fn(val_preds, val_y)
    if epoch % 10 == 0:
        print(f"Epoch {epoch} | Train Loss: {loss.item():.4f} | Val Loss: {val_loss.item():.4f}")

5. Оценка: момент истины

Сравните свои прогнозы с фактическими значениями. Если они совпадают, станцуйте от радости. Если нет, вините гиперпараметры!

import matplotlib.pyplot as plt
# Обратное преобразование прогнозов
train_preds = scaler.inverse_transform(preds.detach().numpy())
val_preds = scaler.inverse_transform(val_preds.detach().numpy())
plt.figure(figsize=(12,6))
plt.plot(data.index[-len(val_y):], scaler.inverse_transform(y_val), label='Фактические')
plt.plot(data.index[-len(val_y):], val_preds, label='Прогнозируемые', alpha=0.7)
plt.title("Энергетическая схватка за прогнозирование потребления")
plt.legend()
plt.show()
graph TD A[Исходные данные] --> B[Предварительная обработка] B --> C[Модель GRU] C --> D[Обучение] D --> E[Оценка] E --> F[Прогнозы на будущее]

6. Прогнозирование будущего

Потому что какой смысл быть магом времени, если вы не можете предсказать завтрашние цифры?

def predict_future(model, data, steps, seq_length=7):
    model.eval()
    predictions = []
    current_seq = data[-seq_length:]
    for _ in range(steps):
        with torch.no_grad():
            seq_tensor = torch.Tensor(current_seq[-seq_length:]).unsqueeze(0).unsqueeze(-1)
            pred = model(seq_tensor)
            predictions.append(pred.item())
            current_seq = np.append(current_seq, pred.item())[1:]
    return scaler.inverse_transform(np.array(predictions).reshape(-1,1))
next_week_energy = predict_future(model, scaled_data, steps=7)
print(f"Потребление энергии на следующей неделе: {next_week_energy.flatten()}")

Заключительные советы от профессионалов

  • Настраивайте длину последовательности, как будто настраиваете гитару — 7 дней хорошо подходит для недельных паттернов;
  • Добавьте больше слоёв, если ваши данные более драматичны, чем мыльная опера;
  • Попробуйте разные скейлеры, когда ваши данные ведут себя как бунтующий подросток;
  • Используйте дропаут, если ваша модель начинает запоминать, как тот ребёнок в школе. Помните, даже самые лучшие модели не могут предсказать, когда сломается ваша кофемашина. Для этого вам понадобится настоящий хрустальный шар… или договор на техническое обслуживание. Удачного прогнозирования! 🚀