Исследуйте свои большие данные с помощью Spark, подход для начинающих.

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

Введение

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

Деловое понимание

Sparkify - это служба потоковой передачи вымышленной музыки, такая как Spotify или Pandora, и я буду использовать Sparkify Churn Prediction как формулировку проблемы, чтобы подробно объяснить эту статью. Я буду использовать PySpark для этого проекта.

Примечание. Набор данных Sparkify был предоставлен программой Udacity Data Scientist Nanodegree, и эта статья является частью моего проекта Capstone.

Понимание данных

Я начал с исследовательского анализа данных на моем локальном компьютере с небольшого набора данных Sparkify, предоставленного Udacity.

# loading dataset
df = spark.read.json("mini_sparkify_event_data.json")
# columns in dataset
df.printSchema()
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)
# Number of Rows
df.count()
286500
# showing types of events in dataset or pages a user might visit while using the Sparkify app.
df.select("page").dropDuplicates().show()
+--------------------+
|                page|
+--------------------+
|              Cancel|
|    Submit Downgrade|
|         Thumbs Down|
|                Home|
|           Downgrade|
|         Roll Advert|
|              Logout|
|       Save Settings|
|Cancellation Conf...|
|               About|
|            Settings|
|     Add to Playlist|
|          Add Friend|
|            NextSong|
|           Thumbs Up|
|                Help|
|             Upgrade|
|               Error|
|      Submit Upgrade|
+--------------------+

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

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

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

Добавляем в качестве целевой переменной Churn. Если пользователь посетил страницу «Подтверждение отмены», то столбец Отток имеет значение 1, иначе 0 .

Визуализации данных

Давайте глубже разберемся с клиентами Sparkify с помощью визуализаций.

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

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

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

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

['gender', 'level', 'page', 'userId', 'Churn']

Столбец страница в дальнейшем будет разделен на свои события.

df.groupBy(["userId", "Churn", "gender", "level"]).count().show()
+------+-----+------+-----+-----+
|userId|Churn|gender|level|count|
+------+-----+------+-----+-----+
|200008|    0|     1|    1| 1988|
|    93|    0|     0|    0|  566|
|100013|    1|     1|    0|  303|
|    91|    0|     0|    1| 2862|
|     4|    0|     0|    0|  141|
|300021|    0|     1|    1| 4650|
|100019|    1|     0|    0|   89|
|100021|    1|     0|    0|  319|
|   140|    0|     1|    1| 5662|
|    73|    1|     1|    0|   16|
|    59|    0|     0|    1|  693|
|200025|    0|     0|    0|  597|
|    41|    0|     1|    1| 2220|
|    37|    0|     0|    0|   79|
|    54|    1|     1|    1| 2859|
|    69|    0|     1|    0|   39|
|200024|    1|     0|    0|  342|
|300005|    0|     1|    0|   64|
|    27|    0|     0|    0|  291|
|   101|    1|     0|    0|   76|
+------+-----+------+-----+-----+
only showing top 20 rows
Event_list = [(row['page']) for row in df.select('page').dropDuplicates().collect()]
for event in Event_list:
    df_temp = df.filter(df.page==event).groupBy(df.userId).count()
    df_temp = df_temp.withColumnRenamed('count', event)
    df_sparkify = df_sparkify.join(df_temp, 'userId', how='left')

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

df_sparkify.printSchema()
root
 |-- userId: integer (nullable = true)
 |-- Churn: integer (nullable = true)
 |-- gender: integer (nullable = true)
 |-- level: integer (nullable = true)
 |-- count: long (nullable = false)
 |-- Cancel: long (nullable = true)
 |-- Submit Downgrade: long (nullable = true)
 |-- Thumbs Down: long (nullable = true)
 |-- Home: long (nullable = true)
 |-- Downgrade: long (nullable = true)
 |-- Roll Advert: long (nullable = true)
 |-- Logout: long (nullable = true)
 |-- Save Settings: long (nullable = true)
 |-- Cancellation Confirmation: long (nullable = true)
 |-- About: long (nullable = true)
 |-- Settings: long (nullable = true)
 |-- Add to Playlist: long (nullable = true)
 |-- Add Friend: long (nullable = true)
 |-- NextSong: long (nullable = true)
 |-- Thumbs Up: long (nullable = true)
 |-- Help: long (nullable = true)
 |-- Upgrade: long (nullable = true)
 |-- Error: long (nullable = true)
 |-- Submit Upgrade: long (nullable = true)

