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

Код, сопровождающий эту статью, можно найти здесь.

Отток клиентов относится к ситуации, когда клиенты перестают вести дела с компанией.

Согласно статье Harvard Business Review, привлечение нового клиента может быть в 5-25 раз дороже, чем удержание существующего.

Фактически, исследование, проведенное Фредериком Райхельдом из Bain & Company, показало, что повышение уровня удержания клиентов на 5% может увеличить прибыль на 25–95%.

Следовательно, минимизация оттока клиентов должна быть приоритетом для компании. Если мы сможем заранее предсказать клиентов, которые уйдут, мы сможем побудить их остаться, предоставив скидки и поощрения.

В этой статье мы рассмотрим проблему прогнозирования оттока клиентов для фиктивного цифрового музыкального сервиса под названием Sparkify. Мы обучим нашу модель прогнозирования на большом наборе данных (~ 12 ГБ) о действиях клиентов в сервисе и попытаемся предсказать клиентов, которые уйдут, на основе их прошлого поведения.

Поскольку этот набор данных слишком велик, чтобы поместиться на одном компьютере, мы будем использовать Apache Spark, чтобы помочь нам проанализировать этот набор данных.

Apache Spark - одна из самых популярных в мире сред распределенной обработки больших данных (с использованием нескольких компьютеров). Spark значительно быстрее Hadoop MapReduce и имеет удобный API, доступ к которому можно получить через ряд популярных языков: Scala, Java, Python и R.

Для этой задачи мы будем использовать Spark Python API, PySpark.

PySpark предоставляет два подхода к управлению фреймами данных: первый похож на библиотеку Python Pandas, а другой - с использованием SQL-запросов.

Spark ML - это API-интерфейс на основе фреймов данных для библиотеки машинного обучения Spark, который предоставляет пользователям популярные алгоритмы машинного обучения, такие как линейная регрессия, логистическая регрессия, случайные леса, градиент-Boosted Tress и т. Д.

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

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

Альтернативный подход - создать учетную запись в Databricks и использовать их Community Edition для создания прототипа вашей модели. Databricks Community Edition предоставляет бесплатный микрокластер объемом 6 ГБ, а также диспетчер кластера и среду ноутбука. Самое приятное то, что доступ не ограничен по времени!

Если вы хотите воспроизвести код или опробовать набор данных, я включил инструкции по настройке для трех облачных сервисов: IBM Studio Watson, Amazon AWS и Databricks.

Краткое описание проблемы

У Sparkify есть план подписки уровня бесплатного и премиум-класса, и клиенты могут отменить или перейти с премиум-уровня на бесплатный в любое время.

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

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

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

Теперь я расскажу об инструкциях по установке для трех сервисов облачных вычислений: IBM Watson Studio, AWS и Databricks. Не стесняйтесь пропустить их и, если хотите, продолжайте читать «Набор данных».

Настройка IBM Watson Studio

Один из самых простых способов настроить и запустить Spark - использовать платформу IBM Watson Studio. Он имеет удобный интерфейс и доступен бесплатный «Lite Plan».

По «Lite-плану» вам будет предоставлено 50 единиц мощности в месяц. Среда Spark Python 3.5 по умолчанию будет потреблять 1,5 единицы мощности в час, что дает вам примерно 33 часа для работы над проектом.

Чтобы настроить IBM Watson Studio, вам необходимо зарегистрировать учетную запись IBM Cloud, если у вас ее нет.

Затем войдите на домашнюю страницу IBM Watson Studio и войдите в систему.

После входа в систему вы попадете на эту страницу. Выберите «Создать проект».

Затем наведите курсор на «Data Science» и нажмите «Create Project».

Введите название вашего проекта и выберите «Создать».

Выберите «Добавить в проект».

Выберите «Блокнот» в качестве типа актива.

Дайте имя ноутбуку и выберите «Spark Python 3.5XS по умолчанию (драйвер с 1 виртуальным ЦП и 4 ГБ ОЗУ, 2 исполнителя с 1 виртуальным ЦП и 4 ГБ ОЗУ каждый)». Затем нажмите «Создать записную книжку».

Это создаст новую записную книжку, в которой вы сможете приступить к программированию.

Чтобы вставить файл данных, выберите значок «Найти и добавить данные» в правом верхнем углу. Просто перетащите нужный файл данных в поле.

Чтобы создать новый сеанс Spark и прочитать файл данных, выберите «вставить в код» и нажмите «Вставить фрейм данных SparkSession».

Это создаст заранее записанную ячейку.

Вы можете раскомментировать последние две строки для чтения в файле данных. Не стесняйтесь изменить имя фрейма данных.

Теперь вы можете создать свой проект!

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

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

Чтобы остановить свою среду, щелкните вкладку «Среда» в верхней части страницы проекта. Щелкните три точки справа от активной среды и выберите «Остановить».

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

Вы можете проверить свою платежную информацию, выбрав «Управление», а затем «Счета и использование».

