Привет, Хабр!
Четыре года назад, еще в институте, одним из моих первых серьезных проектов была простая LSTM-модель для прогноза погоды. Недавно, пересматривая старые наработки, я задался вопросом: насколько дальше можно зайти, применив накопленный за эти годы опыт и современные инженерные практики?
Эта статья — история такого "рефакторинга длиною в 4 года". Это рассказ о том, как простой академический проект был переосмыслен и превращен в полноценное End-to-End (E2E) решение. Цель — не просто снова предсказать погоду, а на практическом примере продемонстрировать системный подход к построению ML-пайплайна с нуля.
В статье рассматриваются все ключевые этапы: от разработки отказоустойчивого веб-скрапера до проведения сравнительного анализа трех разнородных моделей прогнозирования:
ARIMA — классический статистический метод, используемый в качестве бейзлайна.
N-BEATS — современная SOTA-архитектура, реализованная в библиотеке Darts.
Кастомная LSTM — рекуррентная нейронная сеть, реализованная на PyTorch с использованием полного набора инженерных признаков.
В качестве предметной области были выбраны метеорологические данные за последние 15 лет для города Нижний Тагил. Этот выбор обусловлен двумя факторами: доступностью многолетних архивов для сбора и наличием в данных сложных, но поддающихся анализу паттернов (тренды, сезонность, стохастическая компонента).
Статья структурирована следующим образом:
Этап 1: Описание архитектуры и процесса разработки отказоустойчивого пайплайна сбора данных.
Этап 2: Проведение разведочного анализа (EDA) и реализация продвинутого инжиниринга признаков.
Этап 3: Описание методологии сравнительного эксперимента и процесса обучения моделей.
Заключение: Анализ результатов и итоговые выводы.
Этап 1. Разработка пайплайна сбора данных
Первоочередной задачей проекта являлась разработка надежного и воспроизводимого процесса для сбора исходных данных. Этот этап включал анализ источника, проектирование архитектуры скрапера и реализацию механизма агрегации.
1.1. Источник данных и политика использования
Исходные данные для проекта были получены из открытого архива метеорологических наблюдений, представленного на сайте nizhniy-tagil.nuipogoda.ru. Ресурс содержит детализированные погодные сводки за многолетний период.

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

1.2. Анализ источника и выбор стратегии парсинга
Источник данных не предоставляет публичного API, что потребовало разработки кастомного веб-скрапера.

Первичный анализ HTML-структуры страниц показал, что все ключевые числовые метрики (температура, давление, скорость и направление ветра, облачность) содержатся не в видимых HTML-тегах, а встроены в JavaScript-объект mart.forecastMap.
В связи с этим была выбрана стратегия парсинга, основанная на извлечении данных напрямую из этого JS-объекта с помощью регулярных выражений. Такой подход является более надежным и устойчивым к потенциальным изменениям HTML-верстки сайта по сравнению с традиционным парсингом тегов.
1.3. Инженерные вызовы и архитектура отказоустойчивого скрапера
Переход от парсинга одной страницы к сбору архива за 15 лет (~5400 URL) выявил ряд нетривиальных проблем, характерных для работы с реальными веб-ресурсами.
Проблема №1: "Тихий провал" и защита сервера.
Первые запуски параллельного скрапера приводили к обескураживающему результату: Обработано 5448/5448 URL. Собрано 'сырых' записей: 0.
При этом одиночные запросы в отладочном режиме работали корректно. Диагностика показала, что сервер, обнаружив высокую частоту запросов, начинал отдавать "облегченную" версию HTML без искомого JS-объекта, эффективно "ослепляя" парсер. Кроме того, периодически возникали сетевые ошибки 502 Bad Gateway.
Решение: Отказоустойчивая архитектура.
Для решения этих проблем архитектура скрапера была спроектирована с упором на отказоустойчивость и имитацию более "человеческого" поведения:
Проактивная генерация URL: Вместо того чтобы следовать по навигационным ссылкам "предыдущий день", скрипт на первом шаге генерирует полный список URL для всего целевого временного диапазона. Это делает процесс независимым от верстки и позволяет легко распараллеливать задачи.
Контролируемый параллелизм: Для ускорения процесса используется concurrent.futures.ThreadPoolExecutor. Однако, для обхода защиты сервера количество параллельных потоков (MAX_WORKERS) было эмпирически подобрано и снижено до консервативного значения (3), чтобы не создавать пиковой нагрузки.
Механизм повторных попыток: Для борьбы с сетевыми ошибками в каждый рабочий поток встроен механизм retries с экспоненциальной задержкой (exponential backoff). В случае неудачного запроса, поток делает паузу и повторяет попытку несколько раз, прежде чем окончательно признать URL недоступным.

