
Когда ваш прогноз летит в помойку
Топ-менеджмент использует прогнозы для решений: нужно ли прямо сейчас сжигать дополнительные бюджеты в маркетинге и снижать цены, чтобы дотянуть до плана? Или же наоборот – факт летит выше прогноза, и можно придержать скидки (хотя в этом случае инвестор радостно повысит свои ожидания на следующий месяц).
Проблема начинается тогда, когда алгоритм «в среднем» рисует красивые цифры, а в критический месяц реальность расходится с прогнозом на 20%. Это больно.
Представьте ситуацию. Вы на бизнес-ревью. Вы принесли замечательный прогноз: средняя абсолютная процентная ошибка (MAPE) по стране составила 4.5%. Трава зеленая, мир, дружба, жвачка, вы гордитесь собой. Но после недавнего «инцидента» в голову Большого Директора закрадываются параноидальные мысли, а не математика. Он смотрит на прогноз и говорит:
– Слушай, два месяца назад твой алгоритм пообещал, что мы легко выполним план. Мы расслабились, не стали запускать промо, а по факту продажи оказались на 20% ниже. Мы чудом избежали гнева Большого Инвестора. Сейчас твоя модель снова рисует, что мы идем ровно в цель. Я в это больше не верю! Я уверен, что мы снова недотянем. Давайте директивно заложим в уме риск недовыполнения и прямо сейчас вольем дополнительный миллиард в маркетинг, чтобы закрыть эту дыру. Нет, стоп! Давай сразу сожжем два миллиарда сверху, чтобы наверняка!
И всё. Ваш мир рушится. Трава желтеет, градиентный бустинг больше не бустит, потому что бизнес управляет процессом через панику и ручные корректировки, а не через ваши цифры.
Это большая головная боль любого, кто занимается прогнозами. И проблема здесь не техническая.
Проблема в том, что бизнес не доверяет цифрам, потому что один раз обжегся. Один промах на 20% перевешивает 12 месяцев точных прогнозов.
И пока доверия нет, любой прогноз превращается в точку отсчёта для торга: «А вдруг снова ошибётесь? Давайте заложим риск. Давайте сожжём ещё миллиард сверху, на всякий случай». Прогноз перестаёт управлять решениями – он становится поводом для паники.
Поэтому бизнесу в конечном счёте не так важно, какая у вас средняя ошибка. Ему важно одно: предсказуемость. Он хочет знать, что вы не ошибётесь катастрофически. Никогда. Он хочет получить ответ на один простой вопрос: мы закроем этот месяц в план или нет? Нужно ли прямо сейчас жечь бюджет на промо – или можно ехать кататься на лыжах.
Именно поэтому мой путь был не про то, чтобы снизить среднюю ошибку. Он был про то, чтобы построить прогноз, которому можно верить.
О чём эта статья
-
С чего мы начали: зоопарк прогнозов и недоверие бизнеса
-
Как Excel-сезонка обогнала Prophet
-
Как мы начали мерить качество по-взрослому
-
Что удалось и не удалось выжать из классики
-
Зачем пришлось спускаться на уровень городов и идти в ML
-
Как TSMixer обошёл baseline – и где всё ещё позади
-
Почему финальным ответом стал ансамбль
-
Что на самом деле было целью всей этой истории
Часть 1. Наш ландшафт и зоопарк моделей
Немного контекста про наши данные. Мы – крупный сервис быстрой доставки.
Нам нужно выдать бизнесу железобетонный прогноз продаж, заказов и выручки на 45 дней вперёд на уровне всей страны. Да, физически мы доставляем миллионы заказов в 79 городах (о городах мы ещё вспомним, когда дойдём до машинного обучения), но Большого Директора интересует итоговый результат по компании.
Что такое ЦФЗ
ЦФЗ – центр формирования заказа, или даркстор. Небольшой склад в жилом районе, откуда курьеры забирают заказы. Покупатели туда не приходят. У нас их несколько тысяч по всей стране.
Для тех, кто работает в Excel: что такое Prophet и почему его все любят
Prophet – популярный алгоритм от разработчиков запрещённой ныне Meta. В мире Data Science это что-то вроде кнопки «сделать красиво»: быстрый, простой и часто дающий хорошие результаты.
Как он работает: он берёт ваш график продаж и раскладывает его на три части:
-
Общий тренд – растём или падаем в долгосроке.
-
Сезонность – по дням недели, месяцам, годам.
-
Праздники и аномалии.
Дополнительно Prophet поддерживает внешние признаки – например, маркетинг, цены, макроэкономические факторы.
Но есть важное ограничение: их значения должны быть заранее известны на будущем горизонте прогноза.
Ещё одна особенность: Prophet работает только с одним временным рядом.
С чего мы начинали: кладбище мёртвых животных
Изначально у нас был настоящий зоопарк.
Prophet на каждый ЦФЗ
Кто-то из операционного планирования пошёл в DS-департамент за прогнозами, и ему радостно ответили:
– Прогнозы? А у нас уже есть, вот для логистики на уровне дарксторов (ЦФЗ), забирайте. Только новых ЦФЗ там нет, но у нас же есть планы по открытиям.
– Ну новые ЦФЗ в среднем будут такие же, как и средний ЦФЗ в городе.
Конечно, планы по открытиям были. Вопрос только в том, насколько они правдивы и насколько вообще правда, что новый ЦФЗ будет похож на средний по городу.
Под капотом – отдельный Prophet на каждый ЦФЗ. Несколько тысяч моделей, и прогноз готов. Казалось бы: чем больше моделей, тем точнее? Но здесь всё сложнее.
Проблем было сразу несколько:
-
Точность на уровне страны никто не считал. Модели оценивались на уровне отдельных ЦФЗ. А что получается, когда складываешь тысячи таких прогнозов в одну цифру – никто не проверял.
-
Новые ЦФЗ были дырой в модели. Компания постоянно открывает новые ЦФЗ, а их в модели нет. Значит, их вклад в итоговую цифру – это экспертная догадка в Excel поверх ML.
-
Исходная задача была другой. Этот прогноз изначально делался для логистики: сколько курьеров нужно в конкретном дарксторе завтра. Для этого важна точность на каждый конкретный день. Менеджменту важно другое: как мы закроем месяц, идём ли в план прямо сейчас, нужно ли запускать промо. А это уже про накопленную ошибку за период – и оптимизировать под неё нужно совсем иначе.
Годовая сезонность
Группа бизнес-анализа пошла другим путём и решила сама делать прогнозы в Excel.
Суть метода была гениальна в своей простоте: мы берём факт продаж за ту же неделю прошлого года и умножаем на коэффициент текущего роста.
Грубо говоря, если на прошлой неделе мы продавали на 15% больше, чем год назад, то и на следующие 45 дней закладываем прошлогодний график, умноженный на 1.15.
Единственный минус: точность оценивали буквально глазами.
И снова Prophet – но уже на уровне страны
Неназванный департамент решил взять всё в свои руки и поставил задачу: аналитик подобрал гиперпараметры, упаковал модель в Airflow. Средняя ошибка – то 1%, то 2%. Казалось бы, наконец-то победа.
Для тех, кто работает в Excel: что такое гиперпараметры
Гиперпараметры – это настройки модели, которые задаются вручную до обучения. Модель не подбирает их сама – она работает в рамках тех настроек, которые ей дали.
Например:
-
На сколько дней назад смотреть при прогнозе – 90, 180 или 365 дней?
-
Насколько сильно штрафовать за крупные ошибки?
-
Сколько слоёв должно быть в нейросети?
Подбор гиперпараметров – это отдельная задача: перебираем много вариантов, для каждого смотрим качество и выбираем лучший.
Но топ-менеджмент почему-то всё равно не верил цифрам и продолжал вносить правки руками.
Странно, правда? Такая низкая ошибка – и всё равно недоверие.
Разгадка пришла позже: точность считалась на двухнедельной валидации при горизонте прогноза в 45 дней. И отрезок для валидации каждую неделю был разный. Красивые проценты оказались артефактом неправильного измерения, а не реальной точностью.
Для тех, кто работает в Excel: что такое валидация
Валидация – это проверка модели на исторических данных, которых она не видела во время обучения.
Например: обучаем модель на данных январь–октябрь, а затем смотрим, насколько точно она предсказывает ноябрь–декабрь – период, который она не видела.
Если на обучающих данных ошибка 2%, а на валидации 15%, значит модель просто запомнила историю наизусть, но не научилась предсказывать новое. Особенно это актуально для больших и сложных моделей.
Были и другие экспертные Excel-подходы от разных людей. Их невозможно было толком описать и невозможно воспроизвести – кто во что горазд.
С этим зоопарком прогнозов мне предстояло разобраться одному: восстановить старые подходы, построить честный бэктест, выбрать рабочий baseline, проверить альтернативы и в итоге собрать новый контур целиком.
Эксгумация
Первым делом перед созданием П.Е.С.Е.Ц. (Прогноз Единый Стратегический Ежедневный Целевой) нужно было разобраться, как работают текущие модели. Надо же было с чем-то сравнивать, чтобы понять: мы лучше в два раза или в три.
Я решил провести честный 12-месячный rolling backtest, чтобы сравнивать подходы в одной системе координат. Пардон, хотел провести.
Для тех, кто работает в Excel: что такое Rolling Backtest
Валидация – это разовая проверка на одном периоде. Но одна проверка может обмануть.
Если проверяли на октябре, то, возможно, октябрь оказался спокойным: без акций и аномалий. Модель выглядит отлично. А в декабре с праздниками – провалится.
Rolling Backtest (скользящий бэктест) – это много валидаций подряд на разных периодах.
Представьте, что вы стоите 1 января и хотите сделать прогноз на 45 дней вперёд. У вас есть только данные до 31 декабря. Вы делаете прогноз и записываете его.
Затем наступает 2 января. У вас появляются реальные продажи за 1-е число. Вы заново делаете прогноз на 45 дней, но уже с учётом новой информации.
Rolling Backtest – это симуляция такого процесса на истории. Мы берём прошлый год и проходим его день за днём:
-
День 1: обучили модель на данных до 1 января → сделали прогноз.
-
День 2: добавили факт 1 января → переобучили → сделали прогноз.
-
…
-
День 365: …
Rolling Backtest проверяет модель во всех состояниях системы: спокойные периоды, рост, падение, акции, праздники, сбои.
Это единственный способ честно оценить модель.
С годовой сезонностью в Excel проблем не возникло: я воспроизвёл её логику на Python и прогнал бэктест. Напомню логику: берём факт продаж за ту же неделю прошлого года и умножаем на коэффициент текущего роста. Это наш бейзлайн, точка отсчёта, хуже которой быть не должно.
А вот с ML-моделями начались квесты.
Квест 1. Prophet на уровне ЦФЗ
Собрать честный исторический бэктест по этой модели оказалось той ещё задачей.
-
Не так давно ребята значительно улучшили свои прогнозы, но из-за смены методологии достать старые исторические прогнозы оказалось проблематично.
-
Прогнозы они делали только раз в неделю: гонять несколько тысяч Prophet каждый день вычислительно накладно.
-
Главная проблема – логика учёта новых ЦФЗ уже реализовывалась на стороне бизнеса в Excel и была полностью экспертной.
Воспроизвести эту экспертность для бэктеста невозможно. Поэтому пришлось смотреть точность только на существующих на тот момент ЦФЗ. Как следствие, результат этого бэктеста изначально был несколько оптимистичным.
Квест 2. Prophet на уровне страны
Но был и ещё одна методология – тот самый Prophet на уровне страны от аналитиков. Я взял их методологию и прогнал её через свой бэктест.
Под капотом оказалось много всего интересного. В модель как дополнительные признаки протягивались в будущее макроэкономика, инфляция и ставка ЦБ по последнему известному значению на момент прогноза. Также как признаки использовался классический набор праздников.
А самое интересное – туда пихали «гибридные» ряды по скидкам: на истории модель смотрела на фактические скидки, а на горизонт прогноза 45 дней ей скармливали плановые скидки от бизнеса, которые, спойлер, почти никогда не выполнялись в реальности.
Я начал с очевидного: убрал гибридные ряды плановых скидок. План никогда не равен факту, а значит, на горизонте прогноза модель смотрела на заведомо неверные данные.
Затем я убрал протягивание макроэкономики по последнему известному значению. Качество немного улучшилось.
После этого я отключил динамический подбор гиперпараметров. Каждую неделю модель переподбирала их под уже известный период – и рисковала потерять способность ловить следующий. Я зафиксировал единые параметры на весь бэктест.
Помогло ли это? Нет.
Графики этого бэктеста я даже не буду приводить – они остались лишь в моей памяти (звук удара тарелками в голове). Профиль ошибки оказался очень похожим на сезонку, но ещё более рваным и нестабильным, что мы и сейчас увидим ниже.