Преимущество использования Spark на платформе IBM Watson Studio заключается в том, что он поставляется с предустановленными часто используемыми библиотеками, такими как Pandas и Matplotlib и т. Д. В этом отличие от службы AWS EMR, в которой эти библиотеки предварительно не установлены.

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

Настройка Amazon AWS

Теперь я поделюсь инструкциями по установке Amazon Web Services Elastic MapReduce (EMR).

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

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

Затем перейдите в консоль Amazon EMR и нажмите Начать работу с Amazon EMR. После того, как вы вошли в свою учетную запись, вы готовы создавать свои кластеры.

Выберите подходящее место в верхнем правом углу (выберите ближайшее к вам). В меню слева выберите «Кластеры» и нажмите «Создать кластер».

Настройте свои кластеры со следующими параметрами:

  1. Выпуск: emr-5.20.0 или новее
  2. Приложения: Spark 2.4.0 на Hadoop 2.8.5 YARN с Ganglia 3.7.2 и Zeppelin 0.8.0
  3. Тип инстанса: m3.xlarge
  4. Количество экземпляров: 6
  5. Пара ключей EC2. Не используйте пару ключей EC2 или используйте ее, если хотите.

Если вы хотите запустить код как есть, рекомендуется использовать 6 экземпляров m3.xlarge. Конечно, вы можете попробовать меньшее количество экземпляров (например, 3). Однако, если вы столкнулись с такими ошибками, как «Сеанс неактивен», это, вероятно, означает, что в вашей текущей настройке кластера недостаточно памяти для выполнения задачи. Вам нужно будет создать новый кластер с более крупными экземплярами.

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

Затем вы попадете на страницу, которая показывает, что кластер «запускается». Через несколько минут статус изменится на «Выполняется». Наконец, он изменится на «Ожидание». Весь процесс может занять от 3 до 10 минут. На этом этапе вы можете перейти к следующему шагу.

Наконец, вы можете создать свой блокнот.

  1. В меню слева выберите «Блокноты».
  2. Придумайте имя для своей записной книжки
  3. Выберите «Выбрать существующий кластер» и выберите только что созданный кластер.
  4. Используйте настройку по умолчанию для «Сервисная роль AWS» - это должна быть «EMR_Notebooks_DefaultRole» или «Создать роль по умолчанию», если вы не делали этого раньше.
  5. Вы можете оставить остальные настройки без изменений и нажать «Создать записную книжку» в правом нижнем углу.

Затем подождите, пока состояние записной книжки изменится с «Запускается» или «Ожидание» на «Готово». На этом этапе вы можете «открыть» блокнот.

Теперь вы можете приступить к программированию.

Ниже приведен начальный код для создания нового сеанса Spark и чтения полного набора данных:

# Starter code
from pyspark.sql import SparkSession
# Create spark session
spark = SparkSession \
	.builder \
	.appName("Sparkify") \
	.getOrCreate()
# Read in full sparkify dataset
event_data = "s3n://dsnd-sparkify/sparkify_event_data.json"
df = spark.read.json(event_data)
df.head()

Полный набор данных находится по адресу: s3n://dsnd-sparkify/sparkify_event_data.json

Это то, что вы увидите после запуска кода.

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

Следуйте приведенным ранее инструкциям, чтобы настроить новый кластер, а затем вернитесь к параметру «Блокноты» в левом меню. Щелкните существующую записную книжку, которую вы создали, выберите «Изменить кластер» и выберите вновь созданный кластер. Наконец, выберите «Сменить кластер и запустить записную книжку».

Чтобы избежать непредвиденных расходов на AWS, отключите кластеры и удалите записную книжку после завершения анализа. Вы можете проверить это в опциях «Кластеры» и «Блокноты» в левом меню.

Если у вас есть кластеры в нескольких местах, обязательно проверьте все эти места, так как каждое место будет иметь свой собственный список кластеров!

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

При запуске кода в Amazon EMR вы можете столкнуться со следующими ошибками или исключениями:

  1. TypeError: объект типа «NoneType» не имеет len ()
  2. KeyError: 14933 (число могло быть другим)

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

Проведение анализа на AWS EMR обходится мне примерно в 20 долларов США. Это с учетом того факта, что мне приходилось повторно запускать анализ много раз, поскольку мой ноутбук, размещенный на AWS, вылетал из-за нехватки памяти (изначально я пробовал работать с 3, 4 и 5 экземплярами m3.xlarge).

Следовательно, если вы начнете с достаточно большого кластера (6 экземпляров m3.xlarge для этого проекта), вы сможете запустить код один раз без каких-либо проблем. Это может помочь снизить ваши затраты.

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

С одной стороны, в кластерах AWS EMR нет предустановленных библиотек, таких как Scikit-Learn, Pandas, Matplotlib. Вы сможете завершить проект без этих библиотек, хотя вы не сможете выполнять визуализацию данных.

Если вы действительно хотите использовать эти библиотеки, вам нужно будет установить их, следуя инструкциям по этой ссылке. Это видео тоже было бы полезно.

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