Примечание. Dataframe может отличаться от полного набора данных, работающего в AWS Spark.

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

Примечание. Удаление столбцов Отмена и Подтверждение отмены для предотвращения утечки данных во время моделирования.

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

Библиотека AWS Spark MLlib поддерживает множество контролируемых и неконтролируемых алгоритмов машинного обучения. Я использовал классификатор дерева решений, классификатор случайного леса и классификатор повышения градиента для обучения трех моделей и сравнения результатов перед выбором модели-победителя.

Разделение на тренировку и тест

# spliting in 70% training and 30% testing set
train_data,test_data = final_data.randomSplit([0.7,0.3])

Классификаторы

# Using three classifiers before finalizing best performer
dtc=DecisionTreeClassifier(labelCol='Churn',featuresCol='features')
rfc=RandomForestClassifier(labelCol='Churn',featuresCol='features', numTrees=300)
gbt=GBTClassifier(labelCol='Churn',featuresCol='features')
# fit the models
dtc_model = dtc.fit(train_data)
rfc_model = rfc.fit(train_data)
gbt_model = gbt.fit(train_data)
# transform the models
dtc_predictions = dtc_model.transform(test_data)
rfc_predictions = rfc_model.transform(test_data)
gbt_predictions = gbt_model.transform(test_data)

Метрика оценки

В pySpark мы не можем распечатать матрицу путаницы, вместо этого мы должны использовать MulticlassClassificationEvaluator для оценки точности и f1_score. Мы также можем использовать BinaryClassificationEvaluator для вычисления площади под кривой ROC и площади под точным вызовом.

# calculating accuracy
acc_evaluator = MulticlassClassificationEvaluator(labelCol="Churn",predictionCol="prediction",metricName="accuracy")
# calculating f1 score
f1_evaluator = MulticlassClassificationEvaluator(labelCol="Churn",predictionCol="prediction", metricName="f1")
areaUnderROC = BinaryClassificationEvaluator(labelCol='Churn',metricName='areaUnderROC')
areaUnderPR = BinaryClassificationEvaluator(labelCol='Churn',metricName='areaUnderPR')
dtc_acc = acc_evaluator.evaluate(dtc_predictions)
rfc_acc = acc_evaluator.evaluate(rfc_predictions)
gbt_acc = acc_evaluator.evaluate(gbt_predictions)
dtc_f1 = f1_evaluator.evaluate(dtc_predictions)
rfc_f1 = f1_evaluator.evaluate(rfc_predictions)
gbt_f1 = f1_evaluator.evaluate(gbt_predictions)
dtc_auc = areaUnderROC.evaluate(dtc_predictions)
rfc_auc = areaUnderROC.evaluate(rfc_predictions)
gbt_auc = areaUnderROC.evaluate(gbt_predictions)
dtc_aupr = areaUnderPR.evaluate(dtc_predictions)
rfc_aupr = areaUnderPR.evaluate(rfc_predictions)
gbt_aupr = areaUnderPR.evaluate(gbt_predictions)

Результаты

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

После сравнения всех моделей по разным оценочным показателям Gradient Boosting работает лучше, чем два других. Вы можете видеть, что показатель f1 для алгоритма повышения градиента составляет 0,90, а показатель f1 случайного леса равен 0,79.

Я основываюсь на модели с более высокой оценкой запоминаемости для положительного оттока. В терминах Lehman выбор модели, которая может прогнозировать меньше ложных отрицательных результатов (случаи, когда класс равен 1, но прогнозируется как 0), а не ложных срабатываний (случаи, когда класс равен 0, но прогнозируется как 1), для успешного выявления оттока клиентов.

На данный момент я выберу Gradient Boosting Classifier в качестве своей последней модели.

Развертывать

Я буду запускать этот код на AWS Spark Cluster в качестве своей будущей работы. Но я сейчас нигде не развертываю эту модель.

Будущие работы

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

P.S. Я всегда стараюсь следовать подходу CRISP-DM в своих проектах Data Science.

Эта статья является частью проекта Udacity Data Scientist Nanodegree. Чтобы узнать больше об этом анализе, перейдите по ссылке на мой Github, доступной здесь.