Введение в мощный способ прогнозирования индивидуальных эффектов лечения с использованием синтетических данных на Python с использованием pandas и XGBoost.

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

Этот пост доступен как блокнот Jupyter здесь, на Github.

Вступление

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

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

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

Данные для моделирования подъемов: эксперименты - ключ к успеху

Теперь, когда мы знаем цель моделирования подъемов, как нам ее достичь? Типичной отправной точкой для построения модели улучшения является набор данных из рандомизированного контролируемого эксперимента: нам нужна репрезентативная выборка всех типов клиентов как в группе лечения, так и в контрольной группе, которая не получала лечения. Если доля клиентов, совершающих покупки, значительно выше в исследуемой группе, чем в контрольной группе, мы знаем, что рекламная акция «работает» в том смысле, что поощряет покупку в среднем для всех клиентов. Это называется средним лечебным эффектом (ATE). Количественная оценка ATE - типичный результат A / B-теста.

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

Механика модели

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

  • Объединение групп лечения и контроля в один набор данных и обучение единой модели, в которой лечение является бинарной функцией. На этапе вывода модель используется, чтобы сделать два прогноза для каждого случая, первый с лечением = 1, а второй с лечением = 0. Это называется подходом «S-Learner», поскольку он использует единую модель.
  • Тренировка отдельных моделей для экспериментальной и контрольной групп. На этапе вывода модели лечения и контроля используются для получения прогнозов для каждого случая. Это называется подходом «T-Learner», поскольку он использует две модели.

Эти два подхода представлены на следующей схеме:

Эти подходы широко описаны в литературе по моделированию подъемов и причинно-следственным выводам (Lee et al. 2013, Gutierrez and Gerardy 2016). Их преимущество в том, что они относительно просты и интуитивно понятны, и могут быть реализованы с использованием методов моделирования двоичной классификации, с которыми знакомы многие специалисты по данным, а также специализированных пакетов корпоративного программного обеспечения, такого как SAS (Lee et al. 2013). В то же время причинно-следственный вывод является активной областью исследований в рамках машинного обучения, и другие подходы могут обеспечить лучшую производительность модели. Различные подходы включают древовидные модели, предназначенные для целевого повышения (рассмотрено в Gutierrez and Gerardy, 2016), преобразование целевой переменной (Yi and Frost, 2018) и другие более свежие инновации, такие как X-Learner (Kunzel et al., 2019).

Во всех вариантах моделирование подъема сталкивается с фундаментальной проблемой. Цель состоит в том, чтобы спрогнозировать для отдельного клиента вероятность его покупки, если его лечили, а также вероятность, если не лечить, чтобы рассчитать прирост. Но на самом деле мы никогда не наблюдаем результата для того, кого лечили и не лечили, потому что это невозможно! Кто-то либо лечится, либо нет. В математическом моделировании обычно возникает проблема, если мы не можем наблюдать все результаты, которые нас интересуют. Эта проблема иллюстрирует контрфактический характер моделирования роста и важность рандомизированных экспериментов для понимания CATE для всех типов клиентов.

Гутьеррес и Жерарди (2016) резюмируют эту проблему и указывают путь вперед:

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

Давайте рассмотрим эти концепции на примере набора данных, построив модель S-Learner и оценив ее.

# load packages
import numpy as np
import pandas as pd

from statsmodels.stats.proportion import proportions_ztest

import sklearn as sk
from sklearn.metrics import auc
import xgboost as xgb

import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline

import pickle

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

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

Здесь мы используем синтетические данные из недавней публикации (Zhao et al. 2020), которые находятся в открытом доступе здесь. Эти данные моделируют спланированный эксперимент с равным разделением между экспериментальной и контрольной группами. Мы загружаем только первые 10 000 строк из этого набора данных, который является первым из 100 испытаний (повторений с разными случайными начальными числами). Набор данных построен таким образом, что некоторые характеристики позволяют прогнозировать результат, некоторые - неинформативны, а некоторые - конкретно прогнозировать эффект лечения.

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

Давайте загрузим данные и кратко рассмотрим их.