Настройка Databricks

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

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

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

Кроме того, Community Edition предлагает вам бесплатный неограниченный доступ к кластеру 6 ГБ (не нужно беспокоиться о сроках!), Что делает его идеальной средой для создания прототипа вашей модели.

Аналогично IBM Watson Studio рабочая среда Databricks поставляется с предустановленными часто используемыми библиотеками, такими как Pandas, Matplotlib.

После того, как вы создали свою учетную запись, перейдите на страницу входа в Databricks Community Edition.

Вас встретит аналогичная страница.

В левом меню выберите «Кластеры».

Выберите «+ Создать кластер» в верхнем левом углу страницы.

Дайте название вашему кластеру. Вы можете оставить версию среды выполнения Databricks без изменений. Выберите версию Python, с которой хотите работать. Я выбрал Python 3 для своего проекта. Вы можете оставить поле «Зона доступности» пустым. По завершении выберите «Создать кластер».

Подождите, пока статус не изменится с «Ожидание» на «Выполняется».

Затем выберите «Рабочая область» в левом меню, затем нажмите «Пользователи». Щелкните стрелку рядом с вашим адресом электронной почты и нажмите «Создать», затем «Блокнот».

Дайте имя своей записной книжке, и вы можете оставить остальные настройки без изменений.

Затем вы захотите загрузить свой набор данных. Выберите «Данные» в левом меню и нажмите «Добавить данные».

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

Когда загрузка будет завершена, выберите «Создать таблицу в блокноте».

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

Поздравляем, можно начинать кодить!

Если вы хотите сохранить файл, нажмите «Файл», затем «Экспорт», затем «iPython Notebook».

Если вы хотите изменить ячейку кода на ячейку уценки, введите «% md» в первой строке ячейки.

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

Несмотря на то, что Community Edition является бесплатным сервисом, все же рекомендуется отключить кластеры и удалить записные книжки, чтобы избежать непредвиденных расходов на AWS. Вы можете проверить статус своих кластеров, нажав «Кластеры» в левом меню.

Обратите внимание, что если вы решите использовать полную платформу Databricks вместо Community Edition, с вас будет взиматься плата за использование AWS.

Набор данных

Схема набора данных:

Строка из набора данных:

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

Это список страниц, которые могут посещать пользователи:

  1. Отмена: пользователь перешел на страницу отмены. Не означает, что отмена завершена.
  2. Отправить на более раннюю версию: пользователь отправил переход с премиум-уровня на бесплатный.
  3. Недурно: пользователь показал отрицательно.
  4. Главная: пользователь перешел на главную страницу.
  5. Переход на более раннюю версию: пользователь перешел на страницу перехода на более раннюю версию. Это не означает, что отправлено на более раннюю версию.
  6. Roll Advertising: воспроизводятся рекламные объявления.
  7. Выход: пользователь вышел из системы.
  8. Сохранить настройки: пользователь внес некоторые изменения в настройки и сохранит их.
  9. Подтверждение отмены n: пользователь отменил подписку.
  10. О программе: пользователь посетил страницу с информацией о компании.
  11. Отправить регистрацию: пользователь отправил запрос на регистрацию.
  12. Настройки: пользователь перешел на страницу настроек.
  13. Вход: пользователь вошел в систему.
  14. Зарегистрироваться: пользователь перешел на страницу регистрации. Не означает, что регистрация завершена.
  15. Добавить в плейлист: пользователь добавил песни в плейлист.
  16. Добавить друга: пользователь добавил друга.
  17. Следующая песня: пользователь прослушал песню.
  18. Недурно: пользователь поднял палец вверх.
  19. Справка: пользователь перешел на страницу справки.
  20. Обновление: пользователь перешел с бесплатного на премиум-уровень.

Среди них, возможно, стоит обратить внимание на следующие страницы: «Следующая песня», которая отслеживает песни, воспроизводимые пользователем, «Отправить на более раннюю версию», которая отслеживает, когда пользователь отправляет запрос на переход на более раннюю версию, и «Подтверждение отмены», которое отслеживает, когда пользователь запрос на отмену подтвержден.

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

Обратите внимание, что пользователь может посетить страницы «Переход на более раннюю версию» и «Отмена», но не может отправить запрос на переход на более раннюю версию или отмену.

Остальные страницы относительно просты. Они указывают на то, что пользователь посетил соответствующую страницу.

Как упоминалось ранее, доступны данные за 2 месяца.

Предварительная обработка данных

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

Если вы читаете данные на своем локальном компьютере, это код небольшого набора данных:

# create a Spark session
spark = SparkSession.builder \
    	.master("local") \
    	.appName("Sparkify") \
    	.getOrCreate()
df = spark.read.json("mini_sparkify_event_data.json")

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

Очистка данных

Есть пара столбцов с нулевыми значениями. В частности, отсутствующие записи firstName, gender, lastName, location, registration и userAgent принадлежат пользователям, которые не вошли в систему или не зарегистрировались.

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

