Временные ряды – один из самых популярных типов данных в бизнес‑аналитике, финансах, IoT, медицине и многих других областях. Способность предсказывать будущее значение ряда позволяет принимать более обоснованные решения, планировать ресурсы и автоматически реагировать на изменения.
В этой статье мы подробно разберём, как построить модель предсказания временных рядов, используя Python и библиотеку Keras (часть TensorFlow). Мы охватим всё: от подготовки данных до выбора архитектуры модели, от обучения до оценки качества и практических советов по деплою.
Содержание
- Почему Keras?
- Подготовка и визуализация данных
- Типичные архитектуры моделей
- Обучение модели
- Оценка и интерпретация результатов
- Тонкая настройка гиперпараметров
- Практический пример (прогнозирование продаж)
- Лучшие практики и типичные ошибки
Временной ряд – это последовательность наблюдений, упорядоченных во времени, например:
- курс валют каждый час;
- показатели температуры по дням;
- объём продаж за каждый месяц.
Ключевые свойства, которые влияют на выбор модели:
- Тренд – долгосрочное направление изменения;
- Сезонность – повторяющиеся паттерны (день‑неделя, год‑квартал);
- Автокорреляция – зависимость текущего значения от прошлых;
- Шум – случайные отклонения.
Традиционные статистические методы (ARIMA, SARIMA, Exponential Smoothing) работают хорошо при линейных зависимостях, но часто не справляются с нелинейными паттернами. Именно здесь в игру вступают нейронные сети.
Почему Keras?
Keras – высокоуровневый API для построения и обучения нейронных сетей, встроенный в TensorFlow 2.x. Преимущества:
- Простота и читаемость кода;
- Поддержка разнообразных слоёв (LSTM, GRU, Conv1D, Transformer и др.);
- Автоматическое управление GPU/CPU;
- Интеграция с
tf.dataдля эффективной обработки больших наборов данных; - Встроенные функции обратного вызова (
Callback) для ранней остановки, логирования и сохранения модели.
Все примеры ниже реализованы с использованием Keras, но их легко адаптировать к чистому TensorFlow или другим фреймворкам.
Подготовка и визуализация данных
Загрузка данных
Для примера будем использовать набор «Daily Minimum Temperatures in Melbourne» (доступен в UCI Machine Learning Repository).
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-min-temperatures.csv"
df = pd.read_csv(url, parse_dates=['Date'], index_col='Date')
df.head
Визуализация
plt.figure(figsize=(12,4))
plt.plot(df.index, df['Temp'], label='Daily min temperature')
plt.title('Daily Minimum Temperatures in Melbourne')
plt.xlabel('Date')
plt.ylabel('Temperature (°C)')
plt.legend
plt.show
Преобразование в обучающие окна
Нейронные сети работают с фиксированным размером входа. Мы формируем скользящие окна длиной window_size и предсказываем значение, следующее за окном.
def create_dataset(series, window_size=30, horizon=1):
"""
series – 1‑D массив (numpy) с последовательностью.
window_size – количество прошлых точек, используемых как вход.
horizon – сколько шагов вперёд предсказываем.
""" X, y = [], []
for i in range(len(series) ー window_size ー horizon + 1):
X.append(series[i:i+window_size])
y.append(series[i+window_size+horizon-1])
return np.array(X), np.array(y)
values = df['Temp'].values.astype(np.float32)
window_size = 30
horizon = 1
X, y = create_dataset(values, window_size, horizon)
X = X[..., np.newaxis] # shape: (samples, window_size, 1)
print('X shape:', X.shape, 'y shape:', y.shape)
Деление на train/validation/test
train_size = int(0.7 * len(X))
val_size = int(0.15 * len(X))
X_train, y_train = X[:train_size], y[:train_size]
X_val, y_val = X[train_size:train_size+val_size], y[train_size:train_size+val_size]
X_test, y_test = X[train_size+val_size:], y[train_size+val_size:]
print(f'Train: {X_train.shape[0]}, Val: {X_val.shape[0]}, Test: {X_test.shape[0]}')
Типичные архитектуры моделей для временных рядов
Полносвязные (Dense) сети
Самый простой вариант – «flatten» вход и подать в несколько полносвязных слоёв. Подходит для небольших наборов, но обычно уступает рекуррентным моделям.
Рекуррентные сети (RNN) – LSTM / GRU
Long Short‑Term Memory (LSTM) и Gated Recurrent Unit (GRU) способны запоминать длительные зависимости.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, GRU, Dense, Dropout
def build_lstm(window_size, units=64, dropout=0.2):
model = Sequential([
LSTM(units, input_shape=(window_size, 1), return_sequences=False),
Dropout(dropout),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
return model
lstm_model = build_lstm(window_size)
lstm_model.summary
1‑D сверточные сети (Conv1D)
Свертки способны быстро извлекать локальные паттерны (например, резкие скачки). Комбинация Conv1D + MaxPooling часто используется перед рекуррентным слоем.
from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten
def build_cnn(window_size, filters=32, kernel_size=3):
model = Sequential([
Conv1D(filters, kernel_size, activation='relu',
input_shape=(window_size, 1)),
MaxPooling1D(pool_size=2),
Flatten,
Dense(50, activation='relu'),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
return model
cnn_model = build_cnn(window_size)
cnn_model.summary
Смешанные модели (Conv1D + LSTM)
Сначала Conv1D извлекает локальные признаки, затем LSTM «понимает» их во времени.
def build_cnn_lstm(window_size, conv_filters=32, lstm_units=64):
model = Sequential([
Conv1D(conv_filters, 3, activation='relu',
input_shape=(window_size, 1)),
MaxPooling1D(2),
LSTM(lstm_units),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
return model
cnn_lstm_model = build_cnn_lstm(window_size)
cnn_lstm_model.summary
Трансформеры для временных рядов
Модели типа Transformer (в частности Temporal Fusion Transformer) показывают отличные результаты при наличии длинных зависимостей и множества внешних регрессоров.
from tensorflow.keras.layers import MultiHeadAttention, LayerNormalization, Add, Dropout
def build_transformer(window_size, d_model=64, num_heads=4, ff_dim=128, dropout=0.1):
inputs = tf.keras.Input(shape=(window_size, 1))
# Линейное проецирование к d_model
x = tf.keras.layers.Dense(d_model)(inputs)
# Multi‑Head Attention
attn_output = MultiHeadAttention(num_heads=num_heads,
key_dim=d_model)(x, x)
attn_output = Dropout(dropout)(attn_output)
out1 = LayerNormalization(epsilon=1e-6)(x + attn_output)
# Feed‑Forward
ffn = tf.keras.Sequential([
tf.keras.layers.Dense(ff_dim, activation='relu'),
tf.keras.layers.Dense(d_model)
])
ffn_output = ffn(out1)
ffn_output = Dropout(dropout)(ffn_output)
out2 = LayerNormalization(epsilon=1e-6)(out1 + ffn_output)
# Пулинг и предсказание
out2 = tf.keras.layers.GlobalAveragePooling1D(out2)
outputs = tf.keras.layers.Dense(1)(out2)
model = tf.keras.Model(inputs, outputs)
model.compile(optimizer='adam', loss='mse')
return model
transformer_model = build_transformer(window_size)
transformer_model.summary
Обучение модели
Выбор функции потерь и метрик
- Mean Squared Error (MSE) – стандарт для регрессии.
- Mean Absolute Error (MAE) – менее чувствителен к выбросам.
- Для финансовых задач часто используют Mean Absolute Percentage Error (MAPE).
Callback‑ы
Ключевой элемент – EarlyStopping (остановка при переобучении) и ModelCheckpoint (сохранение лучшей модели).
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
early_stop = EarlyStopping(monitor='val_loss',
patience=10,
restore_best_weights=True)
checkpoint = ModelCheckpoint('best_model.h5',
monitor='val_loss',
save_best_only=True)
history = lstm_model.fit(
X_train, y_train,
epochs=100,
batch_size=32,
validation_data=(X_val, y_val),
callbacks=[early_stop, checkpoint],
verbose=1
)
Визуализация процесса обучения
plt.figure(figsize=(8,4))
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='validation')
plt;title('Кривая обучения')
plt.xlabel('Эпоха')
plt.ylabel('MSE')
plt.legend
plt.show
Оценка и интерпретация результатов
Прогноз на тестовом наборе
y_pred = lstm_model.predict(X_test).flatten
plt.figure(figsize=(12,4))
plt.plot(range(len(y_test)), y_test, label='Истинные')
plt.plot(range(len(y_pred)), y_pred, label='Прогноз')
plt.title('Сравнение предсказаний и реальных значений')
plt.legend
plt.show
Метрики качества
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
mse = mean_squared_error(y_test, y_pred)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f'MSE = {mse:.4f}')
print(f'MAE = {mae:.4f}')
print(f'R² = {r2:;4f}')
Инверсия масштабирования
Если вы применяли нормализацию (StandardScaler, MinMaxScaler), не забудьте вернуть предсказания в оригинальные единицы.
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler
scaled = scaler.fit_transform(values.reshape(-1,1))
y_pred_original = scaler.inverse_transform(y_pred.reshape(-1,1)).flatten
Тонкая настройка гиперпараметров
Для улучшения качества часто используют поиск по сетке (Grid Search) или более продвинутый Bayesian Optimization. В Keras удобно применять keras-tuner.
!pip install -q keras-tuner # выполнить один раз
import keras_tuner as kt
def model_builder(hp):
model = Sequential
# Количество LSTM‑слоёв
for i in range(hp.Int('num_layers', 1, 3)):
model.add(LSTM(units=hp.Int(f'units_{i}', min_value=32, max_value=256, step=32),
return_sequences=(i < hp.Int('num_layers', 1, 3)-1)))
model.add(Dropout(rate=hp.Float('dropout', 0.0, 0.5, step=0.1)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
return model
tuner = kt.RandomSearch(
model_builder,
objective='val_loss',
max_trials=20,
executions_per_trial=2,
directory='my_dir',
project_name='time_series_tuning'
)
tuner.search(X_train, y_train,
epochs=50,
validation_data=(X_val, y_val),
callbacks=[early_stop])
После завершения поиска можно получить лучшую модель:
best_hps = tuner.get_best_hyperparameters(num_trials=1)[0]
print(best_hps.values)
best_model = tuner.get_best_models(num_models=1)[0]
Практический пример: прогнозирование ежемесячных продаж
Описание задачи
У компании есть исторические данные о продажах за 5 лет (мес × SKU). Нужно предсказать продажи на следующий месяц для каждого SKU.
Шаги решения
- Загрузка данных (CSV →
pandas). - Агрегация по месяцам и SKU.
- Создание признаков:
- Текущий объём продаж;
- Среднее за последние 3/6/12 месяцев;
- Индикатор сезона (месяц года);
- Внешние регрессоры (реклама, CPI).
- Нормализация (например,
StandardScaler). - Формирование окон (window_size = 12, horizon = 1).
- Обучение модели LSTM с двумя слоями и dropout.
- Оценка на отложенной выборке и визуализация.
- Сохранение модели в формате
.h5и экспорт вTensorFlow Servingдля онлайн‑прогноза.
Ключевой код (сокращённый)
import tensorflow as tf
import pandas as pd
from sklearn.preprocessing import StandardScaler
df = pd.read_csv('sales.csv', parse_dates=['date'])
df['month'] = df['date'].dt.to_period('M')
monthly = df.groupby(['sku', 'month']).agg({'sales':'sum'}).reset_index
monthly['month_num'] = monthly['month'].dt.month
monthly['log_sales'] = np.log1p(monthly['sales'])
scaler = StandardScaler
monthly[['log_sales', 'month_num']] = scaler.fit_transform(
monthly[['log_sales', 'month_num']])
def build_dataset(df, sku, window=12):
series = df[df['sku']==sku].sort_values('month')['log_sales'].values
X, y = create_dataset(series, window, horizon=1)
X = X[..., np.newaxis]
return X, y
X_all, y_all = [], []
for sku in monthly['sku'].unique:
X_s, y_s = build_dataset(monthly, sku)
X_all.append(X_s); y_all.append(y_s)
X_all = np.concatenate(X_all)
y_all = np.concatenate(y_all)
train_idx = int(0.75 * len(X_all))
X_train, y_train = X_all[:train_idx], y_all[:train_idx]
X_val, y_val = X_all[train_idx:int(0.9len(X_all))], y_all[train_idx:int(0.9len(X_all))]
X_test, y_test = X_all[int(0.9len(X_all)):], y_all[int(0.9len(X_all)):]
model = Sequential([
LSTM(128, input_shape=(12,1), return_sequences=True),
Dropout(0.2),
LSTM(64),
Dropout(0.2),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train,
epochs=50,
batch_size=64,
validation_data=(X_val, y_val),
callbacks=[early_stop, checkpoint])
Деплой
Сохраните модель:
model.save('sales_forecast_lstm')
Запустите TensorFlow Serving:
docker run -p 8501:8501
--mount type=bind,source=$(pwd)/sales_forecast_lstm,target=/models/sales_forecast_lstm
-e MODEL_NAME=sales_forecast_lstm -t tensorflow/serving
Отправьте запрос через curl или любой клиент REST.
Лучшие практики и типичные ошибки
- Не забывайте о сезонности. Добавьте индикаторные переменные (месяц, квартал, день недели).
- Старайтесь использовать как минимум 3‑5 циклов сезонности. Для месячных данных – 12 мес., для дневных – 7 дн.
- Разделяйте наборы по времени, а не случайным образом. Перемешивание разрушит временную связь.
- Нормализуйте только тренировочную часть. При трансформировании теста используйте те же параметры.
- Следите за переобучением. LSTM‑модели быстро «запоминают» тренировочные последовательности.
- Регуляризация: dropout, L2‑регуляризация, ранняя остановка.
- Аугментация данных. Для небольших наборов полезно генерировать синтетические сериалы (шум, сдвиги).
- Тестирование на реальном бизнес‑периоде. Иногда метрика MSE не отражает финансовый риск – используйте бизнес‑ориентированные KPI.
Предсказание временных рядов с помощью Keras – мощный и гибкий подход, который позволяет решать задачи от простых «скользящих средних» до сложных многовариантных прогнозов с внешними факторами. Мы рассмотрели:
- Как подготовить данные и сформировать окна;
- Какие архитектуры (Dense, LSTM, GRU, Conv1D, Transformer) подходят под разные типы рядов;
- Процесс обучения, визуализации и оценки модели;
- Методы гиперпараметрической оптимизации;
- Полный практический пример с деплоем.
Помните, что успех проекта зависит не только от модели, но и от качественной предобработки, корректного разделения данных и правильного выбора метрик. Экспериментируйте, используйте кросс‑валидацию по времени (TimeSeriesSplit) и постоянно проверяйте модель на новых данных – и вы сможете построить надёжный предсказательный сервис.
Удачной разработки!