Всем привет! Меня зовут Костя, и в этой статье я продолжу рассказ моей коллеги Анастасии из команды доступности Magnit Tech о том, как можно искать проблемные товары на полках магазинов, опираясь лишь на данные по дневным продажам и остаткам товара в магазине.

В этой статье я расскажу о другом, альтернативном алгоритме, который детектирует ощутимую долю проблемных позиций наравне с тем, о котором рассказывала моя коллега. Этот алгоритм также прост для понимания и интерпретации бизнесом. Его внедрение и эксплуатация требуют минимальных затрат: вычислительные ресурсы, сопровождение и адаптация под разные форматы магазинов обходятся недорого. Кроме того, он может служить эффективным первым шагом перед внедрением более сложных и ресурсоемких ML-алгоритмов. В отличие от нейросетевых подходов к поиску аномалий, которые требуют тщательной настройки (или даже разработки) оптимальной архитектуры сети, настроить этот алгоритм значительно проще.

Автоматизированный поиск проблемных позиций и своевременное оповещение сотрудников (включая мерчандайзеров) приносят значительную пользу большим торговым сетям и производителям, увеличивая выручку за счет:

  • выкладки на полку востребованных невыставленных позиций;

  • заказа на поставку в магазин популярного товара, числящегося, но фактически в магазине отсутствующего (т.н. виртуальный остаток);

  • обновления некорректного (с более высокой ценой) или распечатки отсутствующего ценника.

Пилотное внедрение алгоритма в форматах Магнита с высокой посещаемостью, таких как “Магнит у дома” и “Семейный Магнит”, показало хорошую точность и достаточный охват проблемных позиций. Это подтверждает, что данные о продажах достаточно информативны для выявления проблемных позиций в магазинах со средним и высоким трафиком.

Алгоритм в целом(TLDR)

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

1. Сбор и предобработка данных

  • Берутся необработанные данные о дневных продажах (в штуках или чеках) и остатки товара по каждому магазину.

  • Данные очищаются от влияния сезонности, акций и изменения цен с помощью коэффициента эластичности, который считается с использованием прогноза продаж:

Yбазовые⠀продажи= Yпродажи × Cэластичность+сезонность

Это позволяет сравнивать продажи в «нейтральных» условиях (без промо, праздников и т. д.).

2. Определение естественного уровня продаж

Для каждой позиции в магазине оценивается естественная интенсивность продаж λ:

Среднедневные продажи λ или доля дней с продажами P_{sell}. В алгоритме используется λ, так как это чувствительнее к изменениям.

3. Выбор модели распределения

Для низкодискретных (редко продающихся) товаров применяется распределение Пуассона:

P_{Poisson}(k,\lambda) = \frac{\lambda^k}{k!} e^{-\lambda}

Для высокодискретных (часто продающихся, но с периодами нулей) — ZIP-модель (Zero-Inflated Poisson):

P_{ZIP}= Bernoulli(1-p_0)*P_{Poisson}(k,\lambda)

4. Детекция остановки продаж (CUSUM-алгоритм)

Сравнивается вероятность наблюдения текущих продаж при:

  • Нулевом уровне спроса λ ≈ 0

  • Естественном уровне λ = λ_{естественные}

Вычисляется лог-правдоподобие для каждого дня:

s_i = \log \left( \frac{p_{\lambda \approx 0}(y_i)}{p_{\lambda = \lambda_{\text{естественное}}}(y_i)} \right)

Далее считается накопительная статистика S_k

S_k = \sum_{i=1}^k s_i

и её минимум

m_k = \min_{1 \leq j \leq k} S_j

Они, в свою очередь, используются для расчета CUSUM-статистики:

g_k=S_k-m_k

Если g_k превышает порог (определяется через бутстреп), фиксируется остановка продаж.

5. Сравнение с магазинами-аналогами

Чтобы исключить неоцифрованные внешние факторы (например, нетипичное завершение высокого сезона спроса), динамика продаж в проблемном магазине сравнивается с магазинами-аналогами – похожими по локации, трафику, акциям.

Фильтры для проверки аномалии:

  • Дискретность: Падение продаж в целевой ТТ глубже, чем в аналогах.

  • Тренд: Ускоренное снижение продаж относительно аналогов.

  • Наихудший аналог: Период непродаж длиннее, чем у самого слабого аналога.

6. Фиксация аномалии

Позиция считается проблемной, если:

  • CUSUM выявил значимую остановку продаж.

  • Хотя бы один фильтр сравнения с аналогами подтвердил аномалию.

Идейная часть алгоритма