# filter out all entries with missing names. 
# these also removes entries with missing gender, lastName, 
# location, registration and userAgent
df = df.filter(df.firstName.isNotNull())

Страницы, которые не являются NextSong, имеют нулевые значения для artist, length и sessionId. Все эти переменные действительны только при воспроизведении песен (они не равны нулю, только если _18 _ = ’NextSong’), поэтому мы можем оставить их без изменений.

Функциональная инженерия

Сначала мы преобразуем метки времени в datetime. Исходные метки времени указаны в миллисекундах, поэтому мы должны разделить их на 1000 перед преобразованием.

# original timestamp in milliseconds, so divide by 1000 adjust_timestamp = udf(lambda x : x//1000, IntegerType()) 
df = df.withColumn("ts_adj", adjust_timestamp('ts'))  
# convert adjusted timestamp to datetime 
df = df.withColumn("datetime", from_unixtime(col("ts_adj")))  
# convert registration timestamp to datetime 
df = df.withColumn("reg_adj", adjust_timestamp('registration'))  
# convert adjusted registration timestamp to datetime 
df = df.withColumn("reg_datetime", from_unixtime(col("reg_adj")))  
# drop all the timestamp columns. Will not need them 
columns_to_drop = ['registration', 'ts', 'ts_adj', 'reg_adj'] 
df = df.drop(*columns_to_drop)

Затем мы можем пометить месяцы от 0 до N, где N представляет общее количество месяцев, доступных в наборе данных. Мы можем приблизить дату начала анализа как «2018–10–01 00:00:00».

# add start date of analysis 
df = df.withColumn('analysis_start_date',\
		   lit('2018-10-01 00:00:00'))  
# number the months starting from the very first month of the # analysis 
df = df.withColumn("month_num",\
	      	   floor(months_between(df.datetime,\
	           df.analysis_start_date)))

Замените строковые переменные для gender и level двоичными (0 или 1) переменными.

# engineer free or paid binary variable
# free: 0, paid: 1
df = df.replace(["free", "paid"], ["0", "1"], "level")

# engineer male and female binary binary variable
# male: 0, female: 1
df = df.replace(["M", "F"], ["0", "1"], "gender")

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

def define_churn(x):     
	"""
    	Defining churn as cancellation of service or downgrading	from premium to free tier.
	"""     
	if x == "Cancellation Confirmation":
        	return 1
	elif x == "Submit Downgrade":
		return 1
	else:
		return 0      
churn_event = udf(lambda x : define_churn(x), IntegerType())      
df = df.withColumn("churn", churn_event("page"))

Создание ежемесячной статистики для каждого пользователя:

# number of register page visits
df_register = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Register") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numRegister')
# number of cancel page visits
df_cancel = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Cancel") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numCancelVisits')
# number of upgrade page visits
df_upgrade = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Upgrade") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numUpgradeVisits')
# number of downgrade page visits
df_downgrade = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Downgrade") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numDowngradeVisits')
# number of home page visits
df_home = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Home") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numHomeVisits')
# number of about page visits
df_about = df.select('userId', 'month_num', 'page') \
	.where(df.page=="About") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numAboutVisits')
# number of setting page visits
df_settings = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Settings") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numSettingsVisits')
# number of times user save settings changes
df_saveSettings = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Save Settings") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numSaveSettings')
# number of login page visits
df_login = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Login") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numLogins')
# number of logout page visits
df_logout = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Logout") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numLogouts')
# number of songs added to playlist
df_addPlaylist = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Add to Playlist") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numAddPlaylists')
# number of friends added
df_addFriend = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Add Friend") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numFriends')
# number of thumbs up given
df_thumbsUp = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Thumbs Up") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numThumbsUp')
# number of thumbs down given
df_thumbsDown = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Thumbs Down") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numThumbsDown')
# number of advertisements rolled
df_advert = df.select('userId', 'month_num', 'page') \
	.where(df.page=="Roll Advert") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numAdverts')
# number of songs played
df_songsPlayed = df.select('userId', 'month_num', 'page') \
	.where(df.page=="NextSong") \
	.groupBy('userId', 'month_num') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'numSongsPlayed')
# total amount of time user listened to songs
df_totalListen = df.select('userId', 'month_num', 'length') \
	.groupBy('userId', 'month_num') \
	.agg({'length':'sum'}) \
	.withColumnRenamed('sum(length)', 'totalListenTime')

# number of songs played per session
df_songsPerSession = df.select('userId', 'month_num', 'page', 	'sessionId') \
	.where(df.page=="NextSong") \
	.groupBy('userId', 'month_num', 'sessionId') \
	.agg({'page':'count'}) \
	.withColumnRenamed('count(page)', 'SongsPerSession')
# average number of songs played per session
df_avgSongsPerSession = df_songsPerSession.groupBy('userId', 	'month_num') \
	.agg(avg(df_songsPerSession.SongsPerSession).alias	('avgSongsPerSession'))

