上一篇【智能营销增益(Uplift Modeling)模型——模型介绍(一)】仔细介绍了理论部分,本篇主要是算法库pylift的介绍。
在【营销增益模型实战-Uplift Model原理及应用】一文中提到:
- 目前的算法包虽然可以直接使用,但速度慢,定制性差
由于Uplift模型还未被广泛使用,业界对于该技术的定义混乱,每个领域甚至每个公司都会有自己的魔改版本,甚至连该方法的名称都没有得到统一,举几个常见的例子:
- Estimating heterogeneous treatment effects (Example)
- Incremental response modeling (Example)
- Net scoring (Example)
- True response modeling (Example)
推荐使用的Packages:
- CasualML: Uplift Modeling Python Package by Uber
- Pylift Uplift Meta-learner Python Package
- R Uplift Package
- Opossum: a Python package to create synthetic uplift datasets
- “EconML” (ALICE: Automated Learning and Intelligence for Causation and Economics)
- DoWhy
- pymatch
文章目录
1 pylift 模型介绍
参考文章:一文读懂uplift model 还有官网文档
主要源自: Transformed Outcome (Athey 2016)
Uplift models需要每一个人的两方面信息:是否给予treatment,产出label。理想情况下,我们可以得到一些个体在随机分配到实验组(treat group)和对照组(control group)后的数据,基于他们对于treatment的反应,outcome label可以被转化为下面这个矩阵(Athey and Imbens 2016)(实际情况中可以有其他的正负样本划分方法):
可能在第一眼的时候觉得这个目标矩阵不太靠谱,像拍脑袋的结果,为啥没有买的给不给treatment都是0?
为什么给了treatment买了就是2?
看起来非常不直观,但是是有道理在里面的,假如将一群人随机分为控制组和对照组,最后得到的平均值矩阵就是这群人的lift。
为了说明这个问题,考虑有一群人,人数为2n,其中n个给了treatment t,另外n个不给作为对照组。为了简单起见我们将i =1 ,…, n为treatment组,i =n+1 ,…, 2n为控制对照组。对于每一个用户,原始的outcomes和转换后的分别为 Yi和Zi ,那么对于这群人,对于购买行为的lift为:
也就是说,转化后的outcome取决于前一个group的lift,这个优雅的转换大大简化了lift问题,我们可以直接对z建立回归模型,我们就可以得到对于基于特征x表征的用户的uplift:
2 pylift安装与Quick start
2.1 pylift的安装
Pylift是uplift建模的Python工具包。Pylift在sklearn的基础上针对uplift modeling对各模型做了一些优化,同时集成了一套uplift评价指标体系。所以Pylift内核还是sklearn,在sklearn外面封装了一套API,针对树模型做了uplift优化,可以通过Pylift实现直接的uplift modeling.
官方文档:readthedocs
github地址:wayfair/pylift
最便捷的安装方式:
pip install pylift
2.2 pylift的Quick start
参考:【营销增益模型实战-Uplift Model原理及应用】
from pylift import TransformedOutcome
up = TransformedOutcome(df1, col_treatment='Treatment', col_outcome='Converted')
up.randomized_search() # 对所有参数grid search,十分耗时,使用时注意限制参数searching的数量
up.fit(**up.rand_search_.best_params_)
up.plot(plot_type='aqini', show_theoretical_max=True) # 绘制aqini曲线
print(up.test_results_.Q_aqini)
所有模型的实现都通过类TransformedOutcome操作,通过传参来指定模型。
默认的模型是XGBRegressor. 如果使用其他模型,通过参数sklearn_model指定,如下:
up = TransformedOutcome(df, col_treatment='Treatment', col_outcome='Converted', sklearn_model=RandomForestRegressor)
参数col_treatment
是指数据集df中区分是否是treatment group的字段,通过0/1二值区分,
col_outcome
相当于label字段,如有转化是1,无转化是0.
Pylift另一个方便之处是提供了计算uplift评价指标的函数。如果是使用TransformedOutcome生成
的结果,直接输入up.plot(plot_type='qini')
即可绘制qini曲线。如果是自己的数据,可以通过下述方式来绘制曲线:
from pylift.eval import UpliftEval
upev = UpliftEval(treatment, outcome, predictions)
upev.plot(plot_type='aqini')
其中treatment
标识是否treatment group
,list格式;
outcome
相当于label,list格式;
predictions
是预测的uplift score分数,
注意这是uplift score
,如果不是uplift score
需要自己先将uplift score
计算好。
上一小节模型评估中的图均是通过UpliftEval绘制的。
plot_type参数是绘制曲线的类型,可以绘制以下六种曲线:
- qini:qini曲线
- aqini:修正后的qini曲线,更适应treatment group和control group数据不均衡的数据
- cuplift:累积uplift曲线
- uplift:uplift曲线,不累积
- cgains:累积gain曲线
- balance:每个百分比下,实验组目标人数除以所有人数
通过以下方式计算曲线面积:
upev.Q_aqini # aqini曲线与random line之间的面积
upev.q1_qini # qini曲线与理想曲线之间的面积
upev.q2_cgains # cgains曲线与最佳实践曲线之间的面积
Pylift用于实验很方便,但因为还是基于sklearn,当达到千万量级的数据量时,还是需要考虑分布式。
3 案例一:完整demo案例
来自官方github之中:simulated_data/sample.ipynb
3.1 生成模拟数据
import numpy as np, matplotlib as mpl, matplotlib.pyplot as plt, pandas as pd
from pylift import TransformedOutcome
from pylift.generate_data import dgp
# Generate some data.
df = dgp(N=10000, discrete_outcome=True)
df
数据样式:
treatment
是指数据集df中区分是否是treatment group的字段,通过0/1二值区分,
outcome
相当于label字段,如有转化是1,无转化是0.
3.2 初始化模型
# Specify your dataframe, treatment column, and outcome column.
up = TransformedOutcome(df, col_treatment='Treatment', col_outcome='Outcome', stratify=df['Treatment'])
为了建立建模的框架,只需实例化TransformedOutcome对象,并指定实验组(treatment)和对照组(outcome)(必须输入:pandas.DataFrame
)。
还有一些可选参数,可以自由调节。
其中,stratify
参数比较重要,因为其会直接传递给sklearn.model_select .train_test_split
用来作为训练集/验证集的拆分
3.3 数据EDA 与 特征选择
增益模型的EDA是比较有技巧性的,该库作者已经融合了一些方法进来进行特征选择和检查模型数据。
其中,NIV(net information value) 和 NWOE(net weight of evidence) 两个指标会被计算,两个指标的概念可参考这篇博客:Data Exploration with Weight of Evidence and Information Value in R
来看看这篇博客描述这两个指标:
- NIV measures the strength of a given variable
- while NWOE describes the pattern of the relationship.
Specifically, the higher the value of NIV, the better the given variable is at separating self-selectors – i.e., people who are self-motivated to buy – and persuadables that need to be motivated.
具体来说,NIV的值越高,给到的特征越能辨别出自然转化(self-selectors)与营销敏感人群(persuadables)
# This function randomly shuffles your training data set and calculates net information value.
up.NIV()
up.NWOE(feats_to_use=[0])
3.4 超参数优化与模型拟合
up.randomized_search(n_iter=20, n_jobs=10, random_state=1)
up.fit(**up.rand_search_.best_params_)
3.5 模型评估
up.plot()
好的曲线,是均匀分布的;
一个陡峭、不均匀的曲线代表可能存在非常重要的特征,影响着实验组、控制组,需要找到并纠正这样的特征
3.6 通过误差曲线提高模型
绘制误差曲线可以有效地帮助了解预测模型的实际情况。
方法一:up.shuffle_fit
实现
通过不断shuffle训练集与验证集,使用相同的超参数,然后把几个模型的结果绘制到同一个qini 图中
up.shuffle_fit(params=up.rand_search_.best_params_, nthread=30, iterations=5)
up.plot(show_shuffle_fits=True)
方法二:up.noise_fit
实现
随机扰乱预测集内容,来看会造成如何的误差
up.noise_fit()
up.plot(show_noise_fits=True)
3.7 用自定义目标函数
特别是对于xgboost,更改目标函数可能会很有用。
例如,如果您喜欢以相等的概率去区分四种人:营销敏感性、反作用人群、自然购买人群、无所谓人群,可以考虑在TransformedOutcome
环节使用:MAE
def fair_obj(dtrain, preds):
"""y = c * abs(x) - c * np.log(abs(abs(x) + c))"""
x = preds - dtrain
c = 1
den = abs(x) + c
treat = dtrain > 0
cont = dtrain < 0
grad = c*x / den
hess = c*c / den ** 2
return grad, hess
def huber_approx_obj(dtrain, preds):
d = dtrain - preds #remove .get_labels() for sklearn
h = 1 #h is delta in the graphic
scale = 1 + (d / h) ** 2
scale_sqrt = np.sqrt(scale)
grad = d / scale_sqrt
hess = 1 / scale / scale_sqrt
return grad, hess
def log_cosh_obj(dtrain, preds):
x = preds - dtrain
grad = np.tanh(x)
hess = 1 / np.cosh(x)**2
return grad, hess
# Specify your dataframe, treatment column, and outcome column.
upnew = TransformedOutcome(df, col_treatment='Treatment', col_outcome='Outcome', stratify=df['Treatment'], scoring_method='aqini', scoring_cutoff=0.4)
# Using MAE as the objective.
from xgboost import XGBRegressor
upnew.randomized_search_params['estimator'] = XGBRegressor(objective=log_cosh_obj, nthread=1)
upnew.randomized_search(n_iter=10, verbose=3, n_jobs=1)
upnew.fit(nthread=50, **upnew.rand_search_.best_params_, objective=log_cosh_obj)
upnew.plot(label='New objective', n_bins=30)
一般来说,私自调整自定义目标函数效果不好,但是这样的调整行为一定程度纠正:不均衡的处理/控制
3.8 切换不同的模型
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
up1 = TransformedOutcome(df, col_treatment='Treatment', col_outcome='Outcome', stratify=df['Treatment'],
sklearn_model=RandomForestRegressor)
# RandomizedSearchCV.
up1.randomized_search(param_distributions={'max_depth': range(1,100), 'min_samples_split': range(1,1000)}, n_iter=10, n_jobs=10)
up1.fit(**up1.rand_search_.best_params_)
up1.plot()
4 案例二:Lalonde_sample
一个完整案例:Lalonde/Lalonde_sample.ipynb
%reload_ext autoreload
%autoreload 2
%matplotlib inline
import numpy as np, matplotlib as mpl, matplotlib.pyplot as plt, pandas as pd
from pylift import TransformedOutcome
import pandas as pd
pd.options.display.max_rows = 12
#1 载入数据
cols = ['treat', 'age', 'educ', 'black', 'hisp', 'married', 'nodegr','re74','re75','re78']
control_df = pd.read_csv('http://www.nber.org/~rdehejia/data/nswre74_control.txt', sep='\s+', header = None, names = cols)
treated_df = pd.read_csv('http://www.nber.org/~rdehejia/data/nswre74_treated.txt', sep='\s+', header = None, names = cols)
lalonde_df = pd.concat([control_df, treated_df], ignore_index=True)
lalonde_df['u74'] = np.where(lalonde_df['re74'] == 0, 1.0, 0.0)
lalonde_df['u75'] = np.where(lalonde_df['re75'] == 0, 1.0, 0.0)
#2 清洗数据
df = lalonde_df[['nodegr', 'black', 'hisp', 'age', 'educ', 'married', 'u74', 'u75', 'treat', 're78']].copy()
df.rename(columns={'treat':'Treatment', 're78':'Outcome'}, inplace=True)
df['Outcome'] = np.where(df['Outcome'] > 0, 1.0, 0.0)
# 3 Treatment and Outcome的交叉统计
pd.crosstab(df['Outcome'], df['Treatment'], margins = True)
# 4 建模
up = TransformedOutcome(df, col_treatment='Treatment', col_outcome='Outcome', stratify=df['Treatment'])
up.randomized_search(n_iter=20, n_jobs=10, random_state=1)
up.shuffle_fit(params=up.rand_search_.best_params_, nthread=30, iterations=5)
up.plot(show_shuffle_fits=True)
5 pylift 使用介绍
5.1 pylift一些重要属性
一些TransformedOutcome
之后,up之中重要属性:
up.randomized_search_params # Parameters that are used in `up.randomized_search()`
up.grid_search_params # Parameters that are used in `up.grid_search()`
up.transform # Outcome transform function.
up.untransform # Reverse of outcome transform function.
# Data (`y` in any of these can be replaced with `tc` for treatment or `x`).
up.transformed_y_train_pred # The predicted uplift.
up.transformed_y_train # The transformed outcome.
up.y_train
up.y_test
up.y # All the `y` data.
up.df
up.df_train
up.df_test
# Once a model has been created...
up.model
up.model_final
up.Q_cgains # 'aqini' or 'qini' can be used in place of 'cgains'
up.q1_cgains
up.q2_cgains
UpliftEval
之后评估曲线信息的重要属性:
upev.PLOTTYPE_x # percentile
upev.PLOTTYPE_y
PLOTTYPE
可以用以下任意一种代替:qini
,aqini
,cgains
,cuplift
,balance
,uplift
。由于up.test_results_
和up.train_results_
是UpliftEval
类对象,因此也可以按上面所示类似地对其进行访问。
还可以提取理论上的最大曲线:
# Overfitting theoretical maximal qini curve.
upev.qini_max_x # percentile
upev.qini_max_y
# "Practical" max curve.
upev.qini_pmax_x
upev.qini_pmax_y
# No sleeping dogs curve.
upev.qini_nosdmax_x
upev.qini_nosdmax_y
向上
up.train_results_
可以用来绘制训练数据上的qini
性能
5.2 TransformedOutcome类
up = TransformedOutcome(df, col_treatment='Treatment', col_outcome='Converted')
参数col_treatment
是指数据集df中区分是否是treatment group的字段,通过0/1二值区分,
col_outcome
相当于label字段,如有转化是1,无转化是0.
其中,stratify
参数比较重要,因为其会直接传递给sklearn.model_select .train_test_split
用来作为训练集/验证集的拆分
实例化步骤完成了几件事:
- 定义转换函数并转换结果
- 使用
train_test_split
分割数据 - 设置一个随机状态
- 定义一个untransform函数并使用它来定义一个用于超参数调优的评分函数
- 定义一些默认的超参数。
5.3 up.fit类
RandomizedSearchCV()
, GridSearchCV()
, 或者Regressor()
都可以传导给: up.randomized_search
, up.grid_search
, 或者up.fit
up.fit(max_depth=2, nthread=-1)
举个换模型的例子:
up = TransformedOutcome(df, col_treatment='Test', col_outcome='Converted', sklearn_model=RandomForestRegressor)
grid_search_params = {
'estimator': RandomForestRegressor(),
'param_grid': {'min_samples_split': [2,3,5,10,30,100,300,1000,3000,10000]},
'verbose': True,
'n_jobs': 35,
}
up.grid_search(**grid_search_params)
当然,肯定更倾向于使用xgboost,因为它往往能快速提供良好的结果,同时还允许选择自定义目标函数。
这种可扩展性允许在每个叶子中考虑到P(W=1)的目标函数的可能性。
5.4 模型保存
跟sklearn一样,self.model_final.to_pickle(PATH)
参考文献
1 【营销增益模型实战-Uplift Model原理及应用】
2 Pylift: A Fast Python Package for Uplift Modeling
今天的文章智能营销增益(Uplift Modeling)模型——pylift库的使用(二)分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/5724.html