Прогноз продаж с ИИ: проверка на отложенном периоде

Как проверить прогноз продаж с ИИ: учебные train и test, наивный прогноз и drift, построчные ошибки и MAE. Два промпта без подгонки по факту.

Прогноз продаж с помощью ИИ полезен ровно настолько, насколько честно он проверен на данных, которые модель не видела. Прогноз продаж в Excel и нейросетевой подсказке имеет смысл только при такой проверке. Ниже — воспроизводимая процедура: разбить историю на обучающую и отложенную части, зафиксировать методы и прогнозы до раскрытия факта, посчитать построчные ошибки и MAE. Всё построено на синтетических данных и простой базовой линии. Это не обучение нейросети и не клиентский кейс, а учебный стенд, на котором видно, что сложнее не значит лучше.

Ответ и результат сначала

На учебном отложенном периоде из двух недель наивная базовая линия дала MAE 11 шт./неделю, а простой drift-кандидат — 12. Разница в одну единицу на двух точках ничего не доказывает: это не вывод о превосходстве ИИ и не вывод о том, что базовый метод лучше всегда. Здесь проверена только арифметика на заранее зафиксированных прогнозах. Реального прогона языковой модели не было: оба промпта приведены как спецификация вычисления, а не как отчёт о выполненном запуске.

Почему такой порядок важен, хорошо видно по методике оценки прогнозных моделей: оценка — это построение прогнозов на тестовом множестве, отложенном от обучающих данных, и расчёт по ним метрик, которые направляют решения о внедрении модели (Inference and evaluation of forecasting models). Там же описан backtest — прогон обученного прогнозчика вперёд по тестовому множеству с усреднением ошибки по нескольким окнам. И отдельно сказано: в идеале тестовый период должен быть длинным относительно горизонта прогноза, иначе оценки ошибки могут быть статистически шумными и менее надёжными (Inference and evaluation of forecasting models). Наш двухточечный тест — пример такого короткого окна, и относиться к нему надо соответственно.

Полная история и split

Учебный пример — один SKU и восемь недельных точек. Шесть уходят в обучение, две — в отложенный период. Все восемь значений синтетические, пропусков нет, ноль означает подтверждённый ноль продаж, а не неизвестное значение.

Дата Продажи, шт. Часть
2026-01-05 10 train
2026-01-12 12 train
2026-01-19 14 train
2026-01-26 16 train
2026-02-02 18 train
2026-02-09 20 train
2026-02-16 22 test
2026-02-23 0 test

Границы split фиксируются до любого прогноза. Обучающая часть — строки с 2026-01-05 по 2026-02-09 включительно. Отложенная — 2026-02-16 и 2026-02-23. Горизонт — две недели. Origin — последняя дата обучения, 2026-02-09: более поздние фактические значения в построение прогноза не попадают. Так исключается утечка — ситуация, когда в расчёт прогноза подмешиваются данные из будущего.

Отдельно про ноль в последней строке. Это подтверждённое значение продаж, и его нельзя выбрасывать как «выброс» или «ошибку данных»: выкидывание нулевой строки завышает видимую точность. При этом фактический спрос по продажам не восстановить: остатки, акции, цены и потерянный спрос по этому SKU неизвестны, поэтому причина нуля не объясняется.

Промпт 1 и формулы

Методы выбираются до раскрытия теста. Их два, и оба простые.

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

Кандидат — drift: последнее значение плюс шаг, умноженный на номер горизонта, где шаг равен разности последнего и первого обучающего значения, делённой на число интервалов между ними. Параметры берутся только из шести обучающих точек. Это заданный простой кандидат, а не обученная нейросеть. Шаг: (20 − 10) / 5 = 2. Прогноз на первую неделю: 20 + 2 × 1 = 22. На вторую: 20 + 2 × 2 = 24.

Замороженный прогноз до раскрытия теста:

Дата Базовая линия Drift
2026-02-16 20 22
2026-02-23 20 24

Полный промпт первой фазы, только по обучающим данным, без доступа к тесту:

Подготовь вычисление учебного прогноза двумя заранее заданными методами. Не обращайся к внешним данным, не подбирай метод по невидимому test. Верни для 2026-02-16 и 2026-02-23 при fixed origin 2026-02-09 прогноз baseline и drift, формулу/подстановку и ограничения. Если код не запускался, не сообщай, что выполнил его.

Методы и условия:
{"chosen_before_test": true, "horizon_weeks": 2, "baseline": "Наивный: оба будущих значения = последнему train 20. Не обновлять фактом первой test-недели: один fixed origin.", "candidate": "Drift: last + h*(last-first)/(n-1), h = 1/2; параметры получить только из 6 train-точек. Это заданный простой кандидат, не обученная нейросеть.", "metric": "MAE = сумма абсолютных ошибок / 2, единицы шт./неделю. MAPE в целом неопределён при хотя бы одном actual = 0; не выкидывать нулевую строку.", "history": "Синтетические известные продажи одного SKU; нет пропусков, 0 означает подтверждённый ноль, не unknown. Остатки/акции/цены/потерянный спрос неизвестны.", "governance": "Зафиксировать методы/origin до test. Изменение формул после вскрытия test — нечестная оценка этого же test; для нового выбора потребуется новый неиспользованный период."}