# number of singers listened per month
df_singersPlayed = df.select('userId', 'month_num', 'page', 	'artist') \
	.where(df.page=="NextSong") \
	.groupBy('userId', 'month_num') \
	.agg(countDistinct(df.artist).alias('numSingersPlayed'))

# number of singers per session
df_singersPerSession = df.select('userId', 'month_num', 'page', 	'artist', 'sessionId') \
	.where(df.page=="NextSong") \
	.groupBy('userId', 'month_num', 'sessionId') \
	.agg(countDistinct(df.artist).alias('SingersPerSession'))
# average number of singers per session
df_avgSingersPerSession = df_singersPerSession.groupBy('userId', 	'month_num') \
	.agg(avg(df_singersPerSession.SingersPerSession).alias	('avgSingersPerSession'))

# amount of time spent for each session
df_userSession = df.groupBy("userId", "month_num", "sessionId") \
	.agg(((max(unix_timestamp(df.datetime))-min(unix_timestamp	(df.datetime)))/60.0).alias('sessionTimeMins'))
# average time per session
df_avgUserSession = df_userSession.groupBy('userId', 	'month_num').agg(avg(df_userSession.sessionTimeMins).alias	('avgSessionMins'))

# number of sessions per month
df_numSession = df.select('userId', 'month_num', 	'sessionId').dropDuplicates() \
	.groupby('userId', 'month_num').agg({'sessionId':'count'}) \
	.withColumnRenamed('count(sessionId)', 'numSessions')
# if user had premium level this month
# if user had premium at any point of the month, assumer he/she has # premium for the whole month for simplicity
df_level = df.select('userId', 'month_num', 'level') \
	.groupBy('userId', 'month_num') \
	.agg({'level':'max'}) \
	.withColumnRenamed('max(level)', 'level')
# find user's gender
# assuming nobody changes gender midway
df_gender = df.select('userId', 'month_num', 'gender') \
	.groupBy('userId', 'month_num') \
	.agg({'gender':'max'}) \
	.withColumnRenamed('max(gender)', 'gender')

# start of each month
df = df.withColumn("start_of_month", expr("add_months	(analysis_start_date, month_num)"))
# days since registration to start of each month
df = df.withColumn("daysSinceReg", datediff(df.start_of_month, 	df.reg_datetime))
df_daysReg = df.select('userId', 'month_num', 'daysSinceReg') \
	.groupBy('userId', 'month_num') \
	.agg(min(df.daysSinceReg).alias('daysSinceReg'))
# did user churn this month
df_churn = df.select('userId', 'month_num', 'churn') \
	.groupBy('userId', 'month_num') \
	.agg({'churn':'max'}) \
	.withColumnRenamed('max(churn)', 'churn')

Объединение этой ежемесячной статистики в новый фрейм данных:

all_data = df_register.join(df_cancel, ['userId', 'month_num'], 	'outer') \
	.join(df_upgrade, ['userId', 'month_num'], 'outer') \
	.join(df_downgrade, ['userId', 'month_num'], 'outer') \
	.join(df_home, ['userId', 'month_num'], 'outer') \
	.join(df_about, ['userId', 'month_num'], 'outer') \
	.join(df_settings, ['userId', 'month_num'], 'outer') \
	.join(df_saveSettings, ['userId', 'month_num'], 'outer') \
	.join(df_login, ['userId', 'month_num'], 'outer') \
	.join(df_logout, ['userId', 'month_num'], 'outer') \
	.join(df_addPlaylist, ['userId', 'month_num'], 'outer') \
	.join(df_addFriend, ['userId', 'month_num'], 'outer') \
	.join(df_thumbsUp, ['userId', 'month_num'], 'outer') \
	.join(df_thumbsDown, ['userId', 'month_num'], 'outer') \
	.join(df_advert, ['userId', 'month_num'], 'outer') \
	.join(df_songsPlayed, ['userId', 'month_num'], 'outer') \
	.join(df_totalListen, ['userId', 'month_num'], 'outer') \
	.join(df_avgSongsPerSession, ['userId', 'month_num'], 	'outer') \
	.join(df_singersPlayed, ['userId', 'month_num']) \
	.join(df_avgSingersPerSession, ['userId', 'month_num'], 	'outer') \
	.join(df_avgUserSession, ['userId', 'month_num'], 'outer') \
	.join(df_numSession, ['userId', 'month_num'], 'outer') \
	.join(df_level, ['userId', 'month_num'], 'outer') \
	.join(df_gender, ['userId', 'month_num'], 'outer') \
	.join(df_daysReg, ['userId', 'month_num'], 'outer') \
	.join(df_churn, ['userId', 'month_num'], 'outer')

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

windowlag = (Window.partitionBy('userId').orderBy('month_num'))