1.4. Агрегация и структурирование данных
На выходе скрапера получается массив 3-часовых наблюдений. Для подготовки данных к моделированию выполняется этап агрегации. Данные преобразуются в "широкий" формат, где одна запись соответствует одному дню.

Для каждого ключевого метеорологического показателя (temperature, pressure, wind_speed и др.) вычисляются средние значения для трех периодов — "утро" (06:00, 09:00), "день" (12:00, 15:00) и "вечер" (18:00, 21:00). Такой подход позволяет сохранить информацию о внутридневной динамике признаков. Итоговый датасет сохраняется в локальную базу данных SQLite для дальнейшего использования.
Этап 2. Разведочный анализ и инжиниринг признаков (EDA & Feature Engineering)

После сбора и первичной структуризации данных был проведен их разведочный анализ (EDA) с целью выявления ключевых статистических свойств, паттернов и зависимостей. Результаты этого анализа послужили основой для этапа инжиниринга признаков (Feature Engineering).
2.1. Разведочный анализ данных (EDA)
После загрузки и первичной структуризации данных был проведен их визуальный анализ. На скриншоте представлены временные ряды для трех целевых переменных: утренней, дневной и вечерней температуры.

Уже на этом этапе можно сделать несколько ключевых наблюдений:
Наличие сильной сезонности: Все три ряда демонстрируют четко выраженную годовую периодичность с пиками в летние месяцы и спадами в зимние.
Высокая волатильность: Помимо глобальной сезонности, присутствует значительная ежедневная изменчивость ("шум"), которая и представляет основной интерес для моделирования.
Различное поведение: Диапазоны и средние значения для утренней, дневной и вечерней температуры отличаются, что подтверждает корректность выбора многомерного подхода к прогнозированию.
Дальнейший анализ проводился на примере временного ряда дневной температуры (temperature_day) как наиболее репрезентативного.
-
Декомпозиция временного ряда
С помощью функции seasonal_decompose из библиотеки statsmodels исходный ряд был разложен на три компоненты: тренд, сезонность и остатки.Тренд (Trend): Анализ выявил наличие слабого, нелинейного восходящего тренда, что указывает на долгосрочные изменения в данных.
Сезонность (Seasonal): Была обнаружена ярко выраженная и стабильная годовая сезонность, которая является доминирующим паттерном в данных.
Остатки (Residuals): После удаления тренда и сезонности остатки представляют собой стохастическую компоненту ряда, которую предстоит моделировать.

Анализ автокорреляции
Для оценки "памяти" временного ряда были построены автокорреляционная (ACF) и частично автокорреляционная (PACF) функции.
ACF: Медленно затухающая автокорреляционная функция подтвердила нестационарность ряда, обусловленную наличием тренда и сезонности.
PACF: Частично автокорреляционная функция показала статистически значимые коэффициенты только на первых двух лагах, после чего резко обрывалась. Это указывает на то, что значение температуры в конкретный день наиболее сильно зависит от значений за предыдущие два дня.

Проверка на стационарность
Расширенный тест Дики-Фуллера (ADF) был применен для формальной проверки ряда на стационарность. Результат теста (p-value < 0.05) позволил отвергнуть нулевую гипотезу о наличии единичного корня. Это говорит о том, что нестационарность ряда носит в большей степени детерминированный (сезонный), а не стохастический характер, что является положительным фактором для прогнозирования.
2.2. Инжиниринг признаков (Feature Engineering)
На основе выводов, полученных в ходе EDA, был сформирован расширенный набор признаков, призванный явно передать модели обнаруженные паттерны.
-
Проблема №1: Как "объяснить" модели сезонность и цикличность?
Нейронные сети не понимают, что месяц=12 (декабрь) близок к месяцу=1 (январь). Для них это просто два далеких числа. Та же проблема существует для дня в году и направления ветра в градусах.Решение: sin/cos преобразование.
Эти циклические признаки были преобразованы в двумерное представление с помощью синуса и косинуса. Это позволяет представить их в виде координат на круге, сохраняя информацию о "близости" конечных и начальных точек цикла.codePython
# Пример преобразования для дня в году с учетом високосных лет days_in_year = (df.index.is_leap_year).astype(int) + 365 df['day_of_year_sin'] = np.sin(2 * np.pi * df['day_of_year'] / days_in_year) df['day_of_year_cos'] = np.cos(2 * np.pi * df['day_of_year'] / days_in_year) -
Проблема №2: Как дать модели "краткосрочную память"?
Как показал анализ PACF, недавнее прошлое имеет огромное влияние на настоящее.Решение: Генерация лаговых признаков.
Были созданы признаки, содержащие значения ключевых показателей (температура, давление, облачность) за предыдущие дни (t-1, t-2) и неделю назад (t-7). Это позволяет модели напрямую видеть недавний контекст при формировании прогноза.
После создания временных признаков, включая номер месяца в году, был проведен анализ их взаимосвязи с целевой переменной. На скриншоте показано распределение дневной температуры для каждого месяца.