Первое сравнение моделей
Показывать сразу все три метрики – заказы, штуки и выручку – не очень интересно: они ведут себя похоже. Заказы прогнозируются чуть легче, штуки – чуть тяжелее. Поэтому дальше будем смотреть на выручку.
Первое сравнение моделей дало неожиданный результат:
-
Prophet по ЦФЗ: MAPE 5.56%
-
Тюнингованный Prophet по стране: MAPE ~4.5%
-
Excel-сезонка (бейзлайн): MAPE 3.41%
Важно отметить один нюанс: это сравнение пока сделано не на полном годовом бэктесте. Исторические прогнозы от DS-департамента удалось восстановить только за ограниченный период.
Простая Excel-сезонность уверенно обошла оба варианта Prophet.
Для нашей задачи ошибка на уровне 3–4% считается очень сильным результатом. Это поставило под сомнение саму исходную гипотезу проекта – что мы можем значительно улучшить точность.
Но прежде чем делать выводы, давайте посмотрим на динамику ошибки во времени.
Важно: на графике «Динамика MAPE/WMAPE» каждая точка – это не ошибка за один конкретный день. Это среднее отклонение по всему горизонту прогноза (следующие 45 дней). То есть, если 1 мая точка стоит на 10%, значит прогноз, сделанный 1 мая на полтора месяца вперёд, в среднем ошибался на 10% каждый день.
Ниже – динамика MAPE/WMAPE по датам прогноза для Prophet и Excel-сезонки.
Что мы здесь видим: жёлтая линия (Excel) стабильно ниже голубой (Prophet). На протяжении почти всего периода Prophet даёт более высокую ошибку, чем простая формула сезонности.
Отдельно стоит обратить внимание на конец апреля. В этот период обе модели показывают резкий рост ошибки: у Prophet она достигает ~25%, у сезонки – около 14%. Это совпадает с периодом сильного промо-давления. Обе модели реагируют на такие изменения плохо, однако даже в этом случае простая сезонность оказывается примерно вдвое точнее.
Prophet здесь окончательно выбывает. Дальше смотрим только на сезонку на полном бэктесте. Но остаётся вопрос: а как она поведёт себя на длинной дистанции? Удержит ли свои красивые 3.41%, если посмотреть на год целиком?
Ниже – динамика MAPE по датам прогноза уже на расширенном бэктесте сезонки.
Что мы видим на расширенном бэктесте?
Рост средней ошибки. Средний MAPE увеличивается до более реалистичных 4.55%. Это всё ещё хороший результат для бейзлайна, но уже не та «магия», которую мы видели на коротком периоде.
Пики ошибок в отдельных периодах. Особенно заметны апрель и декабрь – в эти моменты ошибка поднимается выше 18%.
Excel-сезонка – это жёсткий алгоритм. Он исходит из гипотезы, что текущий год будет структурной копией прошлого. В стабильные периоды это работает отлично. Но когда происходит событие, которого не было год назад – смещение сезона, агрессивные промо-кампании или погодные аномалии – модель продолжает ожидать «прошлогоднего» паттерна и не успевает адаптироваться.
Также интересно посмотреть, как качество прогноза зависит от горизонта.
Ниже – MAPE в зависимости от горизонта прогноза.
На графике по оси X – горизонт прогноза: 1 – прогноз на завтра, 45 – прогноз примерно на полтора месяца вперёд.
По оси Y – средняя ошибка (MAPE).
Если прогнозировать на завтра, ошибка составляет около 3.5%, а на 45-й день уже превышает 5%. Это ожидаемое поведение, но в нашем случае важен сам масштаб деградации: качество практически падает вдвое.
Бизнесу часто важнее не то, как мы в среднем ошибаемся по дням, а то, насколько мы смещаем весь прогноз целиком – завышаем его или занижаем.
Для этого посмотрим на WBIAS (Weighted Bias) – отклонение суммы прогноза от суммы факта на всём горизонте прогноза в 45 дней.
Средний WBIAS составил 0.86%. На первый взгляд это отличный результат: в среднем прогноз почти не смещён. Но среднее значение может скрывать важные детали, поэтому дальше смотрим на динамику.
Ниже – WBIAS по датам прогноза.
В спокойные периоды отклонение невелико, но в отдельные моменты ошибка резко возрастает.
Теперь посмотрим на знаковый WBIAS, чтобы понять направление ошибки.
Положительные значения означают перепрогноз, отрицательные – недопрогноз.
Ниже – знаковый WBIAS по датам прогноза.
В спокойные периоды смещение действительно близко к нулю, что хорошо для бюджетного планирования. Однако в шоковые периоды, например, в апреле или декабре, систематическая ошибка может достигать 15–18%.
И наконец посмотрим, как смещение ведёт себя по мере удаления горизонта прогноза.
Ниже – WBIAS в зависимости от горизонта прогноза.
По мере увеличения горизонта прогноз действительно начинает систематически завышаться. Это ожидаемое поведение: небольшие ошибки на ранних шагах постепенно складываются и приводят к заметному bias на дальних горизонтах – примерно с 0.25% до 1.75%.
И наконец – самая важная метрика для топ-менеджмента.
Часто точность прогноза по дням мало кого интересует. Главный вопрос директора звучит гораздо проще: «Мы закроем месяц по плану или нет?»
Чтобы ответить на него, введём метрику EOM (Error End of Month). Она показывает, насколько точно мы предсказываем итог месяца, находясь в конкретном дне.
Ниже – абсолютная ошибка EOM по датам прогноза.
Эта метрика показывает, насколько сильно прогноз месячного итога отличается от факта.
Теперь посмотрим на знаковую ошибку EOM, чтобы понять направление отклонения.
Положительные значения означают перепрогноз, отрицательные – недопрогноз.
Ниже – знаковый EOM по датам прогноза.
Здесь заметна ещё одна проблема: иногда буквально за один день прогноз может измениться от перепрогноза к недопрогнозу.
Это может выглядеть как обычная волатильность модели, но для бизнеса такие колебания очень чувствительны. Сегодня прогноз выглядит «красиво» и его показывают руководству, а на следующий день новая версия прогноза уже рисует противоположную картину.
Такая нестабильность – отдельная проблема. Даже если средняя ошибка модели невысока, сильные колебания прогноза между соседними днями подрывают доверие к системе прогнозирования.
Теперь взглянем, как уточняется прогноз по мере приближения к концу месяца?
Ниже – EOM в зависимости от расстояния до конца месяца.
Если собрать все метрики вместе, картина получается такой: сезонка неплохо работает как baseline, но её устойчивость ограничена. На уровне отдельных дней она регулярно шумит, особенно в нестандартные периоды, а по мере удаления горизонта её ошибка заметно растёт. При этом по накопительным метрикам картина лучше: средний WBIAS остаётся близким к нулю, а по EOM сезонка часто довольно точно угадывает итог месяца.
Причина в самой природе такого алгоритма. Внутри месяца многие факторы не исчезают, а лишь перераспределяются во времени: маркетинговый эффект может сработать 10-го числа или 20-го, похолодание – прийти чуть раньше или позже, пользователь – сделать заказ в понедельник или в среду. Поэтому на дневном уровне сезонка может промахиваться, но на месячном её ошибки нередко взаимно компенсируются.
Иначе говоря, сезонка довольно неплохо угадывает итог месяца, но плохо предсказывает, когда именно внутри месяца произойдут всплески и просадки.
Часть 2. Эксперименты с классикой: можно ли выжать больше из бейзлайнов?
Первое, что приходит в голову: раз Excel-сезонка так хороша, можем ли мы её прокачать?
Напомню логику: мы берём факт продаж за ту же неделю прошлого года и умножаем на коэффициент текущего роста. Что если просто покрутить эти ручки?
Я сделал грубый перебор:
-
Смещение на ровно год вместо смещения на 364 дня (чтобы совпадали день и число)? → Мимо
-
Окно коэффициента 14 дней вместо 7? → Минорный прирост: MAPE с 4.56% до 4.49%
-
Линейные/экспоненциальные веса внутри окна? → Ноль эффекта
-
Микс 364 + 365 дней? → Тоже ничего
По итогам лучшее, что удалось выжать – поменять окно коэффициента с 7 на 14 дней.
-
MAPEупал на 0.07% -
EOM(ошибка конца месяца) чуть снизился с 1.09% до 1.04% -
WBIASнесколько вырос с 0.86% до 0.93%
Приростом это назвать сложно, но попробовать стоило.
Если вернуться к картинкам, один положительный сигнал всё же есть: пики ошибок снизились – с более чем 17.5% до примерно 13%. Неплохо, но это всё ещё не то, к чему мы стремимся. Хотя стоит признать: такой перебор занимает буквально пару минут и почти ничего не стоит вычислительно.
Ниже – WAPE по датам прогноза для сезонки с окном 14 дней.
По EOM ситуация аналогичная, а по WBIAS, несмотря на рост средней ошибки, пики тоже снизились.
Теоретически сезонку можно продолжать усложнять: добавлять корректировки на праздники и выходные, вводить специальные коэффициенты для промо-периодов. Однако подобные правки вряд ли способны решить её главную проблему. Они могут немного сгладить ошибку или сдвинуть отдельные пики, но принципиально изменить поведение модели почти невозможно – слишком ограничена сама структура такого бейзлайна.
Хватит смотреть на среднюю температуру
Тут мы упираемся в проблему: каждый раз разглядывать графики и искать глазами, где там снизился пик ошибки – приятно, но накладно. А смотреть только на средний MAPE опасно: он не даёт полной картины.
Поэтому введём ещё несколько метрик качества:
-
Доля отличных дней (
WAPE < 5%) – процент дней, когда прогноз попадает «в яблочко». -
Доля хороших дней (
WAPE < 10%) – процент дней, когда ошибка не вызывает желания уволить аналитика. -
Скорость деградации – насколько быстро ошибка растёт по мере удаления горизонта прогноза (а для
EOM– насколько быстро она снижается по мере приближения к концу месяца).
Посмотрим на нашу улучшенную сезонку через эту призму.
|
Метрика |
Значение |
Комментарий |
|
WAPE < 10% |
96.38% |
Грубых провалов немного. Но нам нужно чтобы их не было вовсе. |
|
WAPE < 5% |
64.80% |
Результат довольно слабый: точное попадание происходит только примерно в 2/3 дней. |
|
Degrad: WAPE/day |
0.05% / day |
Это означает, что к концу горизонта ошибка почти удваивается. |
|
|WBIAS| < 10% |
97.70% |
Аналогично, здесь мы хотим чтобы провалов также не было |
|
|WBIAS| < 5% |
90.79% |
Результат сильно лучше чем по WAPE |
|
Degrad: |WBIAS|/day |
0.04% / day |
Аналогично WAPE |
|
EOM < 5% |
98.30% |
Это отличный результат. Близок к Идеалу. |
|
EOM < 2.5% |
91.50% |
Тоже вполне неплохо. |
|
Degrad: EOM/day |
0.06% / day |
В контексте EOM это приемлемо |
Иными словами, примерно в каждом третьем дне ошибка превышает 5%. Для бизнеса это означает простую вещь: часто прогноз выглядит «почти правильным», но недостаточно надёжным, чтобы спокойно принимать по нему решения.
Хорошо, но не сезонкой же единой. В мире существуют и другие классические алгоритмы. Например, возьмём SARIMAX и FourTheta и прямо на бэктесте подберём для них оптимальные гиперпараметры – благо вычислительно это тоже стоит копейки.
Скрытый текст
Метод Theta разбирает временной ряд на две основные компоненты.
-
Тренд, усиленный коэффициентом θ (
theta) Это обычная линейная регрессия, но её наклон умножается на параметрθ. Приθ > 1тренд усиливается, приθ < 1– сглаживается. По сути, это ручка, которая регулирует, насколько агрессивно модель экстраполирует рост или падение. -
Остатки, которые прогнозируются экспоненциальным сглаживанием (
SES) Из ряда вычитается тренд, и то, что осталось, прогнозируется черезSES– взвешенное среднее, где свежие наблюдения весят больше.
Приставка Four (Fourier) добавляет третий слой – сезонность через суперпозицию синусоид. Вместо отдельных коэффициентов на каждый день недели модель аппроксимирует сезонный паттерн суммой гармоник с фиксированными периодами: 7 дней, 365 дней и т.д.
Итого:
Прогноз = θ-тренд + SES(остатки) + Σ синусоид(сезонность)
Для тех, кто работает в Excel: что такое ARIMA/SARIMAX и почему она ломается на 45-м дне
Давайте соберём ARIMA прямо в Excel. Никакой магии – это обычная формула.
Шаг 1. Инерция (p)
Продажи инертны: если вчера продали 10 000, сегодня вряд ли будет 3 000. Самый простой прогноз – взять вчера и позавчера с разными весами.
В Excel это буквально:
Прогноз = 0.6 × Вчера + 0.4 × Позавчера
Параметр p = 2 просто означает, что в формуле участвуют два прошлых дня. Веса алгоритм подберёт сам.
Шаг 2. Работа над ошибками (q)
Вчера вы предсказали 10 000, а пришло 10 300. Ошибка = +300. Логично учесть это сегодня:
Прогноз = [Инерция] + 0.3 × Ошибка_Вчера
Параметр q = 1 означает: в формулу добавляется одна прошлая ошибка.
Шаг 3. Сезонность и тренд
Но понедельник не похож на субботу. Сравнивать вчера и сегодня мало, поэтому добавляется сезонный блок (1, 1, 0, 7):
-
7 (S)– цикл равен 7 дням -
первая
1 (P)– смотрим на один прошлый такой же день -
вторая
1 (D)– берём разницу между прошлыми однотипными днями -
0 (Q)– старые сезонные ошибки не учитываем
Параметр trend=None означает: не дорисовывай искусственную прямую линию роста, а извлекай динамику из самих данных.
Как модель предсказывает будущее дальше чем на 1 день?
Представьте, что у вас таблица, где сегодня 30 апреля. Вам нужно заполнить пустые строки до 15 июня.
День 1 (прогноз на 1 мая)
Всё работает идеально: формула берёт реальные факты за 30 и 29 апреля и выдаёт прогноз.
День 2 (прогноз на 2 мая)
Теперь формула просит Факт_за_1_мая, но факта ещё нет. Поэтому модель подставляет туда свой же прогноз за 1 мая.
День 45 (прогноз на 15 июня)
К этому моменту модель уже строит прогнозы на прогнозах, а те – на прогнозах от предыдущих прогнозов. Это рекурсивный режим: модель буквально питается собственными галлюцинациями.
Именно поэтому такая классика шикарно работает на горизонте в несколько дней, но начинает разваливаться, когда бизнесу нужно знать итог следующего месяца.
Результат не впечатлил
|
Метрика |
Сезонка (14д) |
FourTheta |
SARIMAX |
|
WAPE |
4.49% |
5.85% |
5.94% |
|
WAPE < 5% (отл.) |
64.8% |
42.4% |
39.1% |
|
WAPE < 10% (хор.) |
96.4% |
95.1% |
96.1% |
|
Деградация WAPE |
0.05% / день |
0.10% / день |
0.09% / день |
|
WBIAS |
0.93% |
0.65% |
-0.33% |
|
|WBIAS| < 5% (отл.) |
90.8% |
66.5% |
69.7% |
|
EOM |
1.04% |
1.60% |
1.76% |
|
EOM < 2.5% (отл.) |
91.5% |
75.2% |
70.1% |
|
EOM < 5% (хор.) |
98.3% |
94.9% |
95.2% |
|
Деградация EOM |
0.06% / день |
0.13% / день |
0.15% / день |
FourTheta и SARIMAX проигрывают сезонке по всем фронтам – и по средней ошибке, и по стабильности. Но есть два момента, которые важно не пропустить.
Первый – деградация.
Сезонка теряет 0.05% точности за каждый дополнительный день горизонта. Классика – примерно вдвое быстрее. К 45-му дню это уже не мелочь.
Второй – направление ошибки.
Сезонка систематически занижает прогноз (BIAS +0.93%), а SARIMAX, наоборот, завышает (-0.33%). Они не просто ошибаются по-разному – они ошибаются в противоположные стороны. И делают это в разное время года.
Ниже – динамика WBIAS по датам прогноза.
В июне сезонка уходит в минус, SARIMAX – в плюс. В октябре картина зеркальная. Именно здесь впервые появляется идея будущего ансамбля: там, где одна модель промахивается вверх, другая тянет вниз – и в среднем они частично гасят друг друга.
Классика выжата досуха по отдельности, но возможно вместе они могут дать то чего не даёт ни одна из них.
Для тех, кто работает в Excel: почему среднее из моделей с ошибкой 4.5% и 4.0% может дать 3.5%
На первый взгляд кажется, что если взять одну модель с ошибкой 4.5% и вторую с ошибкой 4.0%, то их совместный прогноз даст что-то среднее – около 4.25%. Но на самом деле ансамбль может показать ошибку 3.5%, побив обоих родителей.
Секрет – в корреляции ошибок.
Допустим, реальный факт завтра – 100 заказов.
Сезонка предсказывает 115 (ошибка +15), а ARIMA – 85 (ошибка -15). У обеих моделей MAPE = 15%. Но если усреднить прогнозы:
(115 + 85) / 2 = 100
то ошибка ансамбля окажется 0%.
Чтобы эта магия сработала, ошибки моделей должны быть раскоррелированы. Если обе модели одновременно тянут прогноз вверх, ансамбль не спасёт – вы просто получите усреднённый оверпрогноз. Но если модели устроены по-разному, то в одном периоде одна может быть слишком оптимистичной, а другая – слишком пессимистичной, и они начнут гасить ошибки друг друга.
Как это заметить на практике? Даже без матриц корреляции можно посмотреть на динамику MAPE, BIAS и EOM по месяцам. Часто там видна своеобразная шахматка: в апреле сезонка улетает вверх, а ARIMA держится лучше; в декабре – наоборот.
Именно поэтому ансамбль может дать двойной выигрыш:
-
на микроуровне – сгладить выбросы на отдельных днях;
-
на макроуровне – сгладить провальные месяцы и повысить стабильность прогноза.
Сила ансамблей – и почему всё-таки не 3.5%
Именно на это я и сделал ставку. Нагенерил в цикле несколько десятков вариаций классических моделей и собрал из них все разумные комбинации ансамблей – пары, тройки, четвёрки. В топ ожидаемо вырвался ансамбль из годовой сезонки и ARIMA.
|
Ансамбль |
WAPE |
BIAS |
EOM |
|
Сезонка (14д) + ARIMA |
4.32% |
0.46% |
1.11% |
|
Сезонка (14д) + FourTheta |
4.35% |
0.61% |
1.09% |
|
Сезонка (14д) – solo |
4.49% |
0.93% |
1.04% |
Что произошло?
-
WAPEупал с 4.49% до 4.32% -
BIASснизился с 0.93% до 0.46%
Звучит неплохо – пока не смотришь глубже.
|
Метрика |
Сезонка (solo) |
+ ARIMA |
+ FourTheta |
|
WAPE < 5% (отл.) |
64.8% |
66.5% |
66.5% |
|
WAPE < 10% (хор.) |
96.4% |
97.0% |
97.0% |
|
Деградация WAPE |
0.05% / день |
0.04% / день |
0.05% / день |
|
|WBIAS| < 5% (отл.) |
90.8% |
85.2% |
86.5% |
|
Деградация |WBIAS| |
0.04% |
0.02% |
0.01% |
|
EOM < 2.5% (отл.) |
92.8% |
87.0% |
86.7% |
|
Деградация EOM |
0.06% / день |
0.08% / день |
0.08% / день |
По WAPE прогресс минимальный: доля отличных дней выросла с 64.8% до 66.5%. Это плюс примерно шесть дней в году.
По BIAS картина интереснее. Средняя накопительная ошибка упала почти вдвое, но доля отличных дней парадоксально снизилась – с 90.8% до 85.2%. Усреднение убирает крупные систематические перекосы, но добавляет мелкий шум в дни, когда сезонка и без того попадала идеально. Зато деградация по горизонту действительно снизилась.
По EOM неприятнее всего: сезонка-одиночка даёт 92.8% дней с ошибкой меньше 2.5%, ансамбль – только 87%. На дальнем горизонте ARIMA затухает и начинает мешать тому преимуществу сезонки, которое мы уже видели выше: она лучше удерживает месячный итог, чем внутримесячную динамику.
Ниже – WAPE по датам прогноза для ансамбля и сезонки.
Некоторые пики действительно стали ниже, но в декабре–январе ошибка ансамбля заметно выросла.
Итого: шаг вперёд по средней ошибке и пикам, полшага назад по стабильности. По сути, мы складывали два ведра с дырками: дырки разные, вода вытекает чуть медленнее, но вёдра всё равно дырявые.
Можно было пойти дальше – подобрать оптимальные веса, отдельные веса для каждого дня горизонта. WAPE при этом снижается с 4.32% до 4.22%. Но это не то, зачем мы здесь собрались.
Теперь классика точно выжата досуха. Пора расчехлять тяжёлую артиллерию.