df = pd.read_csv('data/uplift_synthetic_data_100trials.csv', nrows=10000)
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 43 columns):
 #   Column                      Non-Null Count  Dtype  
---  ------                      --------------  -----  
 0   Unnamed: 0                  10000 non-null  int64  
 1   trial_id                    10000 non-null  int64  
 2   treatment_group_key         10000 non-null  object 
 3   conversion                  10000 non-null  int64  
 4   control_conversion_prob     10000 non-null  float64
 5   treatment1_conversion_prob  10000 non-null  float64
 6   treatment1_true_effect      10000 non-null  float64
 7   x1_informative              10000 non-null  float64
 8   x2_informative              10000 non-null  float64
 9   x3_informative              10000 non-null  float64
 ...
 42  x36_uplift_increase         10000 non-null  float64
dtypes: float64(39), int64(3), object(1)
memory usage: 3.3+ MB
df.head()

Сколько из этих 10 000 записей относится к экспериментальной группе, а сколько - к контрольной?

df['treatment_group_key'].value_counts()
control       5000
treatment1    5000
Name: treatment_group_key, dtype: int64

Есть разделение 50/50. Давайте закодируем переменную обработки как двоичный 0/1:

df['treatment_group_key'] = df['treatment_group_key'].map(
    arg={'control':0, 'treatment1':1})

Анализируйте результаты экспериментов

Каков был общий коэффициент конверсии?

df['conversion'].mean()
0.3191

Каков коэффициент конверсии в экспериментальной группе по сравнению с контрольной группой?

exp_results_df = \
df.groupby('treatment_group_key').agg({'conversion':['mean', 'sum',
    'count']})
exp_results_df

(exp_results_df.loc[1,('conversion', 'mean')] \
    - exp_results_df.loc[0,('conversion', 'mean')]).round(4)
0.1042

Коэффициент конверсии в экспериментальной группе (37%) значительно выше, чем в контрольной группе (27%), что указывает на то, что лечение эффективно для стимулирования конверсии: ATE положительный и составляет около 10%.

Часто в реальных данных разница не так велика, и для определения результата A / B-теста обычно проводится тест значимости.

proportions_ztest(count=exp_results_df[('conversion', 'sum')],
                  nobs=exp_results_df[('conversion', 'count')])
(-11.177190529878043, 5.273302441543889e-29)

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

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

Постройте модель подъема

Здесь я буду использовать XGBClassifier для обучения S-Learner; то есть единая модель, включающая все функции, где индикатор лечения также является функцией. Я разделю данные на наборы для обучения и проверки (разделение 80/20) для ранней остановки. Я также буду использовать набор для проверки, чтобы проиллюстрировать показатели оценки модели. В реальном проекте из этого процесса следует зарезервировать протяженный набор тестов, где метрики оценки на наборе тестов будут использоваться для окончательной оценки модели.

train, valid = sk.model_selection.train_test_split(
    df, test_size=0.2,random_state=42)
print(train.shape, valid.shape)
(8000, 43) (2000, 43)

Укажите функции в виде списка. Это включает столбец обработки и все функции, которые находятся в восьмом столбце и далее:

features = ['treatment_group_key'] + df.columns.tolist()[7:]
print(features)
['treatment_group_key', 'x1_informative', 'x2_informative', 'x3_informative',... 'x36_uplift_increase']

Соберите обучающие и проверочные наборы для обучения классификатора XGBoost:

X_train = train[features]
y_train = train['conversion']
X_valid = valid[features]
y_valid = valid['conversion']
eval_set = [(X_train, y_train), (X_valid, y_valid)]

Пришло время создать и обучить модель.

model = xgb.XGBClassifier(learning_rate = 0.1,
                          max_depth = 6,
                          min_child_weight = 100,
                          objective = 'binary:logistic',
                          seed = 42,
                          gamma = 0.1,
                          silent = True,
                          n_jobs=2)
%%time
model.fit(X_train, y_train, eval_set=eval_set,\
          eval_metric="auc", verbose=True, early_stopping_rounds=30)
[0]	validation_0-auc:0.693049	validation_1-auc:0.648941
Multiple eval metrics have been passed: 'validation_1-auc' will be used for early stopping.