# generate 1 month lag features
all_data = all_data.withColumn('numRegister_lastMonth', lag(all_data			['numRegister']).over(windowlag))
all_data = all_data.withColumn('numCancelVisits_lastMonth', lag	(all_data['numCancelVisits']).over(windowlag))
all_data = all_data.withColumn('numUpgradeVisits_lastMonth', lag	(all_data['numUpgradeVisits']).over(windowlag))
all_data = all_data.withColumn('numDowngradeVisits_lastMonth', lag	(all_data['numDowngradeVisits']).over(windowlag))
all_data = all_data.withColumn('numHomeVisits_lastMonth', lag	(all_data['numHomeVisits']).over(windowlag))
all_data = all_data.withColumn('numAboutVisits_lastMonth', lag	(all_data['numAboutVisits']).over(windowlag))
all_data = all_data.withColumn('numSettingsVisits_lastMonth', lag	(all_data['numSettingsVisits']).over(windowlag))
all_data = all_data.withColumn('numSaveSettings_lastMonth', lag	(all_data['numSaveSettings']).over(windowlag))
all_data = all_data.withColumn('numLogins_lastMonth', lag(all_data	['numLogins']).over(windowlag))
all_data = all_data.withColumn('numLogouts_lastMonth', lag(all_data	['numLogouts']).over(windowlag))
all_data = all_data.withColumn('numAddPlaylists_lastMonth', lag	(all_data['numAddPlaylists']).over(windowlag))
all_data = all_data.withColumn('numFriends_lastMonth', lag(all_data	['numFriends']).over(windowlag))
all_data = all_data.withColumn('numThumbsUp_lastMonth', lag(all_data	['numThumbsUp']).over(windowlag))
all_data = all_data.withColumn('numThumbsDown_lastMonth', lag	(all_data['numThumbsDown']).over(windowlag))
all_data = all_data.withColumn('numAdverts_lastMonth', lag(all_data	['numAdverts']).over(windowlag))
all_data = all_data.withColumn('numSongsPlayed_lastMonth', lag	(all_data['numSongsPlayed']).over(windowlag))
all_data = all_data.withColumn('totalListenTime_lastMonth', lag	(all_data['totalListenTime']).over(windowlag))
all_data = all_data.withColumn('avgSongsPerSession_lastMonth', lag	(all_data['avgSongsPerSession']).over(windowlag))
all_data = all_data.withColumn('numSingersPlayed_lastMonth', lag	(all_data['numSingersPlayed']).over(windowlag))
all_data = all_data.withColumn('avgSingersPerSession_lastMonth', lag	(all_data['avgSingersPerSession']).over(windowlag))
all_data = all_data.withColumn('avgSessionMins_lastMonth', lag	(all_data['avgSessionMins']).over(windowlag))
all_data = all_data.withColumn('numSessions_lastMonth', lag(all_data	['numSessions']).over(windowlag))
all_data = all_data.withColumn('level_lastMonth', lag(all_data	['level']).over(windowlag))

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

  1. numRegister_lastMonth: сколько раз пользователь регистрировался в прошлом месяце
  2. numCancelVisits_lastMonth: количество посещений пользователем страницы отмены в прошлом месяце.
  3. numUpgradeVisits_lastMonth: сколько раз пользователь посещал страницу обновления в прошлом месяце
  4. numDowngradeVisits_lastMonth: количество посещений пользователем страницы перехода на более раннюю версию в прошлом месяце.
  5. numHomeVisits_lastMonth: количество раз, когда пользователь посещал домашнюю страницу в прошлом месяце.
  6. numAboutVisits_lastMonth: сколько раз пользователь заходил на определенную страницу за последний месяц.
  7. numSettingsVisits_lastMonth: сколько раз пользователь посещал страницу настроек в прошлом месяце.
  8. numSaveSettings_lastMonth: Сколько раз пользователь сохранял изменения в настройках за последний месяц
  9. numLogins_lastMonth: количество раз, когда пользователь входил в систему в прошлом месяце
  10. numLogouts_lastMonth: количество выходов пользователя из системы за последний месяц
  11. numAddPlaylists_lastMonth: количество песен, добавленных пользователем в плейлист в прошлом месяце.
  12. numFriends_lastMonth: количество друзей, добавленных пользователем за последний месяц
  13. numThumbsUp_lastMonth: количество отметок "Нравится", поставленных пользователем в прошлом месяце.
  14. numThumbsDown_lastMonth: Количество отметок "Не нравится", поставленных пользователем в прошлом месяце
  15. numAdverts_lastMonth: количество рекламных объявлений, показанных пользователю в прошлом месяце.
  16. numSongsPlayed_lastMonth: количество композиций, которые пользователь сыграл в прошлом месяце.
  17. totalListenTime_lastMonth: Общее время прослушивания для пользователя в прошлом месяце
  18. avgSongsPerSession_lastMonth: среднее количество композиций, проигранных пользователем за сеанс в прошлом месяце.
  19. numSongsPlayed_lastMonth: количество песен, сыгранных пользователем в прошлом месяце.
  20. avgSingersPerSession_lastMonth: Среднее количество исполнителей, сыгранных пользователем за сеанс в прошлом месяце.
  21. avgSessionMins_lastMonth: Среднее количество минут на сеанс пользователем в прошлом месяце.
  22. numSessions_lastMonth: количество сеансов, проведенных пользователем в прошлом месяце
  23. daysSinceReg: количество дней с момента регистрации до первого дня текущего месяца для каждого пользователя.
  24. level_lastMonth: отслеживает, был ли пользователь платным или бесплатным пользователем в прошлом месяце. Если пользователь был платным пользователем в какой-либо период времени в прошлом месяце, мы будем предполагать, что он / она был платным пользователем в течение всего месяца для простоты.

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

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

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

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

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

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

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

