В этом посте речь пойдет про пилотное ML-исследование для гипермаркета «Утконос», где мы прогнозировали выкуп скоропортящихся товаров. При этом мы учли данные не только по остаткам на складе, но и производственный календарь с выходными и праздниками и даже погоду (жара, снег, дождь и град нипочем только «Taft’у Три погоды», но не покупателям). Теперь мы знаем, например, что «загадочная русская душа» особенно жаждет мяса по субботам, а белые яйца ценит выше коричневых. Но обо всем по порядку.



Пилот в ритейле больше чем пилот


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


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


И вот тут самое время договориться о пилотном исследовании!


Сами по себе пилоты по сравнению с полноценным ML-проектом имеют понятные ограничения и специфику.


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

Разумеется, эти особенности проявляются не в каждом пилотном проекте, но они составляют важную часть его рисков.


Немного о задаче


Задолго до знакомства с машинным обучением «Утконос» уже использовал собственную аналитическую систему, которая с весьма высокой точностью прогнозировала выкуп товаров на неделю. Тем не менее, многие товары нуждались в более оперативных поставках. В первую очередь это касалось скоропортящихся продуктов, многие из которых еще и весьма недешевы. Традиционная вилка: купишь много — будут убытки, купишь недостаточно — покупатель уйдет за своей любимой телячьей вырезкой, заготовленной при полной луне и слегка моросящем дожде, к конкуренту. Для достаточно точного прогноза на послезавтра возможностей текущей системы оказалось недостаточно. Решения на базе машинного обучения, позволяющие учитывать большее число факторов, выглядели в данном случае более перспективными.


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


  • из категории «Охлажденное мясо» — как скоропортящиеся продукты, данные по которым наиболее важно обновлять оперативно;
  • из категории «Яйцо куриное» — как товары со специфическим сезонным спросом, которые нельзя спрогнозировать просто как «в четверг все покупают X, а в пятницу — X умножить на коэффициент». Хотя куриные яйца не являются сложными для прогнозирования выкупа и как раз для них вполне приемлем недельный горизонт планирования, именно на этих товарах предстояло показать, что машинное обучение действительно видит сложные взаимосвязи и строит нетривиальный прогноз.

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


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


Нас ожидало короткое, но увлекательное приключение.


Данные со складов: загадочные и необходимые


При работе с историческими данными первый же вопрос, который перед нами встал, — как отделить реальные продажи от «максимально доступных продаж» (т.е. случаев, когда заканчивающийся на складе товар был выкуплен на 100%, однако при его наличии объем продаж мог быть выше)? Такие неосуществленные желания покупателей ведь никак не отображаются в данных.


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


Поэкспериментировав с разными вариантами (реконструкцией «действительного спроса» на основании по-разному рассчитанных коэффициентов и фильтрацией датасета продаж с разными порогами доступности), мы в итоге подобрали оптимальный порог, который не делал набор данных слишком узким. Идеал — наличие товара в течение всего дня — заметно сокращал данные даже для самых продаваемых товаров. А так как мы работали с скоропортящимся охлажденным мясом, очевидно, что закупки по нему балансировать так, чтобы как можно меньше товара отправлялось в мусор, поэтому случаи неполной доступности были нередки.


Охлажденное мясо (необычной птицы)


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


В целях экономии времени «из коробки» достали готовую библиотеку, хорошо работающую с временными рядами, — Prophet от Facebook.



Результаты работы модели на обучающих данных сразу показывают и достоинства, и недостатки. Модель хорошо улавливает «сезонность» спроса, но плохо — пики покупок. Также праздники, зашитые в Prophet по умолчанию, не до конца совпадают с нашим календарем. Относительное отклонение составляет 31.36%, будем его дальше использовать как базовый результат.


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



У нашего охлажденного мяса четкий возрастающий тренд на общее число покупок, число покупок растет от понедельника к субботе и падает в воскресенье, летом покупки заметно «проседают». Плохо, что лето не попадает в наш тестовый период; с другой стороны — запомним, что период каникул и отпусков важен для уровня продаж, ведь летние каникулы — в России далеко не единственные.


Логичный вопрос: можно ли использовать эту модель сразу для прогноза на следующие полгода?
Интуитивно кажется, что нет. Эксперимент показал, что так и есть. Общий рисунок сезонности в течение недели верный. Но сразу стало очевидно, что от общего рисунка сезонности есть миллион отклонений как вверх, так и вниз, а среднее отклонение 45.71 % сильно превышает результаты на обучающих данных. Понятно, что это никуда не годится.


