Обзор проекта

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

Есть 3 вида предложений: купи-один-получи-один (BOGO), дисконтное и информационное. В предложении BOGO пользователь должен потратить определенную сумму, чтобы получить вознаграждение, равное этой пороговой сумме. При скидке пользователь получает вознаграждение, равное части потраченной суммы. В информационном предложении нет вознаграждения, но нет и необходимой суммы, которую пользователь должен потратить. Чтобы сделать эффективное предложение, которое может быть выполнено пользователем, нам нужно определить, какие демографические группы лучше всего реагируют на тот или иной тип предложения.

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

Связанный репозиторий кода можно найти здесь.

Постановка задачи

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

Вопросы, на которые нужно ответить:

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

Метрики

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

Исследование данных

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

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

Есть 3 разных набора данных:

profile.json: пользователи программы вознаграждений (17 000 пользователей x 5 полей).

  • пол: (категориальный) M, F, O или null
  • возраст: (числовое) отсутствующее значение, закодированное как 118
  • идентификатор: (строка/хэш)
  • стал_член_на: (дата) формат ГГГГММДД
  • доход: (числовой)

portfolio.json:предложения, отправленные в течение 30-дневного тестового периода (10 предложений x 6 полей).

  • награда: (числовое) деньги, присуждаемые за потраченную сумму
  • каналы: (список) Интернет, электронная почта, мобильные устройства, социальные сети
  • сложность: (число) деньги, которые необходимо потратить, чтобы получить награду
  • продолжительность: (числовое) время, в течение которого предложение будет открыто, в днях
  • offer_type: (string) бого, скидка, информационный
  • идентификатор: (строка/хеш)

transcript.json:журнал событий (306648 событий x 4 поля)

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

Визуализация

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

Кроме того, хорошие распределения для полей age и income в наборе данных profile:

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

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

портфолио:

Для набора данных портфолио выполняются следующие операции:
- каналы информация извлекается в отдельные столбцы
- продолжительность преобразуется в часы, так как у нас есть единица часы в transcript
- id переименован в offer_id

профиль:

Для набора данных профиля выполняются следующие операции:
 — строки, имеющие нулевые значения, удаляются;
 — из became_member_on, became_member_diffсоздается для отображения времени, прошедшего в днях до '2018-08-01', а исходный столбец удаляется
(эта дата является округленным значением максимального значения этого столбца. Когда это сгенерированный набор данных неизвестен, поэтому прошедшее время рассчитывается до этой даты)
- id переименовывается в customer_id

стенограмма:

Следующие операции выполняются для набора данных транскриптов:
- offer_idиamountизвлекаются в отдельные столбцы, поскольку эта информация хранится в столбце значение вместе
- человек переименовывается в customer_id
-
Строки с идентификаторами клиентов, которые не существуют в наборе данных profile, удаляются
- Дублирующиеся строки удаляются

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

Действия пользователя хранятся в наборе данных transcript. Существует 4 типа событий, таких как offer received, offer viewed, offer completed и transaction.

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

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

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

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

customer_profile:введитенабор данных в модель машинного обучения.

  • завершено: (бинарно) показывает, завершено ли предложение пользователем или нет
  • сложность, продолжительность, offer_id, вознаграждение, тип, социальные сети, электронная почта, мобильные устройства, Интернет: эти поля взяты из портфолио.
  • start_time(int): время начала предложения
  • transaction_amount(float): общая сумма транзакции во время действительного предложения
  • возраст, пол, доход, стал_член_diff: эти поля берутся из профиля

Выполнение

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

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

Результаты этих алгоритмов:

---------------------------------
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=None, n_neighbors=5, p=2,
           weights='uniform')
-----------------------------------
test_score  =>  0.7899556148945379
train_score  =>  0.8538837646442865
---------------------------------
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=0,
            splitter='best')
-----------------------------------
test_score  =>  0.8259986820812248
train_score  =>  1.0
---------------------------------
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
            max_depth=None, max_features='auto', max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, n_estimators=100, n_jobs=None,
            oob_score=False, random_state=0, verbose=0, warm_start=False)
-----------------------------------
test_score  =>  0.8815545203836506
train_score  =>  0.9999785110548439
---------------------------------
GradientBoostingClassifier(criterion='friedman_mse', init=None,
              learning_rate=0.1, loss='deviance', max_depth=3,
              max_features=None, max_leaf_nodes=None,
              min_impurity_decrease=0.0, min_impurity_split=None,
              min_samples_leaf=1, min_samples_split=2,
              min_weight_fraction_leaf=0.0, n_estimators=100,
              n_iter_no_change=None, presort='auto', random_state=0,
              subsample=1.0, tol=0.0001, validation_fraction=0.1,
              verbose=0, warm_start=False)
-----------------------------------
test_score  =>  0.8835026243379656
train_score  =>  0.8879792101919672
---------------------------------
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=1000, multi_class='auto',
          n_jobs=None, penalty='l2', random_state=0, solver='lbfgs',
          tol=0.0001, verbose=0, warm_start=False)
-----------------------------------
test_score  =>  0.832588829210796
train_score  =>  0.8331543129204709
---------------------------------
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='scale', kernel='rbf',
  max_iter=-1, probability=False, random_state=0, shrinking=True,
  tol=0.001, verbose=False)
-----------------------------------
test_score  =>  0.8483186224759569
train_score  =>  0.8515701377958644

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

Уточнение

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

Результаты

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

Мы изначально задали 3 вопроса, и на первый из них ответила приведенная выше реализация. Смотрим на левых.

Вопрос 2.Какие поля обеспечивают наибольшую предсказательную силу?

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

Вопрос 3. Какой пол получает больше всего предложений?

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

Обоснование

На этапах предварительной обработки данных мы сделали 2 основных предположения.

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

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

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

Отражение

Мы успешно построили модель, чтобы предсказать, эффективно ли предложение для конкретного клиента или нет, с показателем точности теста 88%. Используя модель, мы можем решить, какое предложение отправить клиенту.
И мы ответили на все наши первоначальные вопросы.

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

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

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

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

Я надеюсь, что вы найдете этот пост полезным, спасибо за чтение!