Один из наиболее актуальных вопросов для любой бизнес-модели, основанной на подписке, - это то, что заставляет людей подписываться и, что более важно, отказаться от подписки. Возьмите свои Spotify-ы, Pandora-s или Netflix-ы. Основной поток доходов этих компаний поступает от абонентской платы за подписку. Итак, вопрос о том, как удержать этих клиентов на подписке, жизненно важен для их выживания.

Что такое отток

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

Если вы это пропустили, котел - это метафора услуги, а утечка - метафора оттока.

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

Начнем с изучения имеющихся в нашем распоряжении наборов данных.

Полный набор данных - это очень подробный пользовательский журнал, который хранится в json-файле размером 12 ГБ, хранящемся на AWS - при таких размерах можно использовать только платформы больших данных, такие как Spark. Чтобы понять доступные функции и построить нашу модель, мы начнем с использования небольшого подмножества данных (~ 128 МБ). Этот образец будет использоваться на одной машине для исследовательского анализа данных.

Первый шаг - загрузить наши данные следующим образом:

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

root
 |-- artist: string (nullable = true)
 |-- auth: string (nullable = true)
 |-- firstName: string (nullable = true)
 |-- gender: string (nullable = true)
 |-- itemInSession: long (nullable = true)
 |-- lastName: string (nullable = true)
 |-- length: double (nullable = true)
 |-- level: string (nullable = true)
 |-- location: string (nullable = true)
 |-- method: string (nullable = true)
 |-- page: string (nullable = true)
 |-- registration: long (nullable = true)
 |-- sessionId: long (nullable = true)
 |-- song: string (nullable = true)
 |-- status: long (nullable = true)
 |-- ts: long (nullable = true)
 |-- userAgent: string (nullable = true)
 |-- userId: string (nullable = true)

Исследовательский анализ

Беглым взглядом на данные мы заметили, что есть строки, в которых отсутствует userId. После дальнейшего расследования выяснилось, что только следующие страницы не имеют userId:

+ — — — — — — — — — -+
|                page|
+ — — — — — — — — — -+
|                Home|
|               About|
| Submit Registration|
|               Login|
|            Register|
|                Help|
|               Error|
+ — — — — — — — — — -+

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

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

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

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

+-------------------------+
|page                     |
+-------------------------+
|About                    |
|Add Friend               |
|Add to Playlist          |
|Cancel                   |
|Cancellation Confirmation|
|Downgrade                |
|Error                    |
|Help                     |
|Home                     |
|Login                    |
|Logout                   |
|NextSong                 |
|Register                 |
|Roll Advert              |
|Save Settings            |
|Settings                 |
|Submit Downgrade         |
|Submit Registration      |
|Submit Upgrade           |
|Thumbs Down              |
|Thumbs Up                |
|Upgrade                  |
+-------------------------+

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

+--------------------+-------------+---------+-----+---------+
|              artist|itemInSession|   length|level|     page|
+--------------------+-------------+---------+-----+---------+
|         Cat Stevens|            0|183.19628| paid| NextSong|
|        Simon Harris|            1|195.83955| paid| NextSong|
|         Tenacious D|            2|165.95546| paid| NextSong|
|          STEVE CAMP|            3|201.82159| paid| NextSong|
|             DJ Koze|            4|208.74404| paid| NextSong|
|           Lifehouse|            5|249.18159| paid| NextSong|
|Usher Featuring L...|            6|250.38322| paid| NextSong|
|                null|            7|     null| paid|Thumbs Up|
|            Harmonia|            8|655.77751| paid| NextSong|
|           Goldfrapp|            9|251.14077| paid| NextSong|
+--------------------+-------------+---------+-----+---------+

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

Разработка функций

Теперь, когда мы четко определили оттока пользователей, пора надеть шляпу по науке о данных и попытаться определить факторы, способствующие оттоку. Как вести себя, если он был недоволен услугой? Я постараюсь ответить на этот вопрос с помощью 6 специально разработанных функций ниже:

  1. Средний час, когда пользователь играет песню

Возможно, люди, которым не нравится эта услуга, обычно включают музыку в разное время в день. Понятно, что разница между этими двумя группами незначительна. Фактические значения составляют 11,74 и 11,71 для оттока и непереключенных пользователей соответственно. Это ничтожная разница в 1 минуту и ​​48 секунд. Однако мы сохраним его в нашей модели, поскольку он может оказаться полезным, когда мы объединим его с другими функциями, которые мы собираемся создать.

2. Пол

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

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

+-----+------------------+
|churn|   avg(gender_dum)|
+-----+------------------+
|    1|0.6153846153846154|
|    0|0.5144508670520231|
+-----+------------------+

3. Дни активности

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

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

4. Количество сеансов

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

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

5. Среднее количество песен за сеанс

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

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

6. Количество обнаруженных ошибок

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

Оказывается, отталкивающие пользователи сталкиваются с меньшим количеством ошибок. Это можно объяснить более длительным периодом времени, в течение которого не оттесненные пользователи являются активными. Таким образом, чем дольше мы пользуемся сервисом, тем больше вероятность возникновения ошибок. Учитывая все обстоятельства, количество ошибок невелико.

Построение нашей модели

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

  1. Логистическая регрессия
  2. Случайный лес
  3. Дерево с усиленным градиентом

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

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

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

Конечный результат:

The F1 score for the logistic regression model is: 0.825968664977952
The F1 score for the random forest model is: 0.8823529411764707
The F1 score for the gradient-boosted tree is: 0.8298039215686274

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

Обучение на полном наборе данных

Следующим шагом было взять наш работающий код и запустить его в кластере на AWS. Мы столкнулись с несколькими проблемами, пытаясь провести анализ полного набора данных. Хотя проблемы казались небольшими по своему характеру, на их решение потребовалось очень много времени, учитывая, сколько времени требуется кластеру Spark для обработки данных.

Во-первых, версия Pandas на кластере устарела. Мы обошли проблему, просто изменив проблемный метод с «toPandas ()» на «show ()». Хотя результат не так привлекателен, он служит своей цели.

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

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

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

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

Exception in thread cell_monitor-17:
Traceback (most recent call last):
  File "/opt/conda/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/opt/conda/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/opt/conda/lib/python3.6/site-packages/awseditorssparkmonitoringwidget-1.0-py3.6.egg/awseditorssparkmonitoringwidget/cellmonitor.py", line 178, in cell_monitor
    job_binned_stages[job_id][stage_id] = all_stages[stage_id]
KeyError: 1256

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

Важность функции

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

+--------------+----------+
|feature       |importance|
+--------------+----------+
|days_active   |0.4959    |
|n_sessions    |0.2146    |
|avg_play_hour |0.1355    |
|avg_sess_songs|0.1009    |
|n_errors      |0.0386    |
|gender_dum    |0.0145    |
+--------------+----------+

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

Заключение

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

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

Спроектированные функции ориентированы на различные варианты поведения. Они включали взаимодействие с службой или привычки слушать, которые фокусируются на времени и личности, а не на музыкальном вкусе. Эти поведения также включают меры удовлетворенности службой, например общее количество ошибок, с которыми сталкиваются пользователи. Перед обучением наших моделей выбранные функции масштабируются до диапазона [0,1], чтобы большие значения не искажали модель. В конце концов, мы обнаружили, что нашей лучшей моделью в группе является классификатор случайного леса. И мы попытались обучить его на полном наборе данных, чтобы добиться еще лучших результатов.

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

Будущие улучшения

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