Для начала попробуем обучать модель ежедневно, представив, что каждый день после завершения работы магазина датасет дополняется продажами за «сегодня». Мы уже знаем, что в продажах есть восходящий тренд в целом — возможно, что оборот на наших тестовых данных растет с большей интенсивностью за счет более активной маркетинговой активности, чем было на обучающем наборе.


Относительный успех: при ежедневном дообучении модели относительное отклонение составляет 33.79%. Мы дополнили параметры модели информацией о перенесенных выходных, религиозных постах и праздниках, традиционных для России (таких как Новый год, Пасха и ряд других). Также были добавлены резкие изменения погоды: дни, когда температура скакала вверх или вниз на 10+ градусов или просто была заметно выше или ниже других дней этого месяца. Теперь в среднем за полгода наш прогноз отклонялся от реальных продаж на 28.48%, а в целом модель начала лучше учитывать всплески покупательской активности. Мы улучшили среднее отклонение на пять процентов! При том, что с выбросами Prophet в принципе работает плохо и данные рекомендуется от них очищать, — это было заметное движение вперед.




Прежде чем показывать предварительные результаты, встал вопрос: можем ли мы еще немного улучшить прогноз? Если посмотреть на корреляцию продаж товара и его средней цены в день — понятно, что это связанные признаки, а цена при построении модели никак не учитывается. Но, судя по набору данных, мы могли взять только некую «усредненную цену за единицу»: в заказах она нередко варьировалась в течение одного и того же дня, т.е. была записана с личной скидкой покупателя, а «витринные» цены в набор данных не входили.



Коэффициент корреляции между средней ценой за единицу в день и числом проданного объема этого вида охлажденного мяса составил -0.61 при p < 0.01. Понятно, что «средняя цена за единицу» — не идеальный показатель: в том случае, если в течение дня было много покупок от, скажем, партнеров с постоянной большой скидкой, в данные закрадутся опасные шумы. Мы же хотели выделить дни, когда были маркетинговые воздействия: общие скидки на группу товаров, скидки всем, кто введет свободно распространявшийся промокод и т.п.


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



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


Яйца куриные (и сложно предсказуемые)


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



В целом наша модель ожидает, что некоторое повышение спроса в Пасху будет, но ее прогноз меньше реального показателя почти в 2 раза (и это отклонение в ~100% во время пасхальной недели делает среднее отклонение за полгода невероятно большим). Почему? Ведь пасхальная неделя случается ежегодно — в данных предыдущих 2 лет должен быть паттерн!


Исследовательский анализ показал, что паттерна нет. Мы его ждали, даже «Утконос» был уверен, что он будет, — но его нет и ничего удивительного, что модель не справляется. В 2018 году (это наши тестовые данные) пик покупок приходится на всю неделю перед Пасхой вплоть до 7 апреля.


В саму Пасху (8 апреля в 2018) покупки яиц всегда падают, что совершенно корректно видит модель. А вот в 2017 году Пасха приходится на 16 апреля, а пик покупок в исторических данных — 8 апреля, и в этом году пик — один день. В 2016 году Пасха приходится на 1 мая. Пик покупок — 29 апреля, с подъемами на день до и день после. В 2015 году Пасха приходится на 12 апреля, пик покупок снова один день, 9 апреля.



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


Сможем лучше!


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


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


В набор данных о «внешнем мире» была собрана следующая информация:


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

Отдельно были добавлены признаки проведения отдельных маркетинговых акций и цена за единицу товара.


На расширенном наборе данных мы снова построили модель, ежедневно дообучавшуюся на новых данных, теперь уже используя RandomForestRegressor. Относительное отклонение немного улучшилось: до 27.29 %. На графике видно, что новая модель лучше прогнозирует влияние маркетинговых акций, но хуже — недельную сезонность.



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



На проверке, можем ли мы еще немного подумать и улучшить результат, пилотное исследование завершилось. Главные цели были достигнуты: мы показали, что машинное обучение в принципе применимо для данных заказчика и показывает неплохие результаты даже в режиме «быстрого пуска», и наметили направление для будущего роста в том случае, если из этого пилота вырастет полноценный проект.