Полный train (date, units):
[["2026-01-05", 10], ["2026-01-12", 12], ["2026-01-19", 14], ["2026-01-26", 16], ["2026-02-02", 18], ["2026-02-09", 20]]

Те же расчёты в Excel. В A2:A7 — даты обучения, в B2:B7 — продажи, B7 = 20 — последнее обучающее значение. Прогноз базовой линии для обеих будущих дат:

=$B$7

Прогноз drift для первой будущей недели и для второй:

=$B$7+1*($B$7-$B$2)/(ROWS($B$2:$B$7)-1)
=$B$7+2*($B$7-$B$2)/(ROWS($B$2:$B$7)-1)

После ввода формулы локаль (разделитель аргументов и десятичный знак) объявляется до использования, иначе ROWS и дробная часть шага могут считаться по-разному на разных машинах. Абсолютные ссылки $B$7 и $B$2 держат origin и первое обучающее значение на месте при протягивании формулы вниз. Проверить формулы просто: результат должен совпасть с 20/20 и 22/24 из таблицы выше.

Выход первой фазы фиксируется до раскрытия теста — например, сохранением файла или сообщения с замороженным прогнозом и временем. Иначе появляется соблазн поправить метод после того, как стали известны факты, и оценка перестаёт быть честной.

Промпт 2 и весь эталон

Вторая фаза оценивает уже зафиксированные прогнозы по раскрытому факту и не меняет ни прогноз, ни методы. Раскрытые фактические продажи: 22 за 2026-02-16 и 0 за 2026-02-23.

Построчные ошибки — модуль разности факта и прогноза:

Дата Факт Базовая линия Drift Ошибка базовой Ошибка drift
2026-02-16 22 20 22 2 0
2026-02-23 0 20 24 20 24

MAE базовой линии: (2 + 20) / 2 = 11 шт./неделю. MAE drift: (0 + 24) / 2 = 12 шт./неделю. В Excel ошибка строки — =ABS(факт - прогноз), MAE по двум строкам — =AVERAGE(две_ошибки).

MAPE здесь не считается: при фактическом нуле деление на ноль делает процентную ошибку неопределённой, поэтому метрика помечается как null. Нулевую строку из расчёта не удаляют — иначе изменится сам тест и пропадёт самый показательный случай.

Полный промпт второй фазы, самодостаточный, с замороженным прогнозом и раскрытым тестом:

Оцени уже зафиксированные прогнозы по последующему раскрытому факту. Не изменяй ни прогноз, ни методы. Выведи все 2 строки actual/forecasts/absolute errors, MAE для каждого метода и MAPE: null, если actual 0 есть. Сравни ошибки только данного test; не выводи причины или универсальный выигрыш ИИ.

Правила:
{"chosen_before_test": true, "horizon_weeks": 2, "baseline": "Наивный: оба будущих значения = последнему train 20. Не обновлять фактом первой test-недели: один fixed origin.", "candidate": "Drift: last + h*(last-first)/(n-1), h = 1/2; параметры получить только из 6 train-точек. Это заданный простой кандидат, не обученная нейросеть.", "metric": "MAE = сумма абсолютных ошибок / 2, единицы шт./неделю. MAPE в целом неопределён при хотя бы одном actual = 0; не выкидывать нулевую строку.", "history": "Синтетические известные продажи одного SKU; нет пропусков, 0 означает подтверждённый ноль, не unknown. Остатки/акции/цены/потерянный спрос неизвестны.", "governance": "Зафиксировать методы/origin до test. Изменение формул после вскрытия test — нечестная оценка этого же test; для нового выбора потребуется новый неиспользованный период."}

Полный frozen forecast:
[{"date": "2026-02-16", "baseline": 20, "drift": 22}, {"date": "2026-02-23", "baseline": 20, "drift": 24}]

Полный test (date, units):
[["2026-02-16", 22], ["2026-02-23", 0]]

Ограничения и следующий период

Что этот стенд позволяет утверждать: на двух зафиксированных точках базовая линия ошиблась меньше, чем drift, на 1 шт./неделю по MAE. Что он не позволяет: утверждать, что ИИ хуже или лучше, что drift не работает, что ноль продаж вызван падением спроса или что модель будет точной в будущем.

Причинная история отсутствует. Продажи без данных об остатках не восстанавливают фактический спрос, поэтому нулевую неделю нельзя трактовать как обвал или как дефект данных. Две тестовые точки слишком малы для обобщения — в идеале тестовый период должен быть длинным относительно горизонта прогноза, иначе оценки ошибки могут быть статистически шумными и менее надёжными (Inference and evaluation of forecasting models). Промпты языковой модели не запускались, обучения нейросети и подбора модели не было.

Правило после теста: менять метод по результатам уже раскрытого периода нельзя — это подгонка под тот же test. Для нового выбора нужен новый неиспользованный holdout, зафиксированный до раскрытия. Для будущего исследования имеет смысл задать более длинные rolling-окна: несколько перекрывающихся окон с фиксированным контекстом, горизонтом и шагом, с усреднением ошибки по окнам. Так оценка перестаёт зависеть от одной пары точек.

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

Валерий Курземнек

Автор метода «Машиночитаемый маркетинг»

Что ещё почитать