Will train until validation_1-auc hasn't improved in 30 rounds.
[1]	validation_0-auc:0.718238	validation_1-auc:0.656877
[2]	validation_0-auc:0.72416	validation_1-auc:0.667244
[3]	validation_0-auc:0.727643	validation_1-auc:0.669992
...
[99]	validation_0-auc:0.852237	validation_1-auc:0.762969
CPU times: user 6.7 s, sys: 87.8 ms, total: 6.79 s
Wall time: 3.48 s


XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
              colsample_bynode=1, colsample_bytree=1, gamma=0.1,
              learning_rate=0.1, max_delta_step=0, max_depth=6,
              min_child_weight=100, missing=None, n_estimators=10,
              n_jobs=2, nthread=None, objective='binary:logistic',
              random_state=0, reg_alpha=0, reg_lambda=1,
              scale_pos_weight=1, seed=42, silent=True, subsample=1,
              verbosity=1)

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

В качестве практического примечания я обнаружил, что в некоторых случаях при использовании T-Learner (здесь не показан) переобучение обучающей выборке может привести к неожиданным результатам при вычислении подъема. По моему опыту, проблему можно решить, уменьшив max_depth или увеличив min_child_weight в XGBClassifier, другими словами, уменьшив количество переобучения.

Еще один момент, который следует учитывать при построении модели, - это выбор функций, которые я здесь опускал. В контексте моделирования улучшения можно использовать показатели оценки модели повышения, представленные ниже, в наборе для проверки, как способ выбора функций, например, путем рекурсивного исключения функций. Выбор функций для моделей подъема также является темой недавних исследований, включая статью, которая является источником используемого здесь набора данных (Zhao et al.2020).

Оценка модели

Теперь у нас есть модель подъема. Построение модели S-Learner довольно просто, если вы уже знакомы с двоичной классификацией. Чтобы на самом деле рассчитать прирост для данного набора данных, при таком подходе нам нужно дважды оценить модель, один раз с обработкой = 1 и снова с обработкой = 0, а затем вычесть их, чтобы получить повышение. Здесь мы делаем это для набора проверки, а затем строим гистограмму оценок повышения.

X_valid_0 = X_valid.copy(); X_valid_0['treatment_group_key'] = 0
X_valid_1 = X_valid.copy(); X_valid_1['treatment_group_key'] = 1
Uplift = model.predict_proba(X_valid_1)[:,1]\
    - model.predict_proba(X_valid_0)[:,1]
mpl.rcParams['figure.dpi'] = 200
mpl.rcParams['figure.figsize'] = (6,4)
plt.hist(Uplift, bins=50)
plt.xlabel('Uplift score')
plt.ylabel('Number of observations in validation set')

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

Теперь главный вопрос: стоит ли доверять этим результатам? Как узнать, насколько хороша эта модель? Метрики для оценки модели повышения уровня сложности более сложны, чем типичные метрики, используемые в контролируемом обучении, такие как ROC AUC для задач классификации или RMSE для регрессии. Вообще говоря, показатели оценки улучшения представляют собой сравнение коэффициента конверсии между экспериментальной и контрольной группами для различных диапазонов прогнозируемой оценки повышения. Для тех, у кого есть высокие оценки улучшения, мы ожидаем увидеть большую разницу между лечением и контролем, в то время как у тех, у кого более низкие оценки улучшения, должна быть меньшая разница или даже больший коэффициент конверсии в контрольной группе в случае спящих собак ( т.е. отрицательная разница).

Квантильные показатели

Популярный способ оценки модели повышения - это квантильная диаграмма. Это даст быстрое визуальное представление о том, «работает» ли модель в смысле истинного подъема с уклоном. Чтобы создать диаграмму квантилей, мы начинаем с прогнозов повышения для нашего набора проверки и объединяем экземпляры в квантили на основе этих оценок. Количество квантилей зависит от того, сколько данных у нас есть, хотя на практике 10 - довольно типичное число (децили). Затем в каждой ячейке мы найдем разницу в коэффициенте конверсии для тех, кто находился в экспериментальной группе, и тех, кто был в контрольной группе. Если модель работает хорошо, мы должны увидеть большую положительную разницу в самом высоком дециле, уменьшающуюся до небольшой или отрицательной разницы в самом низком дециле (то есть скорость обработки аналогична скорости контроля или ниже скорости контроля). Другими словами, по мере увеличения прогнозируемого прироста реальный прирост от контрольной к экспериментальной группе также должен возрасти.