2.3. Итоговый датасет
После всех преобразований был сформирован итоговый датасет, содержащий более 30 признаков для каждого дня. Этот набор данных, включающий как исходные агрегированные метрики, так и сгенерированные инженерные признаки, послужил основой для обучения и сравнения моделей на следующем этапе.

Этап 3. Сравнительный анализ моделей
После подготовки и анализа данных был проведен сравнительный эксперимент по их прогнозированию с использованием трех различных моделей. Целью эксперимента было не только получить прогноз, но и оценить, как различные подходы (статистический, SOTA "из коробки" и кастомный с инжинирингом признаков) справляются с поставленной задачей многомерного прогнозирования.
3.1. Методология эксперимента
Целевые переменные: В качестве целей для прогнозирования выступал вектор из трех значений: temperature_morning, temperature_day, temperature_evening.
-
Выборки данных: Исходный датасет был разделен на три последовательные части:
Обучающая (Train): Данные до YYYY-MM-DD (около 13 лет).
Валидационная (Validation): Данные за 1 год (YYYY-MM-DD - YYYY-MM-DD).
Тестовая (Test): Данные за последний 1 год (YYYY-MM-DD - YYYY-MM-DD).
Масштабирование: Все признаки (features) и целевые переменные (targets) для нейросетевых моделей были нормализованы в диапазон [0, 1] с помощью MinMaxScaler, обученного только на обучающей выборке.
Метрики качества: Для количественной оценки точности прогнозов была выбрана Средняя абсолютная ошибка (MAE), которая измеряет среднее отклонение прогноза от фактического значения в градусах Цельсия.

3.2. Описание моделей-участников
Для проведения сравнительного анализа были выбраны три модели, представляющие различные подходы к прогнозированию временных рядов.
-
ARIMA (Авторегрессионная интегрированная скользящая средняя)
Роль: Статистический бейзлайн. Данная модель используется для оценки базовой предсказуемости ряда и как точка отсчета для более сложных методов.
Реализация: Так как классическая ARIMA является одномерной моделью, было обучено три независимые модели для каждой из трех целевых переменных (temperature_morning, temperature_day, temperature_evening). Гиперпараметры (p=5, d=1, q=1) были выбраны на основе анализа ACF/PACF и для демонстрационных целей.
-
N-BEATS (Neural Basis Expansion Analysis for Interpretable Time Series Forecasting)
Роль: Представитель современных SOTA-архитектур (State-of-the-Art), реализованный в библиотеке Darts.
Реализация: Модель была сконфигурирована для многомерного прогнозирования (output_dim=3). Важно отметить, что N-BEATS в данной конфигурации работает по принципу "end-to-end" и использует для прогноза только историю самих временных рядов, без привлечения внешних инженерных признаков. Это позволяет оценить эффективность мощной архитектуры на "сырых" (масштабированных) данных.
-
Custom LSTM (Long Short-Term Memory)
Роль: Кастомное решение, разработанное для проверки гипотезы о том, что инжиниринг признаков может дать преимущество даже простой нейросетевой архитектуре.
-
Реализация: Была реализована базовая LSTM-архитектура на PyTorch. Модель состоит из двух ключевых слоев:
Один слой LSTM (HIDDEN_SIZE=256, dropout=0.2), который обрабатывает последовательность входных признаков.
Один Linear слой, который преобразует выход LSTM в итоговый прогноз из трех значений.
Ключевое отличие этой модели — несмотря на свою архитектурную простоту, она принимает на вход полный набор из 28 инженерных признаков, созданных на предыдущем этапе (циклические, лаговые, метеорологические показатели). Таким образом, основной акцент делается не на сложности самой нейросети, а на качестве и полноте данных, которые в нее подаются.
Код кастомной LSTM:
class LSTMForecaster(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, output_size):
super().__init__()
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=0.2)
self.linear = nn.Linear(hidden_size, output_size)
def forward(self, x):
lstm_out, _ = self.lstm(x)
prediction = self.linear(lstm_out[:, -1, :])
return prediction
3.3. Процесс обучения кастомной LSTM
Для обеспечения качества и стабильности обучения кастомной модели был реализован профессиональный цикл train-validate-test:
Оптимизатор и функция потерь: Использовался оптимизатор Adam с начальной скоростью обучения lr=0.001 и функция потерь MSELoss.
Планировщик скорости обучения (Learning Rate Scheduler): Был применен ReduceLROnPlateau, который автоматически снижал скорость обучения, если ошибка на валидационной выборке переставала улучшаться в течение 5 эпох.
Ранняя остановка (Early Stopping): Процесс обучения автоматически прерывался, если ошибка на валидационной выборке не показывала улучшения в течение 10 последовательных эпох. Для итогового прогноза использовалась версия модели с наилучшей валидационной ошибкой, сохраненная в процессе обучения.