Алгоритм исходит из 3 предположений о том, как ведут себя продажи по товарной позиции, с которой есть проблема:

  • Если по товарной позиции есть проблема, то продажи по ней останавливаются. Этот перерыв в продажах (период непродаж) длится некоторое время. Сколько именно – зависит от того, как обычно эта позиция продается в этом конкретном магазине. Мы периоды непродаж измеряем в днях.

  • За период непродаж падение продаж позиции в магазине (в %) должно быть заметнее, чем падение этой же позиции в магазинах-аналогах. (Магазины-аналоги – это группа магазинов со схожей сезонностью, графиком промо и скидок).

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


Сбор и предобработка данных

Чтобы частично сгладить скачки спроса из-за сезонности и изменения цен, вместо фактических продаж (в штуках или по чекам) используют базовые продажи.

Базовые продажи за день рассчитываются так: берут фактические продажи за день и умножают их на поправочный коэффициент. Этот коэффициент  C\,эластичность+сезонность отражает, насколько сезонные факторы и цена в этот день влияют на спрос.

 Y_{\text{базовые продажи}}=Y_{продажи}*C_{эластичность+сезонность}

Проще говоря, базовые продажи показывают, сколько товара продалось бы в этот день в магазине, если бы:

  1. Цена была регулярной (без промо и скидок),

  2. Не было сезонных всплесков или падений, влияния дня недели или праздников.

Коэффициенты C\,эластичность+сезонность рассчитываются в команде промо прогноза, подробнее тут.

Однако у такого подхода есть минус: в ряду базовых продаж дней с нулевыми продажами во время акций становится меньше, а в обычные дни (без акций) — больше. Это значит, что корректировка на ценовые колебания получается неполной. На данном этапе этим мы пренебрегли.


Определение естественного уровня продаж

Естественную интенсивность продаж (или, еще говорят, дискретность) позиции можно оценить разными способами, например:

  1. P_{sell} доля дней с продажами за длительный период

  2. \lambda - средние продажи за день

Тогда 1/P_{sell} показывает, за сколько дней в среднем продается хотя бы одна единица товара, а 1/\lambda - за сколько дней в среднем продается ровно одна единица товара. В рассматриваемом алгоритме используется второй вариант, потому что он учитывает величину продаж в день, а не только их наличие. Это приводит к большей чувствительности алгоритма и более раннему его срабатыванию. 


Выбор модели распределения

Остановку продаж по позиции можно описать как аномалию спроса, при которой меняются его характеристики. В статистической литературе этому соответствует ситуация, когда изменяется среднее значение временного ряда продаж. Для детекции таких изменений применяется модель определения разладки (changepoint detection):

  • продажи в день моделируются некоторым вероятностным распределением.

  • факт остановки продаж трактуется как значимое изменение среднего значения дневных продаж с естественного уровня в данном магазине, до нуля.

В качестве модельного распределения для количества продаж за день используется распределение Пуассона:

P_{Poisson}(k,\lambda) = \frac{\lambda^k}{k!} e^{-\lambda}

В целом, распределение Пуассона – стандартный выбор для моделирования величин-счетчиков. Качество получаемой аппроксимации можно оценить, сравнив гистограмму «сколько продаж в день по позиции» было в разные дни с тем, что предсказывает распределение Пуассона с таким же средним.

Альтернативным вариантом оценки базового распределения является ZIP-модификация распределения Пуассона (Zero-inflated Poisson), которая выглядит так:

P_{ZIP}= Bernoulli(1-p_0)*P_{Poisson}(k,\lambda)

Грубо говоря, сначала подбрасывается нечестная монетка (c вероятностью нуля p_0), которая говорит, «бери 0» или «бери величину, распределенную по Пуассону». Часто распределение Пуассона недооценивает вероятность нуля продаж для товаров, которые продаются не каждый день, но когда продаются - то довольно помногу. ZIP явно моделирует эти "избыточные нули".

На рисунке выше видно, что пуассоновская вероятность 0 (равная exp(-\lambda), \lambda- среднедневные продажи) значительно меньше, чем наблюдаемая частота 0 в реальных продажах.

  1. Продажи средне- и низкодискретных, т.е. более редко продающихся позиций, моделируются распределением Пуассона.

  2. Продажи высокодискретных – ZIP-распределением.

Модель распределения Пуассона здесь подходит именно для низкодискретных позиций, поскольку продажи таких позиций в магазине в течение дня представляют из себя поток независимых событий, интенсивность покупок считается постоянной в течение рабочего дня (в среднем меньше 1 штуки в день), а общее количество покупок за день (при фиксированной интенсивности продаж) в среднем зависит только от продолжительности работы магазина в течение дня. Для высокодискретных позиций интенсивность продаж может сильно изменяться: периоды, когда каждый день идут продажи и не по 1 штуке, сменяются достаточно длительными периодами непродаж.