Кроме того, отметим, что количество регистраций, количество посещений страницы отмены и количество входов в систему отсутствовали.

Когда пользователь регистрируется или входит в систему, система не знает его идентификатор. Следовательно, информация об этой деятельности для клиентов не регистрировалась.

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

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

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

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

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

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

Моделирование

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

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

# convert userId, gender, level, level_lastMonth to numeric
convert_numeric = ['userId', 'level', 'gender', 'level_lastMonth']
for feat in convert_numeric:
    featName = feat + "_n"
    all_data = all_data.withColumn(featName, all_data[feat].cast	("float"))
    all_data = all_data.drop(feat)

Вы можете проверить типы данных функций, запустив all_data.persist(). Кроме того, убедитесь, что в ваших функциях ввода нет значений NULL или NaN.

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

# first month is month 0
model_data = all_data \
	.filter(all_data.month_num>0) \ 
	.select('userId_n', 'month_num',\         		'numUpgradeVisits_lastMonth',\
		'numDowngradeVisits_lastMonth',\
		'numHomeVisits_lastMonth',\
		'numAboutVisits_lastMonth',\
		'numSettingsVisits_lastMonth',\
		'numSaveSettings_lastMonth',\
		'numLogouts_lastMonth',\
		'numAddPlaylists_lastMonth',\
		'numFriends_lastMonth',\
		'numThumbsUp_lastMonth',\
		'numThumbsDown_lastMonth',\
		'numAdverts_lastMonth',\
		'numSongsPlayed_lastMonth',\
		'totalListenTime_lastMonth',\
		'avgSongsPerSession_lastMonth',\
		'numSingersPlayed_lastMonth',\
		'avgSingersPerSession_lastMonth',\
		'avgSessionMins_lastMonth',\
		'numSessions_lastMonth',\
		'level_lastMonth_n',\
		'gender_n', 'daysSinceReg', 'churn'         		).withColumnRenamed('churn', 'label')

Мы будем хранить входные характеристики и метки во фрейме данных model_data.

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

train,test = model_data.randomSplit([0.8, 0.2], seed=50)

Обратите внимание, что все входные объекты для точки данных должны быть помещены в один вектор с именем features. Рекомендуется также масштабировать входные объекты. Столбец churn следует переименовать в label. Библиотека Spark ML будет искать векторы features и label, поэтому используйте это соглашение об именах.

inputColumns = ['userId_n', 'month_num',\                 		'numUpgradeVisits_lastMonth',\
		'numDowngradeVisits_lastMonth',\                 		'numHomeVisits_lastMonth',\
		'numAboutVisits_lastMonth',\                 		'numSettingsVisits_lastMonth',\
		'numSaveSettings_lastMonth',\                 		'numLogouts_lastMonth',\
                'numAddPlaylists_lastMonth',\
		'numFriends_lastMonth',\                 		'numThumbsUp_lastMonth',\
		'numThumbsDown_lastMonth',\                 		'numAdverts_lastMonth',\
		'numSongsPlayed_lastMonth',\
                'totalListenTime_lastMonth',\
		'avgSongsPerSession_lastMonth',\
                'numSingersPlayed_lastMonth',\
		'avgSingersPerSession_lastMonth',\                 		'avgSessionMins_lastMonth',\
 		'numSessions_lastMonth',\
                'level_lastMonth_n', 'gender_n',\
		'daysSinceReg']
assembler = VectorAssembler(inputCols=inputColumns,\
			    outputCol="FeaturesVec")  
scaler = StandardScaler(inputCol="FeaturesVec",\
			outputCol="features",\
 			withMean=True, withStd=True)

Мы будем пробовать 3 типа моделей: логистическую регрессию, линейный классификатор опорных векторов и деревья с градиентным усилением (GBT).

# set max_iter to 10 to reduce computation time  
# Logistic Regression 
lr=LogisticRegression(maxIter=10)
pipeline_lr = Pipeline(stages=[assembler, scaler, lr])  
# Support Vector Machine Classifier 
svc = LinearSVC(maxIter=10) 
pipeline_svc = Pipeline(stages=[assembler, scaler, svc])  
# Gradient Boosted Trees 
gbt = GBTClassifier(maxIter=10, seed=42) 
pipeline_gbt = Pipeline(stages=[assembler, scaler, gbt])

Будут сообщены точность теста, оценка f1, точность и отзывчивость. F1-оценка будет предпочтительнее, так как метки несбалансированы.

