Прогноз выглядит отлично, но, возможно, просто повторяет вчерашнее значение — как это поймать?
Дневной прогноз спроса даёт MAPE 4.1% на тестовом окне, и график выглядит почти идеально.
Ограничения:
daily.csvсодержитdateиunits, по строке на день за три года.preds.csvсодержитdateиyhatза последние 90 дней.- Часть SKU продаётся по 0-2 штуки в день.
import pandas as pd
y = pd.read_csv("daily.csv", parse_dates=["date"]).set_index("date")["units"]
p = pd.read_csv("preds.csv", parse_dates=["date"]).set_index("date")["yhat"]
a = y.loc[p.index]
mape = ((a - p).abs() / a).mean() * 100
print(f"MAPE {mape:.1f}%") # 4.1%
Покажите, что прогноз — запаздывающая копия, и почините оценку качества.
Сверните прогноз с фактом — пик на лаге 1 означает копирование вчерашнего. Сравните её с наивным и сезонно-наивным бейзлайнами по MASE, ведь на почти случайном блуждании MAPE мал; настоящее доказательство — обойти сезонно-наивный.
- ✗Судят о прогнозе, ни разу не построив наивный бейзлайн для сравнения
- ✗Верят малому MAPE на медленном ряде, где его даст и копия с лагом один
- ✗Считают MAPE на ряде с почти нулевыми фактами, где метрика взрывается
- →Почему MAPE взрывается при фактах около нуля и что приходит ему на смену?
- →Какой бейзлайн выбрать для ряда с сильной недельной сезонностью?
Метрика ничего не доказывает: на медленном ряде копия вчерашнего значения тоже даёт крошечный MAPE. Кроме того, на SKU с продажами 0-2 штуки знаменатель a обращается в ноль, и MAPE уходит в бесконечность.
Диагностика — взаимная корреляция прогноза с фактом: у запаздывающей копии максимум приходится на лаг 1, а не на лаг 0. Оценка чинится сравнением с наивным и сезонно-наивным бейзлайнами по MASE.
import numpy as np
import pandas as pd
a = y.loc[p.index]
lags = {k: a.corr(p.shift(-k)) for k in range(-3, 4)}
print(max(lags, key=lags.get)) # 1 → прогноз повторяет вчерашний факт
naive = a.shift(1) # наивный
snaive = a.shift(7) # сезонно-наивный, недельный цикл
scale = (a.diff(7).abs()).mean() # знаменатель MASE
def mase(pred):
return (a - pred).abs().mean() / scale
print(mase(p), mase(naive), mase(snaive))
Если mase(p) не ниже mase(snaive), модель не выучила ничего сверх сезонного повтора и в прод не идёт.