Часть 3. Переход в ML: зачем нам города, если мы прогнозируем страну?
До этого момента все наши модели работали сразу на уровне всей страны. Нам ведь нужно итоговое число для Большого Директора – зачем усложнять?
Но когда классика упёрлась в потолок, возникла фундаментальная проблема: данных катастрофически мало.
У нас три года истории.
3 года × 365 дней = около 1100 точек на уровне страны. Для формулы в Excel или ARIMA это отличный массив. Для нейросети – пыль. Сложная модель просто выучит эти 1100 точек наизусть и на новых данных выдаст чушь.
Решение оказалось простым: спуститься на уровень городов.
1100 дней × 79 городов = почти 90 000 строк. Теперь данных достаточно. Мы строим одну модель сразу на всех городах: она ищет общие закономерности – как праздники влияют на спрос, как работают скидки, как меняется поведение в зависимости от размера города. Затем делает прогноз для каждого города отдельно, а мы суммируем их и получаем итоговую цифру по стране.
Чтобы модель выучила эти закономерности, ей нужен контекст – те самые признаки (ковариаты), о которых мы говорили в начале. Здесь они распадаются на три группы:
-
Past covariates– то, что знаем только до момента прогноза: фактические скидки, трафик, количество открытых ЦФЗ. -
Future covariates– то, что знаем заранее на весь горизонт: календарь, праздники, плановые промо. -
Static covariates– постоянные характеристики города: население, география.
И ещё два важных свойства отличают этот подход от классики.
Мультитаргет.
Модель может обучаться сразу на всех трёх наших метриках – выручка, заказы, штуки. Не три отдельные модели, а одна. Это важно: корреляция между целевыми больше 0.9, они движутся вместе. Если учить их совместно, модель улавливает эту связь, и прогнозы получаются согласованными из коробки. Не бывает ситуации, когда необоснованно выручка растёт, а количество заказов по прогнозу падает.
Ретрейн.
Нейросеть обучается на всей доступной истории и выучивает закономерности. Но мир меняется: новые дарксторы, новые паттерны поведения, новые события. Поэтому модель нужно периодически дообучать на свежих данных. Я зафиксировал режим: ретрейн раз в две недели, прогнозы – ежедневно. Между ретрейнами модель работает стабильно – мы это отдельно проверили.
Стоп, а где же CatBoost?
Любой аналитик, знакомый с ML, в этом месте резонно спросит: зачем вообще нейронки? Возьми CatBoost, закинь ковариаты – и вперёд.
Для тех, кто работает в Excel: что такое CatBoost и почему все его советуют
CatBoost – один из самых популярных бустингов над деревьями решений, вместе с XGBoost и LightGBM. Если у вас есть таблица с признаками вроде города, дня недели, скидки, праздника, погоды и трафика, то такие модели часто умеют очень хорошо находить в ней закономерности.
Их сильная сторона – нелинейность и взаимодействия признаков. Например, модель может понять, что одна и та же скидка по-разному работает в пятницу и в понедельник, в Москве и в небольшом городе, в начале месяца и в конце.
Почему его так любят:
-
он часто даёт сильное качество на табличных задачах;
-
быстро обучается;
-
относительно прост в использовании;
-
обычно требует меньше данных и меньше вычислений, чем нейросети.
Поэтому вопрос «а почему вы не взяли CatBoost?» в такой задаче абсолютно естественный.
Я попробовал. И упёрся в горизонт 45 дней.
CatBoost отлично работает, когда нужно предсказать завтра или послезавтра. Но 45 дней – это уже другая история. Есть три варианта, как это сделать, и у каждого своя цена:
-
Предсказывать по одному дню, используя предыдущие прогнозы как факт. Дёшево, но ошибка накапливается с каждым шагом. К 45-му дню модель питается собственными галлюцинациями как и ARIMA.
-
Строить отдельную модель на каждый день горизонта. Чисто, но дорого: 45 дней × 3 таргета = 135 моделей на один прогон. Для годового бэктеста – больше 3500 обучений. Один такой бэктест занял бы неделю. Плюс каждая модель живёт в своём мире и не знает, что предсказали соседние – связность дней теряется.
-
Заставить одну модель выдать сразу все 45 значений. Я попробовал и это. Результат – ошибка вдвое хуже Excel-сезонки. Модель просто выучила среднее по дню недели: данные 40-дневной давности не несут предсказательной силы для конкретного дня, и бустинг это быстро понимает.
И чем длиннее горизонт, тем хуже работают все три варианта. Нас уже сейчас просят думать про квартальное планирование на 120 дней. Закрыть это одной архитектурой в мире бустингов не получится.
Нужен другой инструмент.
Выход в Deep Learning
Хорошая новость: по меркам deep learning задача у нас небольшая. 90 тысяч строк – это объём, который спокойно живёт на обычной игровой видеокарте уровня RTX 3060. для ретрейна и прод-прогноза при желании хватит и CPU, хотя бэктесты и подбор гиперпараметров там уже будут неприятны.
Я отфильтровал модели в Darts по нескольким критериям: нужна была поддержка всех типов ковариат и возможность учить одну модель сразу на выручку, заказы и штуки. Также отсекли совсем простые варианты и круг сузился до трёх кандидатов.
TFT (Temporal Fusion Transformer) – на момент выхода лучшая модель для временных рядов по версии Google, накрученная самыми модными словами в мире DS. Звучит как идеальный выбор – но за это приходится платить. Подбор параметров занял несколько суток. Результат бэктеста – разочарование: модель уходит в оверфит, учится долго, качество не окупает затрат.
Для тех, кто работает в Excel: что такое оверфит
Оверфит – это ситуация, когда модель слишком хорошо подстраивается под исторические данные, но плохо обобщает на будущее.
На обучающем периоде такая модель выглядит впечатляюще: ошибка низкая, график красивый. Но это не значит, что она действительно научилась предсказывать. Возможно, она просто запомнила шум, случайные выбросы и частные особенности именно этого периода.
Поэтому на новом участке истории – где календарь, промо, погода или поведение пользователей уже немного другие – качество резко падает.
Именно это часто происходит, когда мощную модель пытаются обучить на слишком маленьком количестве наблюдений.
TiDE (Time-series Dense Encoder) – полная противоположность, простая и быстрая. Учится шустро, но ведёт себя нестабильно: качество скачет от месяца к месяцу. Все методы борьбы с оверфитом перепробованы – не помогает. Успокаивается только на минимально возможной архитектуре – но тогда теряет смысл. Модели просто не хватает данных, чтобы найти устойчивые закономерности.
TSMixer (Time Series Mixer) – модель с принципиально другим устройством (архитектурой): она смешивает информацию по времени и по признакам одновременно, и при этом хорошо работает на относительно небольших датасетах. На наших объёмах чувствует себя отлично. Именно с ней мы и продолжили.
Для DS: Ковариаты – что подавалось на вход модели
Фичи мы собирали не вслепую, а по итогам масштабного разведочного анализа (EDA). Мы поняли, что физика спроса сильно зависит от географии и нашего собственного поведения.
Итоговый набор разделился на 4 группы:
1. Past Covariates (история, известная только до момента прогноза):
-
Внутренние метрики: store_id_count (число открытых ЦФЗ), users (трафик), bonus_spend, bonus_add.
-
Скидки: basket_discount, trade_discount (важно: мы даем модели только фактические прошлые скидки, плановые в будущее не тянем, так как план часто расходится с фактом).
-
Макроэкономика: key_rate (ключевая ставка), inflation.
2. Future Covariates (календарь, известный заранее на весь горизонт):
-
days_since_opening – возраст дарксторов в городе.
-
is_non_working_day – нерабочие дни строго по производственному календарю (с учетом всех переносов от Минтруда).
-
Праздники: Я не стал парсить весь календарь, а оставили только те дни, на которые реально реагируют наши пользователи. Например: Новый год (разбит на pre/day/post), Пасха, 8 Марта, 23 Февраля, майские, Масленица, 1 Сентября, Черная Пятница и т.д.
3. Static Covariates (характеристики городов, не меняются во времени):
-
География: lat, lon (координаты). На EDA мы увидели, что у южных городов абсолютно другой годовой профиль спроса.
-
Фактор моря: on_sea_coast, on_black_sea. Курортные города живут по своим правилам. А у городов просто рядом с море несколько иные погодные паттерны что также влияет на наши целевые.
-
Масштаб: population_sqrt (население зафиксировали на 2024 год, так как меняется минорно).
-
Категориальные: city_nm (название), federal_district (федеральный округ – критически важная фича, так как маркетинг часто запускает промо-кампании не на всю страну, а на конкретный округ).
4. Временны́е энкодеры (автоматически генерируемые фичи):
"encoder_flags": {
"cyc_dayofweek": 1, # циклический день недели (sin/cos)
"cyc_month": 1, # циклический месяц
"cyc_weekofyear": 1, # циклическая неделя года
"attr_year": 1, # год как числовой признак
"attr_is_month_end": 1, # флаг конца месяца
"attr_is_quarter_end": 1, # флаг конца квартала
"attr_is_year_end": 1, # флаг конца года
"use_position_encoder": 1, # позиционное кодирование
"year_harmonics_K": 3, # гармоники года (3 пары sin/cos)
"week_harmonics_K": 1, # гармоники недели
"scale_encoders": 1, # нормализация энкодеров
}
Для DS: Стратегия подбора и фиксации гиперпараметров
Это, пожалуй, самое важное решение. Я зафиксировал все гиперпараметры и количество эпох на весь период бэктеста и затем в продакшене.
Многие практики добавляют валидационный set и подбирают количество эпох (early stopping) при каждом ретрейне. Я сознательно от этого отказался. Причина – стабильность прежде всего. Если при каждом переобучении модель учится то 15, то 40 эпох, поведение прогноза становится непредсказуемым. Бизнес не может доверять системе, которая каждую неделю ведёт себя по-новому.
Наш пайплайн подбора:
-
Optuna, 500 триалов – грубый поиск по пространству гиперпараметров. А также по некоторым энкодерам количество гармоник, postion encoder и т.д.
-
Отбор топ-кандидатов на валидации размером в 2 прогнозных окна по MAPE на уровне страны + значению Loss.
-
Полный 12-месячный бэктест для каждого кандидата – именно здесь отсеивались конфиги, которые выглядели блестяще на Optuna, но «плыли» от месяца к месяцу. Ретрейн каждые 2 недели, меньше по нашим экспериментам минорно влияет на качество модели , а вот 28 дней показывают себя видимо хуже. Полное время на 1 бэктест около 1-го часа. Данные подавались не окном последние N дней , а полностью, датасет постепенно увеличивался. Standart Scaler на каждом городе и по каждой целевой отдельный также пересчитывался по бэктесту.
-
Финальный выбор – конфигурация, которая показала лучший и наиболее стабильный результат по всем метрикам на всём горизонте бэктеста.
После выбора – ни один гиперпараметр больше не менялся. Один конфиг, один max_epochs=31, одна модель. Навсегда (ну, пока не накопится достаточно новых данных для следующего цикла пересмотра).
Для DS: Итоговые гиперпараметры и архитектура
Конфиг нашей финальной модели выглядит так:
Архитектура TSMixer:
model_input_length: 180 # 4 горизонта прогноза оказались оптимальными (как по учебнику!)
num_blocks: 3
hidden_size: 181
ff_size: 107
dropout: 0.53
activation: "GELU" # LeakyReLU на валидации тоже показывал себя неплохо
norm_type: "LayerNorm"
use_reversible_instance_norm: True
normalize_before: True
Оптимизация:
optimizer: "AdamW"
lr: 4e-4
weight_decay: 0.6
lr_scheduler: "ExponentialLR" (gamma=0.9449)
gradient_clip_val: 1.0
batch_size: 512
max_epochs: 31
precision: "32-true"
accelerator: "cuda"
Несколько комментариев к неочевидным решениям:
-
dropout = 0.53. Звучит как очень много, но для TSMixer это норма. Архитектура на базе MLP с mixer-блоками склонна к переобучению (особенно в multi-target задачах), поэтому в оригинальных статьях от Google рекомендуют агрессивный dropout. Наша Optuna это подтвердила – оптимум попал ровно в диапазон из литературы.
-
weight_decay = 0.6. Выглядит пугающе большим, но мы используем AdamW, а не классический Adam. В AdamW регуляризация весов (weight decay) отделена от вычисления градиентов (decoupled). Поэтому шкала значений тут совсем другая: 0.1–1.0 – это стандартный рабочий диапазон. Для сравнения, в обычном Adam + L2 аналогичная сила регуляризации достигалась бы при значениях около 1e-3.
-
use_reversible_instance_norm (RevIN). Это шикарная техника для борьбы со сдвигом распределения (temporal distribution shift) и растущим трендом. Как это работает: перед подачей в модель алгоритм берет конкретное входное окно (instance-wise) и нормализует таргет (вычитает среднее и делит на std обучаемым affine-слоем). Нейросеть делает прогноз в этой очищенной шкале. А затем на выходе делается обратное преобразование – прогноз возвращается в исходный масштаб окна продаж. В отличие от BatchNorm, RevIN не зависит от батча и идеально сохраняет нестационарную «уровневую» информацию.
Нейросеть входит в игру
Итак, гиперпараметры зафиксированы, ковариаты посчитаны, бэктест на 12 месяцев (с ретрейном каждые 2 недели) завершён. Кладём результаты TSMixer рядом с лучшими бейзлайнами – одиночной сезонкой и ансамблем ARIMA + Сезонка.
|
Метрика |
Excel-Сезонка (14д) |
Ансамбль (ARIMA+Сезонка) |
TSMixer |
|
WAPE (Средняя ошибка) |
4.54% |
4.31% |
3.73% |
|
WAPE < 5% (Дни «Отлично») |
64.80% |
66.45% |
82.62% |
|
WAPE < 10% (Дни «Хорошо») |
96.38% |
97.04% |
100.00% |
|
Деградация WAPE / день |
0.05% |
0.04% |
0.02% |
|
WBIAS |
0.93% |
0.46% |
0.44% |
|
EOM_WAPE (Ошибка за месяц) |
1.05% |
1.17% |
1.23% |
|
EOM < 2.5% (Месяцы «Отл») |
92.52% |
86.73% |
86.78% |
Давайте переведём цифры на язык бизнеса.
Грубых ошибок больше нет. Доля дней с ошибкой меньше 10% по WAPE – ровно 100%. Ни разу за год бэктеста нейросеть не промахнулась катастрофически.
Доля идеальных дней выросла с 64% до 82%. Теперь примерно четыре дня из пяти прогноз попадает в цель. У сезонки каждый третий день ошибка была сильнее 5% – теперь это редкость.
Прогноз почти перестал деградировать по горизонту. Потеря точности составляет 0.02% на каждый дополнительный день против 0.05% у сезонки.
Взглянем на уже знакомые графики.
Ниже – динамика WAPE по датам прогноза.
Главное, что видно на графике: периоды, в которых «сыпятся» простые модели и нейросеть, заметно различаются. Декабрь–январь, которые у сезонки давали самые неприятные пики, для TSMixer становятся обычным периодом. В целом линия ошибки заметно более стабильна, а пики – ниже и реже.
Ниже – WBIAS по датам прогноза.
У сезонки смещение сильнее «гуляет» по периоду, а у TSMixer bias в среднем ближе к нулю и заметно стабильнее.
Но есть одно место, где сезонка всё ещё выигрывает.
Ниже – EOM по датам прогноза.
По EOM сезонка всё ещё выглядит лучше: EOM у TSMixer ≈ 1.23% против 1.05% у сезонки. Более того, на графике видно, что в отдельных периодах пики EOM у нейросети могут быть даже выше.
К этому моменту у меня возникла та же мысль, что, возможно, уже возникла и у вас при взгляде на эти графики и метрики: а давайте скрестим нейронку и сезонку.
Нейросеть встречает сезонку
Берём простой ансамбль с равными весами – 50% TSMixer и 50% сезонка – и смотрим, что получается.
|
Метрика |
Excel-сезонка (solo) |
TSMixer (solo) |
Ансамбль (TSMixer + Сезонка) |
|---|---|---|---|
|
WAPE |
4.54% |
3.73% |
3.54% |
|
WAPE < 5% (отл.) |
64.80% |
82.62% |
79.61% |
|
WAPE < 10% (хор.) |
96.38% |
100.00% |
100.00% |
|
Degrad: WAPE/day |
0.05% |
0.02% |
0.02% |
|
WBIAS |
0.93% |
0.44% |
0.86% |
|
|WBIAS| |
0.93% |
0.44% |
0.86% |
|
|WBIAS| < 5% (отл.) |
90.79% |
89.51% |
95.07% |
|
|WBIAS| < 10% (хор.) |
97.70% |
100.00% |
100.00% |
|
Degrad: |WBIAS|/day |
0.04% |
0.00% |
0.02% |
|
EOM_WAPE |
1.05% |
1.23% |
1.01% |
|
EOM_WBIAS |
-0.24% |
-0.24% |
-0.24% |
|
EOM_MAPE |
1.04% |
1.23% |
1.00% |
|
EOM < 2.5% (отл.) |
92.52% |
86.78% |
88.78% |
|
EOM < 5% (хор.) |
97.96% |
95.93% |
96.94% |
|
Degrad: EOM/day |
0.07% |
0.09% |
0.08% |
WAPE упал до 3.54% – лучше обоих родителей.EOM_WAPE улучшился до 1.01% – сезонка принесла ансамблю свою месячную балансировку.
Но есть и плата за это улучшение: доля отличных дней снизилась с 82.6% до 79.6%. Иначе говоря, сезонка подтянула месячный итог, но заодно затащила в ансамбль часть своих выбросов в нестандартных периодах.
Ниже – динамика WAPE по датам прогноза для трёх вариантов.
На этом графике важны не столько абсолютные значения, сколько сам характер линий. Сезонка даёт пики в нестандартные периоды. Ансамбль идёт между ней и нейросетью: пики становятся ниже, но в моменты, когда сезонка сильно ошибается, она всё равно немного тянет ансамбль за собой. Это и есть цена за улучшенный EOM.
Ниже – деградация WAPE по горизонту прогноза.
Деградация по горизонту действительно стала ниже еще ниже. Это как раз тот случай, когда низкая корреляция ошибок начинает работать на нас.
Ниже – динамика WBIAS по датам прогноза.
По WBIAS ансамбль ведёт себя предсказуемо: там, где сезонка систематически уходила в плюс или в минус, он часть этого смещения компенсирует. Но полностью унаследовать стабильность TSMixer ему всё равно не удаётся.
Я покрутил и веса ансамбля – перебрал комбинации от 10/90 до 90/10. Оптимум нашёлся примерно в районе 60% TSMixer и 40% сезонки. Метрики стали чуть симпатичнее, но принципиально картину это не изменило. Сезонка слишком жёсткая: в нестандартные месяцы она не умеет адаптироваться и тянет ансамбль вниз.
Это не тот результат, с которым можно открыть дверь к Большому Директору с ноги.
Это хорошая база – но ещё не П.Е.С.Е.Ц.
И тут мы упираемся в стену. Простые модели не тянут. Бустинг уже мы не тянем. Тяжёлые нейронки уходят в оверфит. Остался только TSMixer. Но как вообще можно усреднить TSMixer сам с собой?
Оказывается, можно. И именно здесь началось самое интересное.
Нейросеть укачало
Хоть в статье мы рассматриваем только выручку, напомню: на самом деле мы прогнозируем три метрики одновременно – выручку, заказы и штуки. И при ретрейнах обнаружилась неприятная особенность.
Две недели назад модель решила, что главное в этой жизни – точно предсказывать Штуки. Штуки идут идеально, а вот Заказы слегка «гуляют». Проходит две недели, поступают новые данные, мы запускаем плановый ретрейн – и модель передумывает: теперь её любовь это Заказы. Заказы попадают, зато Штуки поплыли.
Бизнес смотрит и не понимает, почему точность метрик скачет от недели к неделе. Доверие падает. Значит, поведение модели между ретрейнами нужно было стабилизировать.
Классическое решение для таких ситуаций – ансамбль. Усредни несколько моделей, и случайные качели сгладятся. Но какие модели усреднять, если у нас есть только одна архитектура (TSMixer)?
Получить разные версии одной нейросети можно тремя способами:
-
Просто поменять
random seed. Самый дешёвый вариант, но почти бесполезный. Модели получаются слишком похожими, их ошибки синхронны, и усреднение почти ничего не даёт. -
Переобучать с разными гиперпараметрами. Разнообразие отличное, но цена слишком высокая. Каждый новый конфиг требует полного исторического бэктеста на 12 месяцев. А лучшие гиперы мы уже и так все просмотрели.
-
Изменить взгляд модели на данные. Оставить ту же архитектуру и те же гиперпараметры, но слегка поменять то, как модель видит историю и за что она штрафуется при обучении. Это и был наш выбор.
Мы нашли два измерения, по которым можно «расщепить» одну и ту же нейросеть, не меняя её базовых настроек.
Измерение первое: как модель видит города
Первое, что мы поменяли – то, как модель воспринимает масштаб.
Вариант 1. Все равны
Мы жёстко нормируем продажи каждого города. Для модели графики Москвы и маленького регионального центра выглядят одинаково по размеру – от 0 до 1. Это заставляет нейросеть не смотреть на абсолютные объёмы, а фокусироваться на общих паттернах: как люди реагируют на погоду, праздники и промо.
Вариант 2. Масштаб имеет значение
Мы сохраняем пропорции. Модель видит, что Москва – это миллиарды, а регион – миллионы. Это помогает ей меньше ошибаться в «больших» городах, которые формируют основной вклад в уровень страны.
Так появились две копии нейросети. Архитектура одна и та же, но на данные они смотрят по-разному – а значит, и ошибаются по-разному.
Измерение второе: на чём фокусируется модель
Второе измерение – это изменение функции потерь, то есть системы штрафов при обучении.
Представьте себе отдел из четырёх аналитиков. Большой Директор даёт им абсолютно одинаковое главное задание:
«Выдайте мне одну цифру: сколько мы продадим завтра вероятнее всего?»
В математике это медиана, или 0.5-квантиль.
Но дальше начинается самое интересное: каждому из них дают разные дополнительные задания, за ошибки в которых тоже сильно штрафуют.
-
Первому говорят: «Помимо главной цифры, рассчитай типичные колебания в спокойные дни – от 20-го до 80-го перцентиля». Чтобы не получить штраф, он с головой уходит в изучение нормы. Из-за этого его основной прогноз становится более консервативным и менее чувствительным к выбросам.
-
Второму говорят: «Помимо главной цифры, рассчитай сценарии полного апокалипсиса и небывалого чуда – 1-й и 99-й перцентили». Изучая исторические катастрофы и пики промо, он становится параноиком. Его основной прогноз теперь острее реагирует на любые риски.
-
Третьему поручают рассчитать вообще весь спектр вероятностей – от 1 до 99. У него самый широкий кругозор.
-
Четвёртый работает по классике: никаких дополнительных заданий нет, но его бьют рублём за возведённую в квадрат ошибку (
MSE), поэтому он до дрожи боится крупных промахов.
В итоге все четверо отвечают на один и тот же вопрос:
«Завтра продадим 100 штук».
Никто из них не становится слепым пессимистом или оптимистом. Но из-за разного «опыта подготовки» их ответы начинают немного отличаться. В те дни, где «любитель нормы» промахнётся из-за всплеска, его может подстраховать «параноик, ожидающий экстрима».
Их ошибки становятся несколько декоррелированными по своей природе.
Для тех, кто работает в Excel: как выглядит эта система штрафов в виде формул
Представьте, что вы используете надстройку «Поиск решения» (Solver) и хотите подобрать коэффициенты прогноза. Обычно в ячейку ошибки пишут классическую MSE:
=(Факт - Прогноз)^2
Ошибка возводится в квадрат, алгоритм боится крупных промахов и выдаёт нам скучное среднее значение.
Но если мы хотим создать того самого «аналитика-параноика», который ждёт экстрима, можно использовать асимметричный штраф:
=ЕСЛИ(Факт > Прогноз; 0.9 (Факт - Прогноз); 0.1 (Прогноз - Факт))
Здесь мы штрафуем недопрогноз с коэффициентом 0.9, а перепрогноз – всего лишь с 0.1. Поскольку штраф за дефицит в 9 раз больнее, Solver будет искусственно завышать прогноз до тех пор, пока примерно 90% фактических продаж не окажутся ниже вашей линии.
Для DS: технические нюансы
Использование квантильных выходов (Quantile / Pinball Loss) вместо MSE оказалось почти идеальным способом заставить копии модели смотреть на данные под разным углом.
Поскольку все квантили предсказываются из одного общего скрытого слоя (shared representation), обучение экстремальным квантилям физически меняет веса нейросети и заставляет её выделять фичи, полезные для детекции аномалий. Это меняет и прогноз медианы.
Что это даёт:
-
Структурное разнообразие. Каждая копия оптимизирует свой набор перцентилей. Ошибки декоррелированы не случайно, а математически – из-за разной формы градиентов.
-
Почти бесплатно по параметрам. Основные блоки
TSMixerостаются одинаковыми. Меняется только последний слой (projection head). Вывод 99 квантилей вместо 1 добавляет совсем немного весов. -
Гиперпараметры не трогаем. Не пришлось заново гонять
Optunaдля каждой копии. Мы взяли уже отвалидированный конфиг дляMSE.
Наши наборы квантилей выглядели так:
-
Set A (Focus):
[0.2, 0.35, 0.45, 0.5, 0.55, 0.65, 0.8]– фокус на центре распределения -
Set B (Tails):
[0.01, 0.025, …, 0.5, …, 0.99]– экстремальные хвосты -
Set C (Full):
q / 100 for q in range(1, 100)– равномерное покрытие всего спектра -
плюс классическая версия, обученная на
MSE
Важный нюанс: чтобы ансамбль можно было корректно усреднять, на этапе инференса из всех квантильных моделей (Set A, Set B, Set C) мы забираем именно медиану, то есть 0.5-квантиль.
Итого: 2 способа видеть города × 4 системы штрафов = 8 копий одной нейросети.
Одна архитектура. Одни гиперпараметры. Восемь независимых точек зрения.
Как собрать их в один прогноз?
Для начала мы пошли по самому простому пути – просто усреднили их с равными весами.
|
Метрика |
TSMixer (solo MSE) |
TSMixer + Сезонка |
Ансамбль (Equal Weights) |
|---|---|---|---|
|
WAPE |
3.73% |
3.47% |
3.43% |
|
WAPE < 5% (отл.) |
82.62% |
84.21% |
91.48% |
|
WAPE < 10% (хор.) |
100.00% |
100.00% |
100.00% |
|
Degrad: WAPE/day |
0.02% |
0.02% |
0.01% |
|
WBIAS |
0.44% |
0.78% |
0.39% |
|
|WBIAS| |
0.44% |
0.78% |
0.39% |
|
|WBIAS| < 5% (отл.) |
89.51% |
93.42% |
94.75% |
|
|WBIAS| < 10% (хор.) |
100.00% |
100.00% |
100.00% |
|
Degrad: |WBIAS|/day |
0.00% |
0.02% |
0.01% |
|
EOM_WAPE |
1.25% |
1.02% |
1.29% |
|
EOM_WBIAS |
-0.08% |
-0.08% |
-0.06% |
|
EOM_MAPE |
1.25% |
1.02% |
1.29% |
|
EOM < 2.5% (отл.) |
87.46% |
89.80% |
86.10% |
|
EOM < 5% (хор.) |
95.93% |
96.60% |
96.61% |
|
Degrad: EOM/day |
0.10% |
0.08% |
0.08% |
Короткий вывод после таблицы
На первый взгляд результат выглядит многообещающе.
-
WAPEснижается до 3.43% -
доля отличных дней (
WAPE < 5%) вырастает до 91.48% -
деградация по горизонту падает до 0.01% в день
-
WBIASтоже становится лучше
Но есть неприятный сюрприз: по EOM такой ансамбль не выигрывает.EOM_WAPE у него 1.29%, то есть хуже и solo TSMixer, и тем более ансамбля TSMixer + Сезонка.
Иначе говоря, простое усреднение резко улучшает дневную стабильность, но не решает задачу месячного итога.
Оптимизация ансамбля: битва за Bias
В итоге у нас на руках оказалось 8 прогнозов. Как их правильно взвесить?
Подбирать веса под каждый город? Слишком опасно. Метрики одного города слишком шумные, и мы почти гарантированно переобучимся под аномалии прошлых периодов.
Минимизировать MAPE ансамбля? Тоже не лучший путь: средняя ошибка у нас и так уже приличная – 3.43%.
Мы вспомнили про главную боль бизнеса – накопительную ошибку (Bias и EOM). Бизнесу часто не нужен идеальный вторник. Бизнесу важно попасть в «Итого за месяц».
Я написал кастомный оптимизатор весов ансамбля. Он подбирал единые для всей страны веса моделей под каждый таргет – отдельно для выручки, отдельно для заказов, отдельно для штук – с одной целью: накопительная ошибка (Bias) на разных горизонтах должна стремиться к нулю.
Дополнительно я добавил штраф (регуляризацию) за сильное отклонение весов от среднего, чтобы не убить разнообразие ансамбля.
И ещё одно важное ограничение (constraint): ни один вес не может быть ниже 0.05.
Зачем это нужно? Чтобы обеспечить робастность. Даже если какая-то из 8 моделей в прошлом жёстко ошиблась, мы не выкидываем её из ансамбля, а оставляем ей право голоса – хотя бы 5%. Потому что в следующем, нестандартном месяце именно её взгляд на мир может спасти нас от проблем.
Финал: Чудо
Я запустил оптимизатор – так на свет появился П.Е.С.Е.Ц.
Посмотрим на метрики.
|
Метрика |
Простое среднее (8 моделей) |
Оптимизированный ансамбль (наш финал) |
|---|---|---|
|
WAPE |
3.43% |
3.29% |
|
WAPE < 5% (отл.) |
91.48% |
93.44% |
|
WAPE < 10% (хор.) |
100.00% |
100.00% |
|
Degrad: WAPE/day |
0.01% |
0.01% |
|
WBIAS |
0.39% |
0.37% |
|
|WBIAS| |
0.39% |
0.37% |
|
|WBIAS| < 5% (отл.) |
94.75% |
100.00% |
|
|WBIAS| < 10% (хор.) |
100.00% |
100.00% |
|
Degrad: |WBIAS|/day |
0.01% |
0.00% |
|
EOM_WAPE |
1.29% |
1.20% |
|
EOM_WBIAS |
-0.06% |
-0.06% |
|
EOM_MAPE |
1.29% |
1.21% |
|
EOM < 2.5% (отл.) |
86.10% |
88.81% |
|
EOM < 5% (хор.) |
96.61% |
99.66% |
|
Degrad: EOM/day |
0.08% |
0.08% |
Улучшение видно почти по всей таблице. Но есть одна цифра, ради которой всё и затевалось:
|WBIAS| < 5% = 100% дней.
Ни одного дня за год бэктеста, когда накопительное смещение выходило бы за пределы приемлемого. Это один из факторов, убивающих доверие бизнеса. Не средняя ошибка сама по себе, а редкие, но болезненные моменты, когда прогноз систематически тянул не туда, и менеджмент это замечал.
Ниже – WBIAS по датам прогноза.
Ниже – знаковый EOM по датам прогноза.
Стоит честно отметить: в некоторых периодах и по некоторым метрикам сезонка всё ещё остаётся непобедимой. Но это уже не главное. Главное – мы практически убрали пики ошибок, а ошибка первого дня прогноза стала почти такой же, как ошибка сорок пятого.
Итоговое визуальное сравнение: с чего начали и чем закончили
Весь этот длинный путь был нужен по сути ради одного.
Да, по среднему WAPE улучшение выглядит скромно – примерно минус 1 процентный пункт. На фоне всей этой монструозной конструкции из городов, квантилей и оптимизатора весов это звучит не так уж впечатляюще. Но смотреть надо не на среднее, а на пики.
Сезонка может ошибиться на 20%.
Наш итоговый ансамбль – не может. Ни разу за весь год бэктеста.
Ниже – WAPE по датам прогноза: итоговый ансамбль против сезонки.
Ниже – WBIAS.
Накопительное смещение прижато к нулю. У сезонки оно “гуляет”, у ансамбля – нет.
Ниже – динамика WBIAS по датам прогноза.
Ниже – деградация MAPE по горизонту.
Это следствие сразу нескольких вещей: агрегации с уровня городов, разнообразия через квантили и оптимизации весов под bias.
Ниже – WBIAS по горизонту прогноза.
У сезонки оно растет, у ансамбля остаётся почти плоским.
Ниже – EOM по дням месяца.
Это самый неприятный график для нашей модели: здесь видно, где она всё ещё проигрывает годовой сезонке.
Но ниже – знаковый EOM по датам прогноза.
По абсолютной величине сезонка местами выигрывает. Но знаковый EOM, как и WBIAS, у ансамбля заметно стабильнее: два соседних дня не противоречат друг другу. Бизнес не видит, как прогноз перекрашивается из зелёного в красное за одну ночь.
Для DS: стоп, а где же иерархическое согласование (MinT)
Искушённый читатель, знакомый с библией прогнозирования от Hyndman, наверняка уже нахмурился:
– Позвольте, у вас же классическая иерархия: город → страна. Почему вы просто суммируете прогнозы снизу вверх (
Bottom-Up)? Где жеMinT(Minimum Trace Reconciliation), который математически гарантирует согласованность и уменьшает дисперсию ошибок?
Отвечу честно: я это не внедрял. И вот почему.
MinT бывает очень разным и точно не является волшебной таблеткой.
1. Простые реализации (OLS / WLS) требуют сильного “верха”.
Чтобы MinT реально работал в плюс, нужна очень качественная top-level модель – то есть прогноз сразу на всю страну, который задаёт “генеральную линию партии”, а нижние уровни уже под неё подстраиваются.
В нашей реальности Bottom-Up – сумма прогнозов по городам – стабильно обыгрывал любую модель, обученную сразу на тотале страны. Шум на уровне страны у нас специфический, и размазывать ошибку сверху вниз означало бы портить хорошие прогнозы в регионах.
2. Сложные реализации (MinT-Shrinkage) – это ещё один риск оверфита.
Более продвинутые подходы пытаются выучить ковариационную матрицу ошибок – кто с кем коррелирует. Но здесь появляется ещё одна точка отказа: мы только что потратили кучу времени, чтобы победить оверфит в нейронках, и добавлять сверху ещё один обучаемый слой на остатках было бы довольно рискованно.
3. Это дорого инженерно.
Чтобы честно проверить MinT, его нельзя просто накинуть в конце. Нужно встраивать шаг реконсиляции прямо внутрь большого бэктеста. Это означало бы пересчитать всё заново, сохраняя не только прогнозы, но и ковариации ошибок на каждом шаге.
Скажу прямо: на это у нас уже банально не хватило ни вычислительных ресурсов, ни человеческих сил. А ансамбль с квантилями уже давал ошибку < 3% и согласованность через оптимизацию весов.
Лучшее – враг хорошего.
А что с квантилями? Бесплатные доверительные интервалы
Побочным и, пожалуй, самым приятным эффектом всей этой истории с квантилями стало то, что мы пришли за стабильностью, а нашли кое-что ещё интересное.
На выходе из нейросети мы получили не просто одну точку прогноза, а полный спектр распределения вероятностей – от 1-го до 99-го перцентиля. Да, чтобы математика сходилась аккуратно, пришлось сделать небольшую мультипликативную калибровку: сдвинуть интервалы так, чтобы медиана ложилась ровно на результат работы итогового ансамбля. Но после этого у нас на руках оказался очень мощный инструмент.
Разобрав эти доверительные интервалы, мы увидели, что они на удивление умные:
-
Они реагируют на календарь. Диапазоны автоматически расширяются в выходные и праздники. Модель понимает, что дисперсия спроса в субботу выше, чем в унылый вторник.
-
Они учитывают горизонт. Ширина коридора логично растёт по мере удаления в будущее.
-
Они асимметричны. Верхние перцентили (например, 90-й) улетают от медианы дальше, чем нижние (10-й). Модель выучила физику Q-commerce: продажи могут неожиданно взлететь в два раза из-за снегопада, но почти никогда не падают в два раза просто так.
Для DS: Суммирование квантилей
Обычно суммировать квантили снизу вверх – почти математическое преступление: квантиль суммы не равен сумме квантилей, если слагаемые не комонотонны. У нас суммирование идёт сразу в двух направлениях: дни → месяц и города → страна.
Здесь мы использовали корреляцию как грубую прокси на синхронность:
-
Корреляция между городами высокая. Федеральное промо, погода и праздники бьют по всем сразу. Города часто ходят синхронно, как один большой «супергород».
-
Автокорреляция по дням тоже высокая. Если понедельник выше нормы, вторник почти наверняка тоже. Дни внутри месяца не независимы.
После этого я просто посмотрел на паре месяцев, какой процент факта попадает в диапазоны, и результат меня устроил. В обоих направлениях ряды оказались достаточно “склеены”, чтобы суммирование квантилей давало адекватные интервалы – и для месячного итога, и для тотала по стране. Дополнительную калибровку я делать не стал.
Теперь, благодаря полному набору перцентилей, мы можем оценить вероятность достижения вообще любой цифры.
И когда на бизнес-ревью Большой Директор спрашивает:
– Ну что, а выполним ли мы амбициозный план для Большого Инвестора?
мы просто открываем график и твёрдо говорим:
– Конечно выполним! С вероятностью менее 1%.
И показываем красивый коридор, где медиана идёт своим чередом, а план инвестора парит где-то в стратосфере, далеко за пределами 99-го перцентиля нашего прогноза.
Ниже – спектр доверительных интервалов прогноза.
Вместо итогов
Если вы дочитали до этого места – значит вы либо строите прогноз сами, либо страдаете от чужого. В обоих случаях вот три мысли которые стоит забрать с собой.
Начинайте с самого простого. Серьёзно.
Главная ошибка – сразу тянуться к SOTA. Наша Excel-сезонка из трёх строк кода побила Prophet, FourTheta и SARIMAX. И по EOM она до сих пор лучше нашего монструозного ансамбля из восьми нейросетей. Именно поэтому мы её не убили. Сезонка продолжает считаться параллельно и живёт в том же дашборде рядом с П.Е.С.Е.Ц.
Простая модель – не временное решение. Это эталон и страховка одновременно. Если ваш ML не бьёт формулу из трёх ячеек на честном годовом бэктесте – удаляйте ML.
Измеряйте то, что болит у бизнеса.
Можно построить гениальную модель и выкинуть её – потому что неправильно измерили. Можно построить мусорную модель, порадоваться красивому MAPE и удивляться почему никто не верит прогнозу.
Бизнес мыслит не днями – он мыслит месяцами и бюджетами. Смотрите на пики ошибок, на накопительное смещение, на ошибку конца месяца. Смотрите насколько прогноз стабилен между соседними днями – потому что именно это замечает менеджмент, а не средний MAPE.
Стабильный прогноз с WAPE 4% в сто раз ценнее чем прогноз с WAPE 2% который раз в полгода галлюцинирует на 20%. Один промах на 20% перевешивает двенадцать месяцев точных цифр – мы убедились в этом на собственном опыте.
Прогноз которому не верят – не существует.
Он просто становится поводом для паники. Точкой отсчёта для торга: «а вдруг снова ошибётесь? давайте заложим риск, давайте сожжём ещё миллиард сверху». Технически задача была только про точность. Но на самом деле смысл её в том, чтобы Большой Директор открыл дашборд и принял решение на основе цифр, а не вопреки им.
Мы строили не модель. Мы строили доверие.
Что дальше?
Сейчас наш П.Е.С.Е.Ц. (Прогноз Единый Стратегический Ежедневный Целевой) крутится в проде. Бизнес ему доверяет , качели остановились, а аналитики перестали седеть по понедельникам. Теперь мы точно и с вероятностью 99% знаем, что план мы не выполним!
Поэтому впереди новая задача: мы строим байесовский иерархический what-if симулятор эластичности с упаковкой в Excel, чтобы понять, какие ручки (скидки, маркетинг, стоимость доставки) нужно покрутить, чтобы этот план всё-таки выполнить.
P.S. При создании ни один песец не пострадал.
Автор: KelThuzed


