Оцените удельные продажи розничных товаров Walmart.

Оглавление:

  1. Проблема бизнеса/реального мира
  2. Постановка задачи
  3. Источник данных и обзор данных
  4. Использование ML в решении проблемы
  5. Показатели эффективности
  6. Исследовательский анализ данных
  7. Существующий подход
  8. Первоклассное решение
  9. Разработка функций и предварительная обработка данных
  10. Объяснение моделей
  11. Сравнение моделей
  12. Представление Kaggle
  13. Развертывание
  14. Будущая работа
  15. использованная литература

1. Бизнес-проблема:

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

2. Постановка проблемы:

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

Эта проблема размещена на Kaggle Открытым центром прогнозирования Макридакиса (MOFC) в Университете Никосии, который проводит передовые исследования в области прогнозирования. MOFC хорошо известен своими соревнованиями Makridakis, первое из которых проводилось в 1980-х годах. (Прогнозирование M5) — это пятая итерация, в которой мы используем иерархические данные о продажах из Walmart для прогнозирования ежедневных продаж на следующие 28 дней.

3. Источник данных и обзор данных:

Данные загружаются с Kaggle. "Источник"

3.1 Обзор данных:

Данные о продажах, предоставленные Walmart, охватывают магазины в трех штатах США (Калифорния, Техас и Висконсин) и включают уровень товара, отдел, категории продуктов и сведения о магазине. Кроме того, в нем есть поясняющие переменные, такие как цена, рекламные акции, день недели и специальные события.

Общий обзор организации серии M5 на приведенной ниже блок-схеме.

Данные в основном разделены на 3 файла:

  1. Calender.csv — содержит информацию о датах продажи продуктов, а также о поведении дат (например, месяц, год, день недели, любое особое событие в этот день).
  2. sales_train_evaluation.csv — содержит исторические данные о ежедневных объемах продаж ([включает в себя от d_1 — d_1941], т. е. с 2011–01–29 по 2016–05–22) данные за продукт и магазин
  3. sell_prices.csv — содержит информацию о цене товаров, проданных в магазине и на дату.

4. Использование машинного обучения для решения проблемы:

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

5. Показатели эффективности:

Часто используемой метрикой в ​​задачах на основе регрессии является RMSE. Здесь мы используем пользовательскую метрику WRMSSE в качестве оценочной метрики для этой проблемы. Он определяется как:

где Y_t — фактическое значение будущих продаж исследуемого временного ряда в точке t, (Y_t)^ — сгенерированное прогнозное значение продаж из модели, n — длина обучающей выборки (количество исторических наблюдений), а h — горизонт прогнозирования (28 дней).

6. Исследовательский анализ данных:

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

6.1. Обзор данных:

6.2. Общий объем продаж во всех магазинах в дневном и годовом исчислении:

Наблюдения:

  1. С 2011 по 2016 год наблюдается небольшая тенденция к увеличению общего объема продаж, и 1 января каждого года мы наблюдаем нулевые продажи (мы можем наблюдать, что магазины закрыты в канун Нового года).
  2. Существует аналогичная картина общих продаж по годам и годовая сезонность.
  3. С 2012 по 2015 год общие продажи мы наблюдаем за каждыми 2-месячными продажами, имеющими аналогичную картину, из этого мы можем предположить, что временные ряды каждого года за 2012–2015 годы являются стационарными временными рядами.

6.3. Доля продаж:

Наблюдения:

  1. Из трех штатов на Калифорнию приходится высокий процент от общего объема продаж, общий объем продаж Техаса и Висконсина почти равен.
  2. Несмотря на то, что на штат Калифорния приходится 43,6% от общего объема продаж, интересно, что 2 из общего объема продаж его магазина находятся в конце 4, у магазина CA_3 самые высокие продажи, а у магазина CA_4 самые низкие продажи.
  3. У категории продуктов питания самая высокая доля продаж, а у категории хобби самая низкая. Можно сделать вывод, что люди покупают больше для бытовых нужд, чем для хобби, а продукты питания являются жизненно важными, поэтому продажи также высоки.
  4. Самые высокие продажи в отделе "ЕДА_3", а самые низкие - в отделе "УВЛЕЧЕНИЯ_2". Только на отдел «FOOD_3» приходится 50% продаж, а на остальные 6 отделов приходится 50% продаж. Несмотря на то, что общий объем продаж в категории «УВЛЕЧЕНИЯ» намного меньше (9,3%), интересно, что в его отделе «HOBBIES_1» количество продаж выше, чем в отделе FOODS категории FOODS_1.

