Мета-обучающиеся, такие как S-Learner, T-Learner и X-Learner, являются одними из наиболее широко используемых подходов для моделирования Uplift. При обучении этим подходам я обнаружил, что студенты часто находят модель X-learner несколько запутанной для понимания. В этом посте я описываю модифицированный подход, который я называю упрощенным X-learner (Xs-learner), который легче понять, быстрее реализовать и, по моему опыту, на практике часто работает так же или даже лучше.

Моделирование подъема

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

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

Мета-обучающиеся

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

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

В каждом из примеров я буду использовать следующие обозначения:
- Y: совершил ли пользователь покупку (переменная результата)
- T: получил ли пользователь текстовое сообщение (переменная обработки)
- X: вся другая известная нам информация о пользователе, например. возраст, пол, история покупок. (Набор данных «Ленты» содержит почти 200 признаков, описывающих каждого пользователя)

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from xgboost import XGBClassifier, XGBRegressor
from sklift.datasets import fetch_lenta
from sklift.viz import plot_qini_curve

from numpy.random import default_rng
rng = default_rng()

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

data = fetch_lenta()
Y = data['target_name']
X = data['feature_names']

df = pd.concat([data['target'], data['treatment'], data['data']], axis=1)
gender_map = {'Ж': 0, 'М': 1}
group_map = {'test': 1, 'control': 0}
df['gender'] = df['gender'].map(gender_map)
df['treatment'] = df['group'].map(group_map)
T = 'treatment'

# Split our data into a training and an evaluation sample
df_train, df_test = train_test_split(df, test_size=0.3, random_state=42)

S-learner — самый простой и понятный из этих подходов. С помощью S-learner вы создаете единую модель машинного обучения, использующую все ваши данные, с переменной обработки (получили ли вы текстовое сообщение) в качестве одной из функций. Затем вы можете использовать эту модель, чтобы предсказать, «что произойдет, если пользователь получит текст» и «что произойдет, если пользователь не получит текст». Разница между этими двумя прогнозами — это ваша оценка воздействия текстового сообщения на пользователя.

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

slearner = XGBClassifier()
slearner.fit(df_train[X+[T]], df_train[Y])

# Calculate the difference in predictions when T=1 vs T=0
# This is our estimate of the effect of the coupon for each user in our data
slearner_te = slearner.predict_proba(df_test[X].assign(**{T: 1}))[:, 1] \
            - slearner.predict_proba(df_test[X].assign(**{T: 0}))[:, 1]

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

T-leaner использует две отдельные модели. Первая модель рассматривает только тех пользователей, которые не получили купон. Вторая модель учитывает только тех пользователей, которые получили купон. Чтобы предсказать эффект лечения, мы берем разницу между предсказаниями этих двух моделей.

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

tlearner_0 = XGBClassifier()
tlearner_1 = XGBClassifier()

# Split data into treated and untreated
df_train_0 = df_train[df_train[T] == 0]
df_train_1 = df_train[df_train[T] == 1]

# Fit the models on each sample
tlearner_0.fit(df_train_0[X], df_train_0[Y])
tlearner_1.fit(df_train_1[X], df_train_1[Y])

# Calculate the difference in predictions
tlearner_te = tlearner_1.predict_proba(df_test[X])[:, 1] \
            - tlearner_0.predict_proba(df_test[X])[:, 1]

Упрощенный X-learner (Xs-learner)

Упрощенный X-learner использует 3 модели для формирования прогнозов. Первые две модели — это точно те же модели, которые мы использовали для T-learner: одна модель обучалась только с использованием обработанных наблюдений, а другая — только с необработанными наблюдениями.

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

# We could also just reuse the models we made for the T-learner
xlearner_0 = XGBClassifier()
xlearner_1 = XGBClassifier()

# Split data into treated and untreated
df_train_0 = df_train[df_train[T] == 0]
df_train_1 = df_train[df_train[T] == 1]

# Fit the models on each sample
xlearner_0.fit(df_train_0[X], df_train_0[Y])
xlearner_1.fit(df_train_1[X], df_train_1[Y])