куда:

  1. TN (True Negatives): клиенты, которые, по нашим прогнозам, не уйдут, и которые в действительности не ушли.
  2. FP (ложные срабатывания): клиенты предсказывали отток, но не отказались от него в действительности.
  3. FN (ложно-отрицательные): клиенты, которых мы прогнозируем, не будут уходить, а в действительности уйдут.
  4. TP (True Positives): клиенты, согласно прогнозам, в действительности уйдут и уйдут.

Точность говорит нам, насколько точны наши прогнозы (из которых прогнозируется отток, сколько оттока), а отзыв показывает, насколько хорошо наша модель отозван (сколько из первоначальных отказавшихся клиентов удалось найти модели).

Показатель f1 можно рассматривать как средневзвешенное значение точности и запоминания.

Поскольку количество клиентов, которые ушли, меньше, чем количество клиентов, которые не ушли, f1-score даст более точное представление о производительности модели, а не о точности.

Предупреждение при использовании PySpark MulticlassClassificationEvaluator для сообщения оценки f1 для задачи бинарной классификации заключается в том, что встроенная функция будет обрабатывать обе метки 0 и 1 как отдельные классы и возвращать взвешенные значения f1 для обоих классов. Это приведет к чрезмерно оптимистичной оценке, которая может быть обманчива. Следовательно, я решил определить свою собственную функцию для расчета показателя f1. Более подробную информацию можно найти в Кодексе.

Полученные результаты

Мы будем выполнять k-кратную перекрестную проверку с 5-кратной проверкой, и модели будут оптимизированы с использованием площади под кривой PR.

Использование площади под кривой PR предпочтительнее, чем использование площади под кривой ROC, поскольку дисбаланс классов в наборе данных означает, что использование площади под кривой ROC приведет к излишне оптимистичной картине.

Следующие результаты получены при обучении нашей модели на полном наборе данных.

Логистическая регрессия

paramGrid = ParamGridBuilder() \
	    .addGrid(lr.regParam,[0.0, 0.05, 0.1]) \
	    .build()  
cv_lr = CrossValidator(estimator=pipeline_lr,\
                       estimatorParamMaps=paramGrid,\
                       evaluator=\
		       BinaryClassificationEvaluator(metricName=\
						    "areaUnderPR"),\
                       numFolds=5, seed=42) 
cvModel_lr = cv_lr.fit(train)
lr_results = cvModel_lr.transform(test)
evaluate_model(lr_results)

Точность: 0,790473641959

F1-оценка: 0,387802971071

Точность: 0,666666666667

Напомним: 0,273428886439

Матрица неточностей:

TN:2706.0 | FP:124.0 
FN:659.0  | TP: 248.0

Классификатор линейных опорных векторов

paramGrid = ParamGridBuilder() \
	    .addGrid(svc.regParam,[0.0, 0.05, 0.1]) \
	    .build()
cv_svc = CrossValidator(estimator=pipeline_svc,\
                        estimatorParamMaps=paramGrid,\
                        evaluator=\
		        BinaryClassificationEvaluator(metricName=\
						    "areaUnderPR"),\
                        numFolds=5, seed=42)
cvModel_svc = cv_svc.fit(train)
svc_results = cvModel_svc.transform(test)
evaluate_model(svc_results)

Точность: 0,773882793685

F1-Score: 0,189837008629

Точность: 0,727941176471

Напомним: 0.109151047409

Матрица неточностей:

TN:2793.0 | FP:37.0 
FN:808.0  | TP: 99.0

Деревья с градиентным усилением

paramGrid = ParamGridBuilder() \             	  	 	.addGrid(gbt.minInstancesPerNode,[5]) \             	.addGrid(gbt.maxDepth,[7])\             	   	.addGrid(gbt.subsamplingRate,[0.75])\
        .build()
cv_gbt = CrossValidator(estimator=pipeline_gbt,\
                        estimatorParamMaps=paramGrid,\
                        evaluator=\
		        BinaryClassificationEvaluator(metricName=\
						    "areaUnderPR"),\
                        numFolds=5, seed=42)
cvModel_gbt = cv_gbt.fit(train)
gbt_results = cvModel_gbt.transform(test)
evaluate_model(gbt_results)

Точность: 0,776826331282

F1-Score: 0,409348441926

Точность: 0,572277227723

Напомним: 0,318632855568

Матрица неточностей:

TN:2614.0 | FP:216.0 
FN:618.0  | TP: 289.0

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

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

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

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

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

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

Заключение

В этой статье мы реализовали единую модель для прогнозирования оттока клиентов (переход с премиум-уровня на бесплатный или закрытие своих учетных записей). С этой единственной моделью нам удалось достичь показателя f1 примерно 0,4, что является приличным показателем.

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

Код, сопровождающий эту статью, можно найти здесь.

Спасибо, что прочитали эту статью! Если у вас есть какие-либо мысли или отзывы, оставьте комментарий ниже или отправьте мне письмо по адресу [email protected]. Буду рад услышать от вас!