6.4. Соотношение категорий товаров и товаров по сравнению с долей продаж:

Наблюдения:

  1. Все три штата имеют одинаковую долю продаж для всех трех категорий продуктов. Доля продаж категории продуктов питания одинакова для штатов Техас и Висконсин.
  2. На категорию продуктов питания приходится более высокий уровень продаж, а хобби — самый низкий для всех 3 штатов. В штатах Техас и Висконсин примерно одинаковая доля продаж категории товаров «Хобби».

6.5. Год и категория продукта по сравнению с долей продаж:

Наблюдения:

  1. 2015 г. имеет наибольшее количество продаж, а 2016 г. - самое низкое количество продаж (предоставлено только 4 месяца продаж).
  2. Из трех категорий продажи продуктов питания выше во все годы. Продажи продуктов питания, товаров для дома и хобби в 2013 и 2014 годах одинаковы.
  3. В 2012–2015 годах в продажах категории «Продовольствие» нет большой разницы.

6.6. Общий объем продаж за день по категориям товаров:

Наблюдения:

  1. Ежедневный общий объем продаж категории продуктов питания является самым высоким и имеет тенденцию к росту (продажи растут с годами)
  2. Ежедневные общие продажи категории «Домоводство» также имеют тенденцию к росту, а категория «Хобби» является наименьшей и остается практически неизменной на протяжении многих лет.

6.7. Топ-10 продуктов с самыми высокими продажами:

Наблюдения:

  1. Мы видим, что в первую десятку товаров с наибольшим количеством продаж входят продукты питания (поскольку продукты питания необходимы).
  2. Во-первых, самые высокие продажи относятся к продукту "FOODS_3_090_CA_3", принадлежащему магазину "CA_3" в штате "Калифорния". На втором месте по продажам находится продукт "FOODS_3_586_TX_2", принадлежащий к магазину "TX_2" в штате "Техас". продукт 'FOODS_3_586_TX_3', принадлежащий магазину 'TX_3' из штата Техас.
  3. Висконсин заявляет, что продукт «FOODS_3_090_WI_3», принадлежащий магазину «WI_3», занимает шестое место по продажам.
  4. Интересно, что несмотря на то, что в Калифорнии доля продаж Walmart выше, в первой десятке есть 6 продуктов с самыми высокими продажами из штата Техас.

6.8. Анализ отсутствующих значений:

Наблюдения:

  1. В Prices_data нет пропущенных значений.
  2. Но здесь sell_price — это цена товара за данную неделю/магазин, которая предоставляется за неделю (средняя за семь дней). Если этого нет в наличии, это означает, что товар не продавался в течение исследуемой недели. Нам нужно проверить эти продукты с отсутствующими ценами.
  3. Отсутствующие цены на товары в магазине CA_2 выше. Магазины TX_1 и TX_2 имеют равный процент отсутствующих цен на товары.
  4. Во всех магазинах отсутствует более 17% цены товара.
  5. Нет пропущенных значений для всех столбцов, кроме столбцов событий и типов событий. Понятно, что если имя_события_1/имя_события_2 равно NAN, т. е. этот день не является днем ​​проведения мероприятий.

6.9. Соотношение продаж по дням SNAP и без SNAP по каждой категории продуктов и каждому штату:

Наблюдения:

  1. Продажи в дни SNAP превосходят продажи в дни без SNAP в случае категории продуктов FOOD. Аналогичное количество продаж в дни SNAP или без SNAP в случае категорий продуктов Hobbies и Household.
  2. Продажи по программе SNAP выше в штате Висконсин и ниже в штате Калифорния. Продажи по программе SNAP в штате Висконсин значительно выше, чем продажи без SNAP.