Детекция остановки продаж

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

Алгоритм CUSUM для обнаружения точек изменений

Классическим и широко используемым является алгоритм CUSUM (Cumulative Sum Control Chart). Впервые предложенный Э.С. Пейджем в 1954 году [Page, 1954], CUSUM опирается на принципы последовательного анализа и тесно связан с тестом последовательного отношения правдоподобия (SPRT) Вальда [Wald, 1947].

Для детекции остановки продаж применяется статистика, чем-то напоминающая тест отношения правдоподобия: в числителе стоит правдоподобие того что наблюдаемый ряд дневных (базовых) продаж за некоторый промежуток времени, называемый периодом детекции, появился при нулевом (близком к 0) уровне продаж позиции, а в знаменателе - правдоподобие того, что наблюдаемый ряд был получен при естественном уровне продаж.

Что такое правдоподобие?

Допустим, что Вы владелец магазина и хотите понять, сколько шоколадок в среднем продаётся за день (обозначим это число \lambda). За неделю наблюдали такие продажи: 

[3, 0, 2, 1, 4] (в один день не продали ничего).

Что такое функция правдоподобия?

Это "измеритель правдоподобности" значения \lambda при ваших данных. 

Показывает: "Насколько вероятно увидеть такие данные, если истинное среднее =\lambda?"

Как мы уже знаем, события-счетчики можно пробовать описать распределением Пуассона, давайте в явном виде для него и посчитаем:

L(\lambda \mid \text{данные}) = P(\text{данные} \mid \lambda) = P(3 \mid \lambda) \times P(0 \mid \lambda) \times P(2 \mid \lambda) \times \cdots

Пример для \lambda=2

  1. P(3 \mid \lambda=2) = \frac{e^{-2} \cdot 2^3}{3!} \approx 0.1804

  2. P(0 \mid \lambda=2) = \frac{e^{-2} \cdot 2^0}{0!} \approx 0.1353

  3. И так далее для каждого значения в нашей выборке

  4. Перемножаем все вероятности

  5. L(\lambda=2) \approx 0.0009

Что показывает отношение правдоподобий (Likelihood Ratio)?

Сравнивает правдоподобность двух значений \lambda, как бы отвечает на вопрос «Во сколько раз одно \lambda правдоподобнее другого при этих данных?»

Пример:

  1. L(\lambda=2) \approx 0.0009

  2. L(\lambda=1.5) \approx 0.0006

  3. L(\lambda=2)/L(\lambda=1.5) = 1.5

То есть "\lambda=2 в полтора раза правдоподобнее чем\lambda=1.5" для ваших данных.

Для нашей задачи детекции падения продаж до 0 мы сравниваем правдоподобие на «нормальном» уровне с правдоподобием на «нулевом» уровне.

На графике выше можно увидеть правдоподобие для данных [3, 0, 2, 1, 4] из нашего примера. Кривая показывает "правдоподобность" разных значений\lambda . Отношение правдоподобия это сравнение высот кривой для двух разных \lambda.

Часто для работы с отношениями правдоподобий их логарифмируют (Log-Likelihood ratio), чтобы дроби превратить в разности.

LR = \frac{L(\lambda_1)}{L(\lambda_2)} \quad \rightarrow \quad LLR = \log(LR) = \log\left(L(\lambda_1)\right) - \log\left(L(\lambda_2)\right)

Таким образом, если отношение правдоподобий мало, то более вероятно наблюдать имеющийся отрезок ряда продаж при естественном уровне продаж, чем при нулевом; если это отношение велико, то более вероятно, что продажи значимо упали до 0, чем они остались на естественном уровне. Поскольку нужно вовремя определить вторую ситуацию, надо уметь детектировать момент, когда это отношение становится значимо велико, при этом значимое падение отношения (связанного с ростом продаж) нет надобности отслеживать.

Расчет CUSUM-статистики g_k для k-го дня, прошедшего с начала периода детекции, делается по таким формулам:

s_i = \log \left( \frac{p_{\lambda \approx 0}(y_i)}{p_{\lambda = \lambda_{\text{естественное}}}(y_i)} \right)

s_i - логарифм отношения вероятностей наблюдать продажи, величиной в y_i штук в день iс момента начала периода детекции при нулевом и при естественном уровне продаж.

S_k = \sum_{i=1}^k s_i

S_k– это логарифм произведения отношений вероятностей наблюдать ряд продаж при этих двух интенсивностях продаж.

Далее из них собирается CUSUM-статистика. Во-первых, вычисляется минимальное значение нашего «правдоподобия» среди тех, которые мы видели за k шагов:

m_k = \min_{1 \leq j \leq k} S_j

И оно используется для «сравнения» с текущим:

g_k=S_k-m_k

Для систематического погружения в детали CUSUM рекомендую обзор.

На рисунке ниже показаны графики, первый из которых соответствует ряду из g_k, второй – ряду S_k:

Как можно догадаться, при нормальном или повышенном уровне продаж значение величиныS_k, отражающей отношение вероятностей, идет вниз. Наоборот, при аномально низком уровне продаж величина S_kрастет. Поскольку нам нужно детектировать только падение продаж, из S_kвычитается минимум m_k, тем самым как бы экранируя поведение S_k, связанное с естественной и более высокой интенсивностью продаж (видно, что относительно исходной формулировки CUSUM у нас знак другой). Последнее наглядно видно на графике для g_k, когда при нормальном уровне продаж значение g_k близко к 0, а при аномально низком – идет вверх.

Чтобы детектировать остановку продаж по позиции необходимо определить порог, который должна превысить статистика g_k. Для этой цели используется параметрический бутстреп:

  • N раз, симулируется выборка из распределения Пуассона (или его смеси с распределением Бернулли) длиной в период детекции, со средним, равным естественному уровню продаж

  • Для каждой такой выборки считается соответствующий временной ряд со значениями статистики

  • Выбирается значение порога, которое превышается этой статистикой в заданном проценте случаев, скажем, не более, чем в 5% случаев из N

Вообще говоря, эти 5% - \alpha из АБ-тестов. Так можно балансировать между точностью и покрытием. Важно понимать цену ошибки: сигнал ложный, сотрудник зря подходил к полке vs скорость реакции  – чем меньше % случаев, тем дольше ждем.

Величина порога - длина периода непродаж, измеренная в периодах, когда в среднем продается в точности 1 единица товара, при превышении которой происходит детекция остановки продаж. На рис. ниже синим показан ряд продаж позиции, черным - график статистики g_k, а красной горизонтальной линией - значение порога. При достаточном количестве дней без продаж статистика g_k превышает значение порога и продажи по позиции признаются остановившимися.


Сравнение с магазинами-аналогами и фиксация аномалии

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

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

  • Единство географического местоположения (общность сезонных колебаний)

  • Примерное соответствие календарей акций и скидок на товары

  • Достаточное по объему количество магазинов-аналогов с целью получения статистически надежных выводов

  • Трудоемкость расчета (расчет сравнения по аналогам не должен быть слишком трудоемким)

Позиция с остановкой продаж считается аномалией и выдается в качестве сигнала в магазин, если при сравнении динамики продаж этой позиции в магазине, где выявлена остановка продаж, с динамикой продаж этой позиции в магазинах-аналогах выявлено более глубокое, чем у аналогов снижение продаж.

У нас существует несколько способов фильтрации по аналогам:

  • По динамике дискретности позиции. Если в периоде непродаж дискретность на аналогах выше, то позициях считается аномалией. Здесь разумно в некотором виде сравнивать друг с другом распределения дискретностей.

  • По тренду динамики. У временных рядов дискретностей можно выделять тренды и сравнивать их: если у магазина продажи летели в 0 быстрее аналогов, значит это аномалия. Уточню, что в пункте 1 сравниваются сами дискретности, а здесь – тренды ее изменений.

  • По взвешенному тренду динамики. Тренд можно вычислять по-разному, но, как бы то ни было, для высокодискретной позиции наибольший вклад в вычисление этого тренда дадут дни с ненулевыми продажами. Для некоторых случаев дни периода непродаж можно брать в вычисление тренда с некоторым весом

  • По сравнению непродаж с наихудшим аналогом. Можно оценить дискретность продаж товара в наихудшем из магазинов-аналогов и отбирать позиции, по которым длительность непродаж хуже, чем несколько этих дискретностей.

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

  1. Более высокодискретные товары, поскольку для них падение продаж до нуля является более глубоким относительно их более низкодискретных аналогов

  2. Товары с более длительным периодом непродаж (в дискретностях) относительно аналогов.


Выводы

Предлагаемый алгоритм детекции аномалий спроса анализирует продажи позиции в двух аспектах:

  • Падение продаж позиции относительно естественного уровня продаж для того магазина, где эта позиция продается

  • Глубина падения продаж в данном магазине относительно таковых в магазинах-аналогах

Круг параметров, которые необходимо настроить для получения желаемого поведения алгоритма таков:

  • Значение p для определения величины порога

  • Длина периода детекции проблемы

  • Степень жесткости фильтрации при сравнении с аналогами

Дальнейшие шаги развития указанного алгоритма предполагают различные способы моделирования распределений продаж (подбор разных вариантов помимо Пуассона и ZIP) и подбор иных вариантов способов детекции значимости остановки.

Комментарии (0)