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

в 6:14, , рубрики: Forbes, Hackathon, machine learning, microsoft, Блог компании Microsoft, машинное обучение

Авторы публикации — Дмитрий Сергеев и Юлия Петропавловская.

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

image

Подготовка данных

Большую часть времени провели в очистке, восстановлении и объединении данных, так как датасеты были сильно загрязнены и сгруппированы по четырём отдельным категориям:

  • Контракты клиентов
  • Посещаемость
  • Заморозки
  • Коммуникации между клиентами и клубом

Тестовые и тренировочные наборы данных были разбиты по месяцам. Train содержал информацию о клиентах за декабрь 2015 года, а Test — за март 2016. Для каждой из категорий мы объединили Train и Test части для дальнейшей обработки.

Контракты клиентов

Контракты стали первым набором данных, за который мы взялись, так как именно там содержалась целевая переменная — "продлил ли клиент свой договор", а также коды контрактов и клиентов в количестве 17631 штук, послуживших ключами для объединения всех остальных датасетов. Небольшое количество пропущенных значений в переменных были восстановлены модами. Затем создали фичи для сезона (зима, весна...), месяца и дня, в который был заключен контракт с клубом, и переменные "длительность контракта", "остаток дней заморозки" и "остаток бонусов на счету". Различные категориальные переменные, такие как возрастная группа, сегмент клуба и т.д. оставили без изменений.

Посещаемость

Начали с создания переменной — длительность разового похода в фитнес-клуб.

image

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

Также в датасете присутствовали категориальные переменные, градации которых мы решили сгруппировать в более общие категории. Например, "КатегорияТренера":

additional = ['Сотрудник СПА', 'Врач']
coach = ['Тренер мастер', "Тренер фитнес"]
coach_vip = ["Тренер персональный", "Тренер элит"]
other = ['Другое']

Аналогично — "НаправлениеУслуги":

sport = ["Тренажерный зал", "Водные программы", "Аэробика", "Боевые искусства", "Mind Body", 
         "Танцевальные программы", "Игровые программы", "Йога", "Групповые программы"]

health_beauty = ["Солярий", "Парикмахерские услуги", "Лечебный массаж", "Маникюр, педикюр", "Массаж_SF",
                "Терапевтические процедуры", "Физиотерапевтические процедуры", "Косметические услуги",
                "Аппаратная косметология", "Окрашивание", "Аппаратная косметология_SF", "Врачи", "Врачи_SF",
                "Продажа косметических товаров", "Инъекции", "Прочие услуги SPA", "Лечебное питание", "Инъекции_SF", "SPA"]

Наконец, добавили переменную с частотой посещения клуба в месяц и суммарные количества посещений в различные сезоны (зима, весна..) и сгруппировали данные по кодам клиентов, содержавшихся в датасете по контрактам. Итого, из 3 700 000 записей осталось ~15 000 наблюдений.

Заморозки

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

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

image

Коммуникации

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

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

Сначала разбили переменную "Вид взаимодействия" на 3 дамми:

  • личная встреча
  • телефон
  • другое

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

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

В результате, из 1 500 000 строк получили 15500 и объединили их с финальным датасетом. После преобразования категориальных переменных в дамми количество столбцов раздулось до 72 штук.

Машинное обучение

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

  • Random Forest
  • Neural Network
  • SVM
  • k-NN
  • Naive Bayes
  • Logit regression
  • Decision Stumps

Каждый из классификаторов, в целом, показывал очень неплохие результаты на валидации. Random Forest на 1000 деревьев с 10-fold cv давал 0.9499 AUC, двухслойная нейронная сеть смогла поднять результат до 0.98, а гроза соревнований на Kaggle, XGB, показал впечатляющие 0.982. Также xgboost помог с визуализацией важности признаков:

image

Первая тройка достаточно ожидаема — "длина контракта", "остаток бонусных баллов" и "средняя длина визита". Также в первой десятке "количество успешных коммуникаций", "остаток дней заморозки" и, внезапно, "посещал ли фитнес зимой".

Остальные модели, кроме решающих пеньков, в среднем, давали по 0.92-0.94 AUC и были добавлены в ансамбль для уменьшения коррелированности между различными предсказаниями.

Ансамбль задумывался в виде двух уровней — на первом сотня decision stumps, предсказания по которым объединялись при помощи принципа большинства голосов (majority vote), т.е. если 51 пенёк был "за", а 49 "против", то ставилась единица. На втором — подключались предсказания по остальным классификаторам для последующего объединения.

image

Для создания ансамбля использовался метод взвешенных средних, каждый классификатор тренируется отдельно, а затем из их предсказаний создается линейная комбинация:

image

aj — веса, с которыми предсказания входят в ансамбль
yj(x) — индивидуальные предсказания классификаторов
p — число используемых моделей

Веса определялись путём минимизации logloss-а ансамбля, при помощи замечательной функции minimize, возвращавшей оптимальные значения вектора весов x0.

from scipy.optimize import minimize
opt = minimize(ensemble_logloss, x0=[1, 1, 1, 1, 1, 1, 1])

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

В результате такого отбора отпала логистическая регрессия и, сожалению, все пеньки, зато AUC вырос ещё на пару тысячных процента и составил 0.98486. Totally worth it.

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

image

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

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

Ссылка на репозиторий.

Автор: Microsoft

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js