6.10. Влияние событий на продажи:

Наблюдения:

  1. Продажи на спортивных мероприятиях увеличиваются по сравнению с национальными мероприятиями, где продажи снижаются.
  2. Увеличение продаж является высоким в дни спортивных мероприятий и вторым по величине в дни культурных мероприятий и дней религиозных мероприятий, продажи остаются постоянными (т.е. не увеличиваются и не уменьшаются по сравнению со средними продажами в дни мероприятий).
  3. Продажи на спортивных мероприятиях составляют 110% от средних продаж в дни мероприятий, т. е. средний объем продаж в дни мероприятий увеличивается на 10%.
  4. Продажи на культурных мероприятиях составляют 108% от средних продаж в дни мероприятий, т. е. увеличение на 8% к средним продажам в дни мероприятий.
  5. Продажи на национальных мероприятиях составляют 89% от средних продаж в дни мероприятий, т. е. наблюдается снижение на 11% к средним продажам в дни мероприятий.
  6. Продажи выше в дни без событий по сравнению с днями событий, и в целом продажи снижаются в дни событий (люди делают меньше покупок в праздничные дни), но ненамного.

6.11. Тенденция продаж с начала и до конца в течение месяца:

Наблюдения:

  1. Очевидно, мы наблюдаем, что продажи высоки в начале месяца и остаются стабильными в течение первых 15 дней месяца.
  2. После 15 дней месяца продажи постепенно снижаются и составляют 75% от продаж в начале месяца.

6.12. Продажи в выходные и будние дни в каждом месяце 2016 года:

Наблюдения:

  1. Продажи выше в начальные дни каждого месяца.
  2. Продажи выходного дня высоки как в начале месяца, так и в конце месяца.
  3. К концу месяца наблюдается снижение продаж в будние дни.
  4. Но продажи по выходным стабильны (высокие как в начале, так и в конце месяца).
  5. Исходя из этого, можно сказать, что на продажи влияют выходные и будние дни.

6.13. Относительное изменение продаж по сравнению с предыдущими днями без привязки к дням привязки:

Наблюдения:

  1. Каждое событие привязки длится «15 дней», поэтому мы проверили изменение продаж за последние 15 дней с первого дня привязки до продаж за 15 дней привязки.
  2. Мы наблюдаем, что продажи Walmart всегда увеличиваются по сравнению с моментальными событиями (мгновенными событиями в трех штатах).
  3. Здесь мы нанесли на график относительное изменение/увеличение продаж для каждого из 60 моментальных событий, произошедших в 2011–2016 годах.
  4. 37-е мгновенное мероприятие, начинающееся с «2014–05–01» и заканчивающееся «2014–05–15» (продолжительностью 15 дней), обеспечивает увеличение продаж до 30,55%, а 49-е мгновенное мероприятие начинается с «2015–03–01» и заканчивающийся 15 марта 2015 г. (длится 15 дней), продажи вырастут до 29,96%.
  5. Наименьшее увеличение продаж по сравнению с моментальными событиями составляет 3,31% для 57-х моментальных событий, начиная с «2015–11–01» и заканчивая «2015–11–15».
  6. Еще одна интересная вещь, которую мы наблюдаем, это моментальные события, происходящие в начале месяца.

6.14. Годовой доход от каждой категории товаров, каждого штата, каждого магазина:

Наблюдения:

  1. Продукты питания приносят самый высокий доход, за ними следуют товары для дома и, наконец, товары для хобби. 88% дохода генерируется только за счет продуктов питания и товаров для дома. Аналогично пропорции продаж каждой категории продуктов.
  2. Доход из Калифорнии является самым высоким, а Техас и Висконсин имеют одинаковую долю дохода. Несмотря на то, что доля продаж в Висконсине составляет 27,6%, полученный доход составляет 26,3%, в отличие от доли продаж в штате Калифорния, которая составляет 43,6%, но полученный доход увеличивается до 45%. Доля выручки и продаж составляет те же 28,8% для штата Техас.
  3. Магазин CA_3 имеет высокую выручку, а CA_4 — самую низкую долю продаж. Интересный магазин TX_3, который занимает 6-е место по доле продаж, но приносит 4-е место по доходу. Напротив, магазин WI_3, занимающий 5-е место по объему продаж, приносит доход, который находится на 7-м месте.
  4. Техас и штат Калифорния, хотя в некоторых магазинах меньше продаж, полученный доход высок (возможно, в этих штатах продаются дорогие товары). Напротив, в штате Висконсин, хотя продажи высоки, полученный доход сравнительно меньше (возможно, здесь продается больше продуктов по более низким ценам).
  5. 2015 год принес высокие доходы. Мы отмечаем, что выручка увеличивается из года в год. В 2016 году выручка меньше, так как доступны данные только за 4 месяца.

6.15. Штат, магазин, продажи в выходные и будние дни:

Наблюдения:

  1. Во всех трех штатах продажи в выходные дни выше, чем в будни.
  2. В Калифорнии и Техасе продажи в воскресенье выше, чем в субботу, но в Висконсине продажи в воскресенье меньше, чем в субботу.
  3. Во всех магазинах Калифорнии и Техаса распродажи по выходным высокие, а по воскресеньям продажи выше, чем по субботам. Магазин CA_3 имеет высокие продажи на выходных, так как мы видели, что CA_3 также имеет высокий доход и самую высокую долю продаж.
  4. Во всех магазинах Висконсина распродажи по выходным высоки, а по воскресеньям — меньше, чем по субботам.
  5. Во все годы продажи в выходные дни высоки по сравнению с продажами в будние дни. В 2011 и 2013 годах продажи в субботу выше, чем в воскресенье.

7. Существующий подход:

7.1. Решение победителя:

https://www.kaggle.com/c/m5-forecasting-accuracy/discussion/163216

В этом решении для обучения используется модель LightGBM с целью Tweedie. Разделение проверки представляло собой основанное на времени разделение на 5 кратностей, при этом каждое кратное 28 дней продаж прогнозируется с использованием предыдущих исторических данных о продажах. т. е. 1-й раз - поезд d1-d1577 и прогноз для d1578-d1605; 2-й раз — поезд d1-d1829 и прогноз для d1830-d1857; 3-й раз — поезд d1-d1857 и прогноз для d1858-d1885; 4-й раз — поезд d1-d1885 и прогноз для d1886-d1913; 5-й раз — обучить d1-d1913 и предсказать для d1914-d1941, а затем вычислить среднее значение каждой складки для ошибки WRMSSE. Затем окончательная модель обучается от d1-d1941 и прогнозируется на 28 дней d1942-d1969. Стратегия, использованная при разделении модели, применялась для каждого из 10 магазинов; на каждую неделю одна модель обучается прогнозировать продажи, например: модель m1 — прогнозирует первые 7 дней (F1-F7), модель m2 — прогнозирует вторые 7 дней (F8-F14), модель m3 — прогнозирует третьи 7 дней (F15-F15-F14). F21), модель m4 — прогнозирует четвертые 7 дней (F22–28), т. е. всего мы обучаем 40 моделей (10*4) [10 соответствует десяти магазинам, а 4 подразумевает 4 недели для 28-дневного горизонта прогноза]. Извлеченные функции представляли собой функции задержки, скользящее среднее/стандартное значение с различным размером окна. Здесь функции задержки, используемые для модели m1, представляют собой {lag_7, lag_8….lag_21}, m2 имеет функции задержки {lag_14, lag_15,… lag_28}, m3 имеет отставание. функции {lag_21,lag_22,…lag_35}, а m4 имеет функции задержки {lag_28,lag_29,…lag_42}.

7.2. Улучшения:

Как мы видели в нашем анализе из EDA, значение sell_price (цена продукта для данной недели/магазина), которое предоставляется за неделю (среднее за семь дней), отсутствует. Для всех продуктов с отсутствующими значениями sell_price вменено среднее значение sell_price для этого идентификатора продукта. Создан новый набор функций для данных о продажах, функции скользящей медианы вдоль функций скользящей средней, поскольку медианные значения менее подвержены выбросам.

8. Первое решение:

8.1. Прогноз продаж как среднее значение продаж за последние 4 недели для этого рабочего дня

Из EDA мы заметили, что существует еженедельная модель продаж, т. Е. Существует тенденция продаж в каждый будний день на неделе, поскольку продажи в выходные дни выше, чем продажи в будние дни. Используя данные о продажах поездов за последние 4 недели, мы пытаемся вычислить средние продажи для каждого дня недели. Затем спрогнозируйте продажи на следующие 4 недели с этим средним значением продаж на основе дня недели.

8.2. Скользящие средние

Как следует из названия, продажи прогнозируются на основе среднего значения продаж за последние N дней, где «N» — это гиперпараметр. После выполнения настройки гиперпараметра с различными значениями «N» делается вывод, что N = 35 (т. Е. Прогнозирование продаж в данный день как средние продажи за последние 35 дней) дает лучшую производительность.

9. Разработка функций и предварительная обработка данных:

  1. Сначала мы создаем единый фрейм данных, объединяя данные о продажах, данные календаря, данные о ценах. Затем предварительно обработайте отсутствующие значения цен путем преобразования средней цены этого идентификатора.
  2. Извлечение функций из необработанных данных временных рядов играет важную роль в том, насколько хорошо модель способна прогнозировать продажи. Здесь мы создаем некоторые важные функции по историческим продажам, такие как функции отставания, скользящие функции.
  3. Функции задержки создаются таким образом, что для продукта в текущий день он получает свои продажи за 3 месяца до этого с периодом смены, чтобы учитывать 28 дней для прогноза.Постоянная смена скользящего среднего значения и медианы также создаются функции.
  4. Некоторые другие функции извлеченных данных календаря: день месяца, функция со значением года 0 для 2011 и 1 для 2012…. 5 для 2016 года, номер недели дня в месяце, например: 29-е число января соответствует 5-й неделе января. такая функция, которая определяет, является ли день выходным или нет.
  5. Категориальные функции преобразуются в функции, закодированные метками, с помощью LabelEncoder. Также разбивая значения столбцов «d», чтобы взять только номер дня. При этом мы преобразуем все функции либо в числовые, либо в кодированные метками функции, прежде чем применять модели регрессии к окончательному набору данных.

10. Объяснение моделей:

10.1. Линейные и древовидные модели:

  1. После разработки функций и предварительной обработки данных у нас осталось 58 миллионов точек данных, что является огромным объемом данных. Вместо того, чтобы пытаться построить модель ML на этом огромном масштабе данных, мы используем данные за последние 2 года, то есть с 1 января 2014 года. Затем мы разделяем данные на разделение Train-CV-Test по времени следующим образом:

2. SGDRegressor с квадратом потерь, линейная модель подгоняется к данным поезда, и выполняется перекрестная проверка с другим значением альфы (множитель к члену регуляризации) для данных CV, и найденное значение альфы = 0,1 дает более низкий показатель WRMSSE. Затем спрогнозировали продажи на тестовых данных с альфа = 0,1 с локальной оценкой WRMSSE 0,8931.

3. Применяются другие древовидные модели, такие как RandomForest, LGMBRegressor, регрессорная модель AdaBoost. RandomForest работает по методу начальной агрегации, который включает в себя обучение каждой базовой_модели (дерева решений) на другой выборке данных, где выборка выполняется с заменой, таким образом, он объединяет несколько деревьев решений при определении окончательного результата, а не полагается на отдельные деревья решений. LGMBRegressor — это метод повышения градиента, который имеет префикс «Light» из-за его высокой скорости и требует меньше памяти для работы. AdaBoost также повышает алгоритм, который помогает объединить несколько слабых учеников в одного сильного ученика.