Александра Царева, специалист отдела интеллектуального анализа «Инфосистемы Джет»

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


  1. Kasyan666
    26.03.2019 14:32

    Прошу прощения, а не из-за этого нововведения, примерно начиная с Нового Года, из уже сформированной и заказанной (а, иногда, и предоплаченной) корзины «пропадают» товары с формулировкой: «К сожалению, не будет доставлен следующий товар: xxx»?


    1. atsareva Автор
      26.03.2019 14:35

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


      1. Mogwaika
        26.03.2019 14:53

        В случае «пропажи» важна синхронизация сайта с бд склада, а не столько наличие товара на складе.


  1. gofat
    26.03.2019 14:43

    Странновато выглядит график с продажами в день прогноза, как топовой фичей. Вы уверены, что у вас модель не «протекала»? А то выглядит, будто вы частично на ответе обучались.
    И еще вопрос — а почему не исключить сезонность и тренд из данных простой моделью, а уже по остаткам от нее пройтись RandomForest'ом? Он же тренд не сможет поймать.


    1. atsareva Автор
      26.03.2019 15:45

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

      Тренд на общее возрастание числа продаж мы постарались учесть при генерации фич для RandomForest'а, добавив туда в том числе общие продажи по товару за предыдущие периоды от дня прогноза.


    1. hextler
      27.03.2019 14:46

      почему лес не сможет отследить тренд?


      1. gofat
        27.03.2019 15:03

        Вот статьи, где на примерах показывают ограничения:
        freerangestats.info/blog/2016/12/10/extrapolation
        medium.com/datadriveninvestor/why-wont-time-series-data-and-random-forests-work-very-well-together-3c9f7b271631

        В целом, при постоянном дообучении, либо при отсутствии долговременного тренда, а так же при достаточно «близких» во времени предсказаниях, можно и его использовать. Но это может быть чревато проблемами в реализации.


        1. hextler
          27.03.2019 15:44

          Спасибо!


  1. molec
    26.03.2019 16:26
    +2

    При всех шероховатостях, вы все равно молодцы, что пытаетесь идти по трудному пути!

    1. Пилотом обычно называют все же тестовое внедрение, а у Вас я из текста так и не понял, пошел ли Ваш прогноз в заказ. Это очень важно, тк моментально дает Вам кучу фидбека, гордости за себя и тикетов для работы ;)
    2. Не указан горизонт и уровень прогнозирования, а это очень важно, для понимания цифр. Одно дело, прогнозировать ближайшую неделю и суммарно товарную группу, другое — один товар на пару месяцев вперед.
    3. Касаемо фичи «продажи в день прогноза», если прогноз на будущее. Обычно делают каскад фич: продажи предыдущего дня, продажи предыдущего того же дня недели (пн к пн и т.д.), месяца, года. Не уверен, что здесь это сработает, но все же. И тогда прогноз на много дней надо запускать итеративно, на дальнейших днях либо подменяя факт прогнозом, либо используя модель без фичи.
    4. Не очень понятно, подавали ли флаги дней недели в RF. Банально, логарифмировали ли целевую переменную. Дни недели должны быть в топе!
    5. Такое чувство, что фичу «дней до Курбан-байрам» RF просто использовал как некий идентификатор дня или восходящий тренд.

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


    1. atsareva Автор
      26.03.2019 17:40

      Спасибо за комментарии!
      1. У нас пилотным исследованием (пилотом) обычно называется именно исследование, до внедрения, о нем эта история. А дальнейшие действия заказчика комментировать не можем.
      2. Мы моделировали ежедневный прогноз (на один конкретный день — послезавтра) для одного товара (конкретный SKU, не группа).
      3. Да, у нас были некоторые из перечисленных вами фичей для RandomForest плюс некоторые другие (вроде изменения цены товара относительно этих периодов). Думаю, что можно наинженерить еще и подобрать оптимум, но в данном случае для нас было важно показать принципиальную возможность улучшения первых результатов.
      4. Флаг дня недели — это «день недели для продаж» (в смысле, что день, когда будут происходить продажи, а не когда мы строим прогноз), он пятая по важности фича для RandomForest.
      5. У нас было в числе фичей некоторое число других праздников как флагов и как дней, оставшихся до них (например, Пасха и Новый год), они тоже оказывали влияние для отдельных товаров, хотя и не вошли в топ-5. Скорее всего, это все же шум какой-то природы, но для того, чтобы установить это точно, — стоит еще поисследовать возможные фичи. Например, я бы проверила, нет ли такого, что от мая к концу лета мясо просто покупают больше/меньше благодаря выездам на шашлыки, а Курбан-байрам к концу лета близок.

      К сожалению, по разнообразию прогнозируемых SKU мы были ограничены как по постановке задачи и времени, так набором данных.


      1. molec
        27.03.2019 09:36

        Спасибо за ответ по содержанию.
        По-моему, совсем немного недоработали, чтобы добить задачу и сделать более универсальное решение.
        Ваш вариант заточен на прогноз одного дня, в случае, если захотите прогнозировать даже завтра+послезавтра модель придется перестраивать основательно.
        В таком случае, топ критериев вполне интерпретируем.
        Во-первых, модель вполне разумно говорит, что продажи завтра очень похожи на вчера. Это логично, так она избавляется от влияния годовой сезонности и изменения ассортимента, в некотором смысле восстанавливает тренд.
        Во-вторых, модель строит коэффициент продажи в (день_недели_завтра / день_недели_вчера), вернее 7 таких коэффициентов. Во сколько раз продажи в среду выше таковых в понедельник, во сколько четверг выше вторника и т.д. Так она восстанавливает недельную сезонность.
        На самом деле, здесь уже сделано 95% работы, остается обработать исключительные ситуации.
        В третьих, учесть цену. В идеале, было бы подавать на вход скидку а не цену, результаты бы чуть улучшились.
        Ну и в-четвертых — праздники как еще неописанные всплески спроса. Их Ваша модель скорее всего видит через те самые дни до праздника.

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


  1. Alexashk
    26.03.2019 16:43

    Даты пиков покупки яиц в 2015-2016гг. обусловлены тем, что яйца красят в «чистый четверг».

    Но результат в другие годы это не объясняет.


  1. fivehouse
    26.03.2019 23:22

    Замечательная и образцовая статья по ML! Авторы большие молодцы. Удалось уйти от шаблонов и трескотни. Для того, чтобы статью можно было использовать как пример всем (сверх искусственным интеллектам и ML), хотелось бы видеть в конце оценку результатов в виде таблички. То есть сравнение ожидаемых результатов и полученных результатов. И снова — МОЛОДЦЫ! И Джет и Утконос реально (и скажу честно неожиданно) порадовали прежде всего пониманием и подбором правильных людей и идей.


    1. S_A
      27.03.2019 09:20

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


      1. atsareva Автор
        27.03.2019 14:45

        С большим удовольствием почитаю про ограничения регрессора случайного леса в таких случаях, если вас не затруднит сослаться на материалы по теме! Пока известные мне материалы в основном касались ошибок при генерации фичей и предупреждали про опасность попадания части целевой переменной, которая находится выше по восходящему тренду и не имеет аналогов в тренировочном наборе, в тестовый сет.
        По нашему опыту, для некоторых проектов после преобразования данных из time like в feature like случайный лес дает такие же или лучше результаты (вот просто для примера работа, где Random Forest сравнивается с ARIMA в прогнозировании вспышек гриппа). Про то, как лучше учитывать специфику временного ряда при генерации фич, написано тоже немало. Например, для модели из статьи учитывать возрастающий тренд оборота магазина помогают общие фичи продаж.


  1. squaremirrow
    27.03.2019 14:46

    Некоторое время работал с системой прогноза спроса одного российского ритейлера, так что тема статьи мне во многом близка. Правда, там было совсем мало машинного обучения, в основном старая добрая эконометрика.
    Мы сталкивались с теми же самыми проблемами, что и вы. Спрос чрезвычайно чувствителен к внешним событиям, которые невозможно предсказать из данных по продажам. То есть с сезонностью ещё можно работать, а вот взмах руки коллеги из ценообразования, приводящий к кардинальному изменению спроса, предсказать просто напросто невозможно. И ладно бы, если этот взмах влиял только на один товар — но зачастую такие резкие перемены приводили к полному «переделу рынка» между разными производителями категории. На мой взгляд, невозможно добиться высокой точности даже по ходовым товарам, если отсутствует координация действий между ценообразованиями и прогнозистами. И нужно строить модели, учитывающие перекрестные эластичности спроса, иначе необъяснимые взлеты и падения будут возникать всегда.
    Касательно FB Prophe — мне кажется, или он у вас цепляет сезонность там где не нужно? Например, на последнем графике с яйцами, он похоже зацепил новогодний взлет и падение, и пытался их спрогнозировать по сезонности, хотя ясно, что период там совсем не подходящий. В этом, на мой взгляд, кроется опасность «черных ящиков» — никогда не знаешь, чего от них ждать.


    1. atsareva Автор
      27.03.2019 14:56

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

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