Такой подход позволил автоматически найти оптимальное количество эпох для обучения (~30 из 150 запланированных) и предотвратить переобучение модели.
4. Заключение. Результаты и их обсуждение
Финальным шагом эксперимента являлось обучение всех трех моделей на обучающей выборке и генерация прогнозов на тестовый период (последний год данных).
4.1. Сравнительный визуальный анализ прогнозов

Визуальный анализ графиков позволяет сделать следующие выводы:
ARIMA: Прогноз модели представляет собой практически горизонтальную линию, соответствующую среднему значению ряда. Модель оказалась неспособна уловить ни годовую сезонность, ни ежедневные колебания, что свидетельствует о ее неприменимости для данной задачи в базовой конфигурации.
N-BEATS: Модель успешно идентифицировала и воспроизвела основной низкочастотный паттерн — годовую сезонность. Ее прогноз имеет характерную синусоидальную форму. Однако, модель полностью проигнорировала высокочастотную компоненту ("шум"), генерируя излишне сглаженный прогноз, не отражающий резких локальных изменений погоды.
Custom LSTM: Прогноз данной модели демонстрирует наилучшее соответствие реальным данным. Красная пунктирная линия практически повторяет траекторию фактических значений. Модель смогла успешно выучить как глобальную сезонную волну, так и локальные, ежедневные колебания температуры.
4.2. Количественная оценка
Для объективного сравнения была рассчитана метрика MAE (Mean Absolute Error) для каждой модели по каждой из трех целевых переменных.
Итоговые метрики качества моделей (MAE, °C).
Модель |
MAE temp_morning (°C) |
MAE temp_day (°C) |
MAE temp_evening (°C) |
ARIMA |
10.04 |
11.31 |
10.72 |
N-BEATS |
15.52 |
16.13 |
15.63 |
Custom LSTM |
2.89 |
2.94 |
3.25 |
Количественные результаты полностью подтверждают выводы визуального анализа. Кастомная LSTM-модель демонстрирует снижение средней абсолютной ошибки в 3-5 раз по сравнению с обоими бенчмарками. Интересно отметить, что более сложная архитектура N-BEATS показала ошибку даже выше, чем у простого статистического бейзлайна, что подчеркивает ее неспособность справиться с задачей без дополнительных данных.
4.3. Интерпретация результатов
Высокая точность кастомной LSTM-модели объясняется не столько сложностью самой рекуррентной архитектуры, сколько ее способностью эффективно использовать богатый контекст, предоставленный на этапе инжиниринга признаков. В отличие от N-BEATS, которая оперировала только историей целевого ряда, LSTM имела доступ к более чем 30 признакам, включая:
Информацию о сезонности (через sin/cos преобразования).
Информацию о недавнем прошлом (лаговые признаки).
Данные о других метеорологических факторах (давление, облачность, ветер).
Именно этот расширенный набор признаков позволил даже простой LSTM-архитектуре выучить сложные нелинейные зависимости и генерировать значительно более точные прогнозы.
Заключение
В рамках данного проекта был успешно реализован полный E2E-пайплайн для прогнозирования временных рядов. Проведенный сравнительный анализ моделей позволил сделать следующие ключевые выводы:
Продемонстрирована эффективность системного подхода, охватывающего все этапы от сбора данных до обучения моделей.
Подтверждена критическая важность инжиниринга признаков. Качественная подготовка и обогащение данных позволили относительно простой кастомной архитектуре LSTM значительно превзойти по точности более сложную SOTA-модель.
Описан и реализован отказоустойчивый, параллельный скрапер, чей подход (проактивная генерация URL, парсинг JS-объектов) может быть применен для решения широкого круга задач по сбору данных.
Перспективы для дальнейшей работы включают исследование более сложных архитектур (Transformers), автоматизированную оптимизацию гиперпараметров.
Исходный код проекта доступен в репозитории на GitHub: https://github.com/Runoi/WeatherForecast
Послесловие
В заключение, хотелось бы добавить небольшое пояснение. Эта статья, как и две предыдущие, является результатом систематизации и документирования уже завершенных проектов. Работа над ними велась не в реальном времени, а представляла собой ретроспективный анализ инженерных решений, принятых на каждом из этапов.
Данная публикация завершает этот цикл описания готовых наработок. Новые проекты потребуют времени на исследование и разработку с нуля, поэтому будущие статьи, вероятно, будут выходить с большими интервалами.
Спасибо за ваше внимание к моим работам!
ChePeter
Вот тут решали похожую задачу и получили Нобелевскую премию
https://hij.ru/read/27463/