4. При настройке гиперпараметров всех этих моделей наблюдается, что RandomForestRegressor с лучшими гиперпараметрами n_estimators = 50, max_depth = 10 дает локальную оценку WRMSSE 0,7487.

LGMBRegressor с лучшими гиперпараметрами num_leaves = 125, Learning_rate = 0,075, локальный показатель WRMSSE равен 0,6087.

AdaBoostRegressor с лучшими гиперпараметрами n_estimators = 50, Learning_rate = 0,05, локальный показатель WRMSSE равен 0,7944.

10.2. Пользовательский ансамбль:

  1. Поскольку мы наблюдаем, что модели на основе дерева ансамбля работают лучше, чем линейные модели, мы пытаемся построить пользовательскую модель ансамбля следующим образом:
  2. Сначала разделите весь набор данных на два набора данных, обучите и протестируйте на основе разделения по времени. Затем набор данных поезда разбивается на два дополнительных полунабора данных D1, D2 на основе разделения по времени.
  3. Из набора данных D1 мы делаем выборку с заменой, чтобы получить N наборов данных и обучить N моделей в качестве базовых учеников. Здесь N → количество базовых учеников является гиперпараметром. Для каждого обученного базового ученика мы получаем прогнозы D2 и тестовые данные и используем их в качестве признаков.
  4. Мы обучаем метамодель с X_i как N функций, где каждая функция, которую мы получаем, основана на прогнозах D2 для каждого обученного базового ученика, а Y_i является реальным значением D2.
  5. После обучения метамодели с N признаками тестовых данных, извлеченных из N обученных базовых учащихся, мы прогнозируем продажи на основе обученной метамодели.
  6. Настройка гиперпараметров выполняется для разных базовых обучающихся, метамодели и «N» → количество базовых обучающихся, и обнаружение XGBRegressor в качестве базового обучающегося и LGMBRegressor в качестве метамодели с N = 18 дает более низкий локальный WRMSSE 0,6995.

11. Сравнение моделей:

Производительность моделей, опробованных на нашей задаче:

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

12. Отправка на Kaggle:

Оценка Kaggle по каждой из моделей:

  1. Из приведенного выше частного рейтинга лидеров мы видим, что из всех моделей Модель пользовательского ансамбля работает хорошо, поскольку она способна прогнозировать продажи с более низким баллом (WRMSSE) = 0,66282 частного балла.
  2. После того, как мы получим количество дней проверки (1914–1941) и количество дней оценки (1942–1969) из окончательной лучшей пользовательской модели ансамбля и отправим ее в заданном формате, мы получим рейтинг рейтинга 0,66282, который занимает strong>352 из 5558 участников и входит в 7% лучших.

13. Развертывание:

  1. После обучения пользовательской модели ансамбля с лучшими гиперпараметрами мы сохраняем базовые модели и метамодель в файлах pickle и развертываем их в AWS вместе с API-интерфейсом flask, построенным вокруг конечного конвейера, который возвращает прогнозируемые продажи.
  2. Создается веб-страница, которая принимает файл csv или txt в качестве входных данных и дает прогноз продаж на следующие 28 дней, то есть с 23 мая 2016 года по 19 июня 2016 года.
  3. Прогноз продаж для образца продукта из магазина с его историческими продажами в файле csv выглядит следующим образом.

Демонстрационное видео, объясняющее результаты

14. Будущая работа:

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

15. Ссылки:

  1. https://www.analyticsvidhya.com/blog/2019/12/6-powerful-feature-engineering-techniques-time-series/
  2. https://www.kaggle.com/c/m5-forecasting-accuracy/discussion/144067
  3. https://www.kaggle.com/dhananjay3/wrmsse-evaluator-with-extra-features
  4. https://www.kaggle.com/headsortails/back-to-predict-the-future-interactive-m5-eda
  5. https://www.kaggle.com/c/m5-forecasting-accuracy/discussion/163216
  6. https://www.appliedaicourse.com/lecture/11/applied-machine-learning-online-course/3194/mapping-to-ml-problem-time-series-forecastingregression/7/module-6-machine-learning -реальные примеры

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