# Calculate the difference between actual outcomes and predictions
xlearner_te_0 = xlearner_1.predict_proba(df_train_0[X])[:, 1] - df_train_0[Y]
xlearner_te_1 = df_train_1[Y] - xlearner_0.predict_proba(df_train_1[X])[:, 1]

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

# Even though the outcome is binary, the treatment effects are continuous
xlearner_combined = XGBRegressor() 

# Fit the combined model
xlearner_combined.fit(
  # Stack the X variables for the treated and untreated users
  pd.concat([df_train_0, df_train_1])[X], 
  # Stack the X-learner treatment effects for treated and untreated users
  pd.concat([xlearner_te_0, xlearner_te_1])
)

# Predict treatment effects for each user
xlearner_simple_te = xlearner_combined.predict(df_test[X])

Полный X-учащийся

Для упрощенного X-Learner требовалось 3 модели ML. Полный X-leaner, первоначально предложенный Künzel et al. требуется 5 моделей ML.

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

# Define the new models that are not used in the simple version
xlearner_te_model_0 = XGBRegressor()
xlearner_te_model_1 = XGBRegressor()
xlearner_propensity = XGBClassifier()

xlearner_te_model_0.fit(df_train_0[X], xlearner_te_0)
xlearner_te_model_1.fit(df_train_1[X], xlearner_te_1)

# Calculate predictions from both models
xlearner_te_model_0_te = xlearner_te_model_0.predict(df_test[X])
xlearner_te_model_1_te = xlearner_te_model_1.predict(df_test[X])

# Calculate the propensity scores
xlearner_propensity.fit(df_train[X], df_train[T])
xlearner_propensities = xlearner_propensity.predict_proba(df_test[X])[:, 1]

# Calculate the treatment effects as propensity weighted average
xlearner_te = xlearner_propensities * xlearner_te_model_0_te + (1 - xlearner_propensities) * xlearner_te_model_1_te

Сравнение результатов

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

fig, ax = plt.subplots(figsize=(20, 10))
def plot_qini_short(model, label, color, linestyle):
    plot_qini_curve(df_test[Y], model, df_test[T], name=label, ax=ax, perfect=False, color=color, linestyle=linestyle)
plot_qini_short(slearner_te, 'Slearner', 'blue', 'solid')
plot_qini_short(tlearner_te, 'Tlearner', 'red', 'solid')
plot_qini_short(xlearner_simple_te, 'Xlearner Simple', 'purple', 'solid')
plot_qini_short(xlearner_te, 'Xlearner', 'green', 'solid')
ax.legend(loc='lower right');

Для этого конкретного набора данных упрощенный X-Learner показал наилучшую общую производительность.

Из этого единственного примера не следует делать каких-либо серьезных выводов об относительной производительности разностных алгоритмов. По моему опыту, алгоритм, который работает лучше всего, сильно зависит от конкретной проблемы, над которой вы работаете. Тем не менее, я думаю, что этот пример демонстрирует, что упрощенный X-Learner (Xs-learner) — еще один подход, который стоит учитывать при работе над задачами Uplift.

Узнать больше

Если вы заинтересованы в причинно-следственном выводе или знаете кого-то, кому было бы полезно его изучить, в конце февраля я проведу занятие по причинно-следственному выводу с акцентом на обычное использование в технологической отрасли. Я буду преподавать мета-обучение и многие другие темы, такие как синтетические элементы управления и двойное машинное обучение. getsphere.com/data-science/applied-causal-inference?source=Instructor-Socials-Medium-11523-x_learner

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

Атей, Сьюзен и Гвидо В. Имбенс. Машинное обучение для оценки разнородных причинных эффектов. №3350. 2015. https://www.gsb.stanford.edu/faculty-research/working-papers/machine-learning-estimating-heretogeneous-casual-effects

Künzel, Sören R., et al. Metalearners для оценки разнородных эффектов лечения с использованием машинного обучения. Известия национальной академии наук 116.10 (2019): 4156–4165. http://sekhon.berkeley.edu/papers/x-learner.pdf

Гутьеррес, Пьер и Жан-Ив Жерарди. Причинный вывод и моделирование подъема: обзор литературы. Международная конференция по прогностическим приложениям и API. ПМЛР, 2017. http://proceedings.mlr.press/v67/gutierrez17a/gutierrez17a.pdf