Реализация X-Learner¶
1 | Задание¶
- Повторите метод
X-learner
перекрестной зависимости для обучения моделей. - Обратите внимание, что вам необходимо реализовать метод без использования встроенных подходов
- Воспользуйтесь датасетами
fetch_x5
, которые были использованы в демонстрации на вебинаре. - Разбейте данные на выборки train, valid, test.
- Обратите внимание, что для обучения перекрестной зависимости вам необходимо использовать выборки контрольной и тестовой групп независимо друг от друга.
- Обучите независимо базовые модели на данных контрольной группы и целевой.
- Скорректируйте таргет.
- Обучите модели на скорректированных таргетах.
- Оцените качество полученной модели X-learner на тестовой выборке при разных значениях параметра g. Сравните по метрике uplift@10% с моделями, полученными на вебинаре.
- Опционально: замерьте качество полученной модели по другим метрикам, которые вы знаете.
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import LabelEncoder
from catboost import CatBoostRegressor
from matplotlib import pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')
random_state = 47
k = 0.1
# функция для оценки uplift@k
def uplift_at_k(uplift_scores, y_true, treatment, k):
n_top = int(len(uplift_scores) * k)
indices = np.argsort(-uplift_scores)[:n_top]
treat_top = treatment.iloc[indices]
y_top = y_true.iloc[indices]
return y_top[treat_top == 1].mean() - y_top[treat_top == 0].mean()
models_results = {
'approach': [],
f'train_uplift@{k*100}%': [],
f'test_uplift@{k*100}%': []
}
2 | Читаем данныем¶
Используем датасет fetch_x5_clients
treatment_flg
: вектор воздействии с клиентом, данные содердат клиенты с которыми мы взаимодействовали (treatment_flg=1) [target] и те с которыми мы не взаимодействовали (treatment_flg=0) [control]target
: результат воздействия, негативный результат (target = 0), позитивны результат (target = 1)
import pandas as pd
df_clients = pd.read_csv('/kaggle/input/fetch-x5/fetch_x5_clients.csv')
df_train = pd.read_csv('/kaggle/input/fetch-x5/fetch_x5_train.csv')
# Клиенты
df_clients.head()
client_id | first_issue_date | first_redeem_date | age | gender | |
---|---|---|---|---|---|
0 | 000012768d | 2017-08-05 15:40:48 | 2018-01-04 19:30:07 | 45 | U |
1 | 000036f903 | 2017-04-10 13:54:23 | 2017-04-23 12:37:56 | 72 | F |
2 | 000048b7a6 | 2018-12-15 13:33:11 | NaN | 68 | F |
3 | 000073194a | 2017-05-23 12:56:14 | 2017-11-24 11:18:01 | 60 | F |
4 | 00007c7133 | 2017-05-22 16:17:08 | 2018-12-31 17:17:33 | 67 | U |
# результат иследования воздействия на клеинта
df_train.head()
client_id | treatment_flg | target | |
---|---|---|---|
0 | 000012768d | 0 | 1 |
1 | 000036f903 | 1 | 1 |
2 | 00010925a5 | 1 | 1 |
3 | 0001f552b0 | 1 | 1 |
4 | 00020e7b18 | 1 | 1 |
df_clients = df_clients[df_clients['client_id'].isin(df_train['client_id'])]
print(df_clients.shape, df_train.shape)
(200039, 5) (200039, 3)
print(f"Dataset features shape: {df_clients.shape}")
print(f"Dataset train shape: {df_train.shape}")
print(f"Dataset target mean: {df_train.target.mean()}")
print(f"Dataset treatment mean: {df_train.treatment_flg.mean()}")
Dataset features shape: (200039, 5) Dataset train shape: (200039, 3) Dataset target mean: 0.6198891216212838 Dataset treatment mean: 0.4998075375301816
df_clients = df_clients.dropna()
df_clients.shape
(182493, 5)
df_features = df_train.merge(df_clients,on='client_id')
df_features.index = df_features['client_id']
del df_features['client_id']
3 | Предобработка¶
- Будем использовать градиентный бустинг, поэтому нам надо конвертировать категориальные признаки в цифровые
- Обработаем datetime фичи в цифровые значения; UNIX время
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
df_features['gender'] = encoder.fit_transform(df_features['gender'])
# преобразуем datetime в unix
df_features['first_issue_time'] = (pd.to_datetime(df_features['first_issue_date'])- pd.Timestamp('1970-01-01')) // pd.Timedelta('1s')
df_features['first_redeem_time'] = (pd.to_datetime(df_features['first_redeem_date']) - pd.Timestamp('1970-01-01')) // pd.Timedelta('1s')
df_features['issue_redeem_delay'] = df_features['first_redeem_time'] - df_features['first_issue_time']
df_features = df_features.drop(['first_issue_date', 'first_redeem_date'], axis=1)
df_features.head(2)
treatment_flg | target | age | gender | first_issue_time | first_redeem_time | issue_redeem_delay | |
---|---|---|---|---|---|---|---|
client_id | |||||||
000012768d | 0 | 1 | 45 | 2 | 1501947648 | 1515094207 | 13146559 |
000036f903 | 1 | 1 | 72 | 0 | 1491832463 | 1492951076 | 1118613 |
# убираем дубликаты если они есть
df_features = df_features.drop_duplicates()
4 | Train/Test подвыборки¶
Разбиваем данные на две подвыборки, определив y
и T
from sklearn.model_selection import train_test_split
y = "target"
T = "treatment_flg"
X = ["age","gender","first_issue_time","first_redeem_time","issue_redeem_delay"]
train, test = train_test_split(df_features,
test_size=0.3,
random_state=47)
5 | Целевые вектора¶
Проверим баланс челевого вектора
print('train target ratio',round(train[y].mean(),3))
print('test target ratio',round(test[y].mean(),3))
train target ratio 0.645 test target ratio 0.644
6 | Моделировние¶
X-learner approach¶
1) Строим две независимые модели на контрольной и тестовой группах
$ \hat{M}_0(X) \approx E[Y| T=0, X] $
$ \hat{M}_1(X) \approx E[Y| T=1, X] $
2) Рассчитываем разность (собственно, Uplift) между значением целевой переменной при воздействии на объекты и без воздействия
$ \hat{\tau}(X, T=0) = \hat{M}_1(X, T=0) - Y_{T=0} $
$ \hat{\tau}(X, T=1) = Y_{T=1} - \hat{M}_0(X, T=1) $
3) Строим две прогнозные модели на фичах сэмплов и значениях Uplift
$ \hat{M}_{\tau 0}(X) \approx E[\hat{\tau}(X)|T=0] $
$ \hat{M}_{\tau 1}(X) \approx E[\hat{\tau}(X)|T=1] $
4) Результаты применения двух моделей складываем с учётом веса $\hat{e}(x)$; propensity score model
- $ \hat{\tau(x)} = \hat{M}_{\tau 0}(X)\hat{e}(x) + \hat{M}_{\tau 1}(X)(1-\hat{e}(x)) $
1) Обучение независимые модели¶
Строим две независимые модели на контрольной и тестовой группах
- Контрольная группа
m0
(treatment_flg = 0) - Целевая группа
m1
(treatment_flg = 1)
Так же обучаем propensity score модель, она у нас не будем константой
from sklearn.linear_model import LogisticRegression
np.random.seed(123)
# модели первого уровня
m0 = CatBoostRegressor(iterations=100,depth=4,learning_rate=0.1,random_seed=42,verbose=0)
m1 = CatBoostRegressor(iterations=100,depth=4,learning_rate=0.1,random_seed=42,verbose=0)
# propensity модель
g = LogisticRegression(solver="lbfgs", penalty='none')
m0.fit(train.query(f"{T}==0")[X], train.query(f"{T}==0")[y])
m1.fit(train.query(f"{T}==1")[X], train.query(f"{T}==1")[y])
g.fit(train[X], train[T]);
d_train = np.where(train[T]==0,
m1.predict(train[X]) - train[y],
train[y] - m0.predict(train[X]))
2) Корректировка таргетов¶
Рассчитываем разность (собственно, Uplift) между значением целевой переменной при воздействии на объекты и без воздействия
# corrected labels for 2nd group of models
d_train = np.where(train[T]==0,
m1.predict(train[X]) - train[y],
train[y] - m0.predict(train[X]))
3) Обучаем скорректированных моделей¶
Using the adjusted target variables ( for both the target and control groups, we train the main models
# second stage
mx0 = CatBoostRegressor(iterations=100,depth=4,learning_rate=0.1,random_seed=42,verbose=0)
mx1 = CatBoostRegressor(iterations=100,depth=4,learning_rate=0.1,random_seed=42,verbose=0)
mx0.fit(train.query(f"{T}==0")[X], d_train[train[T]==0])
mx1.fit(train.query(f"{T}==1")[X], d_train[train[T]==1]);
4) Результаты применения двух моделей складываем с учётом веса¶
Результаты применения двух моделей складываем с учётом веса
def ps_predict(df, t):
return g.predict_proba(df[X])[:, t]
x_cate_train = (ps_predict(train,1)*mx0.predict(train[X]) +
ps_predict(train,0)*mx1.predict(train[X]))
x_cate_test = (ps_predict(test,1)*mx0.predict(test[X]) +
ps_predict(test,0)*mx1.predict(test[X]))
x_cate_test_res = test.assign(cate=x_cate_test)
ct_score_train = uplift_at_k(x_cate_train, train[y].copy(), train[T].copy(), k)
ct_score_test = uplift_at_k(x_cate_test, test[y].copy(), test[T].copy(), k)
print('train uplift @k=10%',round(ct_score_train,3))
print('test set uplift @k=10%',round(ct_score_test,3))
train uplift @k=10% 0.147 test set uplift @k=10% 0.109
Сравним как модель себя показала по сравнению в другими моделями
import io
strs = """
approach train_uplift@10.0% test_uplift@10.0%
2 Two-Model Approach (T-Learner) 0.146412 0.114067
0 ClassTransformation 0.149799 0.111760
3 Two-Model Approach (ddr_control) 0.121663 0.108137
4 Two-Model Approach (ddr_treatment) 0.101789 0.076922
1 S-Learner 0.075230 0.075230
"""
models_results = pd.read_csv(io.StringIO(strs),sep='\t').to_dict('list')
models_results['approach'].append('X Learner')
models_results[f'train_uplift@{k*100}%'].append(ct_score_train)
models_results[f'test_uplift@{k*100}%'].append(ct_score_test)
pd.DataFrame(models_results).sort_values(by='test_uplift@10.0%',ascending=False)
approach | train_uplift@10.0% | test_uplift@10.0% | |
---|---|---|---|
0 | Two-Model Approach (T-Learner) | 0.146412 | 0.114067 |
1 | ClassTransformation | 0.149799 | 0.111760 |
5 | X Learner | 0.147077 | 0.109125 |
2 | Two-Model Approach (ddr_control) | 0.121663 | 0.108137 |
3 | Two-Model Approach (ddr_treatment) | 0.101789 | 0.076922 |
4 | S-Learner | 0.075230 | 0.075230 |