Получите квантили баллов

Создайте новый DataFrame из данных проверки, чтобы добавить оценки и квантили повышения.

Uplift.shape
(2000,)
valid.shape
(2000, 43)
valid_w_score = valid.copy()
valid_w_score['Uplift score'] = Uplift
valid_w_score.head()

Убедитесь, что экспериментальная и контрольная группы примерно сбалансированы в целом для набора проверки (они должны быть таковыми, поскольку мы использовали случайное разделение обучения / проверки, но всегда полезно проверить):

valid_w_score['treatment_group_key'].value_counts()
0    1011
1     989
Name: treatment_group_key, dtype: int64

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

score_quantiles, score_quantile_bins = \
pd.qcut(x=valid_w_score['Uplift score'],
        q=10,
        retbins=True,
        duplicates='drop')

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

score_quantiles.head()
6252    (-0.00339, 0.0186]
4684    (-0.114, -0.00339]
1731        (0.201, 0.398]
4742        (0.121, 0.148]
4521      (0.0391, 0.0548]
Name: Uplift score, dtype: category
Categories (10, interval[float64]): [(-0.114, -0.00339] < (-0.00339, 0.0186]...

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

valid_w_score['Quantile bin'] = score_quantiles
valid_w_score[[
    'treatment_group_key', 'conversion', 'Uplift score',
    'Quantile bin']].head(10)

Убедитесь, что количество обработанных и контрольных наблюдений в квантильных ячейках одинаково, используя groupby / count и некоторую магию мультииндекса:

count_by_quantile_and_treatment = valid_w_score.groupby(
    ['Quantile bin', 'treatment_group_key']) \
    ['treatment_group_key'].count()
count_by_quantile_and_treatment = \ 
    count_by_quantile_and_treatment.unstack(-1)
count_by_quantile_and_treatment

count_by_quantile_and_treatment.plot.barh()
plt.xlabel('Number of observations')

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

График квантиля роста

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

validation_treatment_mask = \
valid_w_score['treatment_group_key'] == 1

Затем мы получаем коэффициенты конверсии в квантилях оценки повышения отдельно для экспериментальной и контрольной групп:

treatment_by_quantile = valid_w_score[validation_treatment_mask]\
    .groupby('Quantile bin')['conversion'].mean()
control_by_quantile = valid_w_score[~validation_treatment_mask]\
    .groupby('Quantile bin')['conversion'].mean()

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

true_uplift_by_quantile = treatment_by_quantile - \
    control_by_quantile
true_uplift_by_quantile.head(5)
Quantile bin
(-0.114, -0.00339]   -0.017486
(-0.00339, 0.0186]    0.034343
(0.0186, 0.0391]     -0.004600
(0.0391, 0.0548]      0.021554
(0.0548, 0.0726]      0.133929
Name: conversion, dtype: float64

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

true_uplift_by_quantile.plot.barh()
plt.xlabel('True uplift')

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

Что мы можем узнать из квантильной диаграммы? Из анализа эксперимента мы знаем, что ATE составляет около 10%. График квантилей, который мы создали с помощью набора для проверки, говорит нам, что, ориентируясь на верхний дециль показателей повышения, мы можем достичь лечебного эффекта более 35%, то есть заметного увеличения. Следующие несколько децилей, по-видимому, также имеют больший лечебный эффект, чем ATE. В зависимости от того, насколько дорого обходится лечение, может иметь смысл охватить ограниченную часть населения, используя эту информацию.

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

Калибровка подъема

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

predicted_uplift_by_quantile = valid_w_score\
    .groupby(['Quantile bin'])['Uplift score'].mean()
predicted_uplift_by_quantile.head(5)
Quantile bin
(-0.114, -0.00339]   -0.035133
(-0.00339, 0.0186]    0.008385
(0.0186, 0.0391]      0.029582
(0.0391, 0.0548]      0.047033
(0.0548, 0.0726]      0.063634
Name: Uplift score, dtype: float32

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

pred_true_uplift = pd.DataFrame({'Predicted Uplift':predicted_uplift_by_quantile,
                                 'True Uplift':true_uplift_by_quantile})

min_on_plot = pred_true_uplift.min().min()
max_on_plot = pred_true_uplift.max().max()

ax = plt.axes()
ax.plot([min_on_plot, max_on_plot], [min_on_plot, max_on_plot],
        linestyle='--', color='tab:orange', label='One-one line')
pred_true_uplift.plot.scatter(x='Predicted Uplift', y='True Uplift',
                              ax=ax, label='Model performance')

ax.legend()

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

Есть еще несколько способов расширения этого анализа на основе квантилей:

  • Прогнозы для лечения = 1 и лечения = 0, которые использовались для расчета прироста, можно было отдельно калибровать относительно коэффициентов конверсии по децилям этих баллов в экспериментальной и контрольной группах соответственно. Это будет калибровка прогнозируемой вероятности конверсии для этих групп.
  • Планки погрешностей могут быть включены на все графики. Для прогнозируемой вероятности конверсии можно было бы рассчитать стандартную ошибку среднего в каждом интервале экспериментальной и контрольной групп отдельно, в то время как для истинного коэффициента конверсии можно было бы использовать нормальное приближение к биномиальному. Затем при вычитании средств обработки и контроля для получения повышения дисперсии, основанные на этих расчетах, будут добавлены, и стандартная ошибка повышения может быть рассчитана в пределах каждого интервала.
  • В деловой ситуации необходимо знать стоимость лечения и ожидаемый доход от конверсии. Диаграмма квантилей роста может быть расширена для отражения роста доходов, которые можно уравновесить с затратами на лечение для оценки прибыльности бизнес-стратегии.

Кумулятивные показатели

График совокупного прироста

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

Эта идея лежит в основе диаграммы совокупного прироста. Формула совокупной выгоды дана Гутиерресом и Жерарди (2016) как:

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

Чтобы получить данные для диаграммы совокупного прироста, нам нужно будет рассчитать количество клиентов в каждой ячейке квантилей оценок, как в экспериментальной, так и в контрольной группах (мы визуализировали это выше, но пересчитаем здесь), а также сумму преобразованных клиентов. . Здесь мы перевернем результат вверх ногами с помощью .iloc[::-1], чтобы смоделировать стратегию таргетинга сначала на клиентов с наивысшими показателями повышения, а затем двигаться дальше вниз.

treatment_count_by_quantile = valid_w_score[validation_treatment_mask]\
    .groupby('Quantile bin').agg({'conversion':['sum', 'count']}).iloc[::-1]

control_count_by_quantile = valid_w_score[~validation_treatment_mask]\
    .groupby('Quantile bin').agg({'conversion':['sum', 'count']}).iloc[::-1]

Вот как выглядит, например, лечебная группа:

treatment_count_by_quantile.head(5)

И совокупные суммы конверсий и общее количество клиентов:

treatment_count_by_quantile.cumsum().head(5)

Собирая все это вместе в формулу совокупного выигрыша, показанную выше, мы получаем:

cumulative_gain = \
    ((treatment_count_by_quantile[('conversion','sum')].cumsum()
      / treatment_count_by_quantile[('conversion','count')].cumsum())
     -
     (control_count_by_quantile[('conversion','sum')].cumsum()
      / control_count_by_quantile[('conversion','count')].cumsum()))\
    * (treatment_count_by_quantile[('conversion','count')].cumsum()
       + control_count_by_quantile[('conversion','count')].cumsum())

Теперь мы можем исследовать и построить график совокупного выигрыша.

cumulative_gain.round()
Quantile bin
(0.201, 0.398]         74.0
(0.148, 0.201]        125.0
(0.121, 0.148]        149.0
(0.0941, 0.121]       172.0
(0.0726, 0.0941]      209.0
(0.0548, 0.0726]      237.0
(0.0391, 0.0548]      242.0
(0.0186, 0.0391]      240.0
(-0.00339, 0.0186]    249.0
(-0.114, -0.00339]    248.0
dtype: float64
cumulative_gain.plot.barh()
plt.xlabel('Cumulative gain in converted customers')

Совокупный выигрыш дает еще один способ взглянуть на потенциальное влияние стратегии, основанной на модели повышения. Если мы предложим лечение каждому клиенту, мы увеличим количество обращенных клиентов на 248. Однако мы можем добиться увеличения в 149 клиентов, что составляет около 60% от максимально возможного, предлагая лечение только 30% лучших клиентов. (верхние 3 дециля), по количеству оценок. Это связано с тем, что по мере продвижения по списку мы ориентируемся на клиентов с более низким прогнозируемым индивидуальным эффектом лечения. Кумулятивное количество конверсий может даже снижаться от корзины к корзине в ячейках с более низким значением диаграммы, поскольку мы фактически теряем клиентов, ориентируясь на спящих собак.

Кривая накопленного усиления

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

Кривая усиления определяется как

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

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

sorted_valid = valid_w_score.sort_values('Uplift score',
    ascending=False)\
.reset_index(drop=True)

sorted_valid[['treatment_group_key', 'conversion',
    'Uplift score']].head(10)

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

sorted_valid['Y_T'] = \
    (sorted_valid['conversion'] * \
    sorted_valid['treatment_group_key']).cumsum()
sorted_valid['Y_C'] = \
    (sorted_valid['conversion'] * \
    (sorted_valid['treatment_group_key']==0)).cumsum()
sorted_valid['N_T'] = sorted_valid['treatment_group_key'].cumsum()
sorted_valid['N_C'] = \
    (sorted_valid['treatment_group_key']==0).cumsum()
sorted_valid[['treatment_group_key', 'conversion', 'Uplift score',
              'Y_T', 'Y_C', 'N_T', 'N_C']].head(10)

Теперь расчет кривой усиления можно произвести следующим образом:

sorted_valid['Gain curve'] = (
    (sorted_valid['Y_T']/sorted_valid['N_T'])
    -
    (sorted_valid['Y_C']/sorted_valid['N_C'])
    ) * (sorted_valid['N_T'] + sorted_valid['N_C'])

Давайте рассмотрим кривую усиления.

sorted_valid['Gain curve'].plot()
plt.xlabel('Number of customers treated')
plt.ylabel('Gain in conversion')

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

Перед вычислением AUC может быть полезно нормализовать данные. Как показано, кривая усиления имеет единицы потребителей по осям x и y. Это может быть полезно для визуализации вещей в реальных количествах. Однако, если бы мы хотели оценить производительность на наборах для валидации и тестирования, например, площади под этими кривыми могут быть несопоставимы, поскольку в этих наборах данных может быть разное количество наблюдений. Мы можем исправить это, масштабируя кривую так, чтобы оси x и y имели максимум 1.

Масштабированная ось абсцисс представляет долю целевой группы населения:

gain_x = sorted_valid.index.values + 1
gain_x = gain_x/gain_x.max()
print(gain_x[:3])
print(gain_x[-3:])
[0.0005 0.001  0.0015]
[0.999  0.9995 1.    ]

А по оси ординат в масштабе отложена доля выигрыша от лечения всей популяции:

gain_y = (
    sorted_valid['Gain curve']
    /
    sorted_valid['Gain curve'].tail(1).values
    ).values

print(gain_y[:3])
print(gain_y[-3:])
[nan  0.  0.]
[1.00802087 1.00534686 1.        ]

Обратите внимание, что первая запись на нормализованной кривой усиления - NaN; всегда будет хотя бы один из них в начале, потому что либо N ^ T_t, либо N ^ C_t будет равно нулю по крайней мере для первого наблюдения, что приведет к делению на нулевая ошибка. Поэтому мы отбросим здесь записи из векторов x и y, чтобы избавиться от NaN s, что не должно быть проблемой, если набор данных достаточно велик.

nan_mask = np.isnan(gain_y)
gain_x = gain_x[~nan_mask]
gain_y = gain_y[~nan_mask]
print(gain_y[:3])
print(gain_y[-3:])
[0.         0.         0.00805023]
[1.00802087 1.00534686 1.        ]

Теперь мы можем построить кривую нормализованного усиления вместе с вычисленной AUC. К этому добавим строку "один-один". Подобно интерпретации линии один к одному на кривой ROC, здесь это соответствует кривой выигрыша, которую мы теоретически ожидаем, обрабатывая клиентов случайным образом: доля выигрыша, которую мы получили бы, обрабатывая всех клиентов, увеличивается в соответствии с долей лечил и АТЕ.

mpl.rcParams['font.size'] = 8
gain_auc = auc(gain_x, gain_y)

ax = plt.axes()
ax.plot(gain_x, gain_y,
        label='Normalized gain curve, AUC {}'.format(gain_auc.round(2)))
ax.plot([0, 1], [0, 1],
        '--', color='tab:orange',
        label='Random treatment')
ax.set_aspect('equal')
ax.legend()
ax.set_xlabel('Fraction of population treated')
ax.set_ylabel('Fraction of gain in converted customers')
ax.grid()

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

Вычисленная здесь AUC дает общий способ сравнения производительности модели повышения эффективности для разных моделей и наборов данных, таких как наборы для обучения, проверки и тестирования для данного приложения. Кривые нормализованного усиления могут быть построены вместе, и их AUC сравниваются таким же образом, как ROC AUC сравниваются для задач контролируемой классификации. Заинтересованный читатель может пожелать разработать модель T-Learner и сравнить с результатами S-Learner, показанными здесь в качестве упражнения.

Заключение

Целью моделирования подъема является создание прогностических моделей индивидуального эффекта лечения. Такие модели позволяют специалистам по обработке данных сегментировать группы населения на группы, которые с большей вероятностью отреагируют на лечение, и группы с меньшей вероятностью. С этой целью было разработано множество методов моделирования; моделирование подъемов продолжает вызывать активный исследовательский интерес. Оценка моделей повышения не так проста, как оценка контролируемых моделей классификации или регрессии, поскольку требует отдельного рассмотрения и сравнения экспериментальной и контрольной групп. Однако были созданы пакеты Python с открытым исходным кодом (CausalML, Pylift), чтобы облегчить разработку и оценку модели улучшения. Несколько полезных методов оценки улучшений, некоторые из которых доступны в этих пакетах, были продемонстрированы здесь с использованием Python и pandas.

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

Спасибо Пьеру Гутьерресу и Роберту Йи за ваш вклад и отзывы

использованная литература

CausalML: пакет Python, который предоставляет набор методов моделирования и вывода причинно-следственных связей с использованием алгоритмов машинного обучения, основанных на недавних исследованиях. Дата обращения 05.07.2020.

Гутьеррес, Пьер и Жан-Ив Жерарди, 2016. Причинно-следственный вывод и моделирование подъемов: обзор литературы. JMLR: Workshop and Conference Proceedings 67: 1–13.

Kunzel, Soren R. et al., 2019. Metalearners для оценки неоднородных эффектов обработки с использованием обучения механической обработке. PNAS 5 марта 2019 г. 115 (10) 4156–4165

Ли, Тайён и др., 2013 г. Моделирование инкрементального отклика с использованием SAS Enterprise Miner. SAS Global Forum 2013: интеллектуальный анализ данных и текстовая аналитика .

Pylift: библиотека улучшений, которая предоставляет, в первую очередь, (1) реализацию быстрого моделирования и (2) инструменты оценки. Дата обращения 05.07.2020.

Йи, Роберт и Уилл Фрост, 2018. Pylift: быстрый пакет Python для моделирования подъемов. Дата обращения 05.07.2020.

Чжао, Чжэнью и др., 2020. Методы выбора признаков для моделирования подъемов.

Первоначально опубликовано на https://www.steveklosterman.com 8 июля 2020 г.