Документация Engee
Notebook

Нейросетевое прогнозирование временных рядов

Введение

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

В данной работе демонстрируется простейший нейросетевой подход к прогнозированию, обученный в авторегрессионной постановке. Модель использует два предыдущих значения ряда для предсказания следующего, а затем итеративно строит прогноз на заданный горизонт. Такой подход служит наглядной отправной точкой для знакомства с более сложными архитектурами нейронных сетей.

Используемые библиотеки

Присоединим необходимые библиотеки. Для работы с нейросетью, нам понадобится библиотека Flux.

In [ ]:
using Flux, Random, LinearAlgebra, Statistics

Исходные данные

Определим исходные данные. Создадим временной ряд из 100 точек: синусоида + линейный тренд + шум. Нормализуем ряд (вычитаем среднее, делим на стандартное отклонение). Сформируем матрицу признаков.

In [ ]:
Random.seed!(42)
# Генерация данных
t = 1:100
L = length(t)
z = 10*sin.(0.2*t) + 0.2*t + 0.3*randn(L)

# Нормализация данных
z_mean = mean(z)
z_std = std(z)
z_norm = (z .- z_mean) ./ z_std  # нормализованные данные

# Формирование входной матрицы
x = zeros(2, L)
x[1, 2:end] = z_norm[1:end-1]  # z(t-1)
x[2, 3:end] = z_norm[1:end-2]  # z(t-2)
x_data = Float32.(x)
z_data = Float32.(z_norm)

Настройка нейросети

Создадим нейросеть: входной слой (2→8), скрытый слой (8→4), выходной слой (4→1). Настроим оптимизатор Adam со скоростью обучения 0.001 и привяжем его к сети. Подготовим массив для записи ошибок на каждой эпохе. Зададим 5000 эпох обучения.

In [ ]:
net = Chain(Dense(2 => 8, relu), Dense(8 => 4, relu),Dense(4 => 1))
optimizer = Adam(0.001)
opt_state = Flux.setup(optimizer, net)
losses = Float32[]
epochs = 5000

Функция потерь

Создадим функцию потерь: передадим на вход модели матрицу признаков, получим предсказания, преобразуем в одномерный вектор и вычислим среднеквадратичную ошибку между предсказаниями и истинными значениями.

In [ ]:
function loss(model, x, y)
    y_pred = vec(model(x))
    return Flux.mse(y_pred, y)
end

Обучение нейросети

Запустим цикл обучения: вычислим градиенты и обновим параметры. После обучения получим предсказания сети на всех 100 точках.

In [ ]:
for epoch in 1:epochs
    current_loss = loss(net, x_data, z_data)
    push!(losses, current_loss)
    Flux.train!(net, [(x_data, z_data)], opt_state) do m, xb, yb
        loss(m, xb, yb)
    end
    if epoch % 100 == 0
        #println("Эпоха $epoch: loss = $(round(current_loss, digits=6))")
    end
end

y_train_norm = vec(net(x_data))
y_train = y_train_norm .* z_std .+ z_mean 

Прогноз временного ряда

Получим спрогнозированные значения временного ряда на 50 точек вперёд.

In [ ]:
function forecast(net, z_norm, z_mean, z_std, horizon=50)
    y_full_norm = zeros(Float32, 100 + horizon)
    y_full_norm[1:100] = z_norm
    for i in 1:horizon
        idx = 100 + i
        input_vec = Float32.([y_full_norm[idx-1], y_full_norm[idx-2]])
        y_full_norm[idx] = net(reshape(input_vec, 2, 1))[1]
    end
    return y_full_norm .* z_std .+ z_mean
end

y_full = forecast(net, Float32.(z_norm), z_mean, z_std, 50)
Out[0]:
150-element Vector{Float64}:
  2.077686166487098
  4.369704383951254
  6.151928340143768
  7.8801850772692745
  9.659601907697848
 10.663412370556056
 10.99663069738901
 11.154949594543305
 10.904175864829527
 11.106108789428392
 10.037363671824608
  9.406718355367095
  7.885179545387794
  ⋮
  8.16153229190152
  9.871865117523257
 11.16291496603565
 11.63761469392388
 11.440856430263558
 10.529202720345774
  8.602405889069477
  6.954229163008996
  5.774611191033784
  4.69222943408103
  3.7329430285822243
  2.9190516942247546

Визуализация

Отобразим исходный сигнал, аппроксимацию и прогноз на графике.

In [ ]:
# Визуализация
gr()
график = plot(1:100, z, 
          label="Исходный сигнал", 
          color=:blue, 
          linewidth=2,
          title="Результаты обучения и прогноза",
          legend=:topleft
          )
plot!(график, 1:100, y_train, 
      label="Аппроксимация", 
      color=:green, 
      linestyle=:dash,
      linewidth=2)
plot!(график, 101:150, y_full[101:150], 
      label="Прогноз", 
      color=:red,
      linewidth=2)
vline!(график, [100.5], label="Начало прогноза", color=:black, linestyle=:dash)

display(график)
No description has been provided for this image

Заключение

В данном примере реализована и обучена полносвязная нейронная сеть для прогнозирования модельного временного ряда. Несмотря на простоту архитектуры, модель успешно приняла как периодическую, так и трендовую составляющие сигнала, а также продемонстрировала возможность многозначного прогноза в авторегрессионном режиме.

На практике нейросетевое прогнозирование временных рядов применяется для:

  • предсказания цен на сырьё, акции и валютные курсы в трейдинговых системах;

  • прогнозирования потребления электроэнергии для балансировки нагрузки энергосетей;

  • оценки спроса на товары в торговле для управления запасами;

  • метеорологического моделирования и краткосрочного прогноза погоды;

  • мониторинга состояния промышленного оборудования по телеметрическим данным.

Представленный скрипт может служить основой для перехода к более продвинутым архитектурам — рекуррентным и трансформерным сетям, специально спроектированным для работы с последовательными данными.