基于AdaBoost的营销响应预测
完整代码、数据见Github
1. 案例背景
会员部门在做会员营销时,希望通过数据预测下一次营销活动时,响应活动的会员名单和具体概率。
数据:
order.xlsx表: sheet1为训练集,sheet2为预测集
特征变量数:13
数据条数:训练集39999条,预测集8843条
NA值:有
异常值:有
13个特征变量:
age:年龄(整数型变量)
total_pageviews:总页面浏览量(整数型变量)
edu:教育程度(分类型变量,值域[1,10])
edu_ages:受教育年限(整数型变量)
user_level:用户等级(分类变量,值域[1,7])
industry:用户企业划分(分类型变量,值域[1,15])
value_level:用户价值度分类(分类型变量,值域[1,6])
act_level:用户活跃度分类(分类型变量,值域[1,5])
sex:性别(0或1)
blue_money:蓝券金额(整数型变量)
red_money:红券金额(整数型变量)
work_hours:工作时间长度(整数型变量)
region:地区(分类型变量,值域[1,41])
2. 案例技术
数据预处理:二值化标志转换 OneHotEncoder、基于方差分析的特征选择的数据降维 SelectPercentile 结合 f_classif
数据建模:管道方法 Pipe、交叉检验 cross_val_score 配合 StratifiedKFold 和自定义得分计算方法、集成分类算法 AdaBoostClassifier
主要用的库:time、Numpy、Pandas、Sklearn
技术应用重点:通过管道方法将多个数据处理环节结合起来,形成处理管道对象,针对对象做交叉检验得到不同参数下的检验结果
3. 案例过程
步骤1:导入库
""" OneHotEncoder:将分类变量和顺序变量转换为二值化标志变量 StratifiedKFold,cross_val_score:交叉验证,前者用来将数据分为训练集和测试集;后者用来交叉检验。 StratifiedkFold 能结合样本标签做数据集分割,而不是完全的随机选择和分割 SelectPercentile,f_classif:前者用来做特征选择的数量控制,后者用来确定特征选择的得分计算标准 AdaBoostClassifier:集成算法,用来做分类模型训练 Pipeline:将不同的环节结合起来(本案例中,将特征选择和集成算法结合起来形成一个”管道对象“,然后针对该对象 训练不同参数下交叉检验的结果) """ import time import numpy as np import pandas as pd from sklearn.preprocessing import OneHotEncoder # 导入二值化标志转化库 from sklearn.model_selection import StratifiedKFold, cross_val_score # 导入交叉检验算法 from sklearn.feature_selection import SelectPercentile, f_classif # 导入特征选择方法 from sklearn.ensemble import AdaBoostClassifier # 导入集成算法 from sklearn.pipeline import Pipeline # 导入Pipeline库 from sklearn.metrics import accuracy_score # 准确率指标
步骤2:数据基本审查(基本状态审查,缺失值审查,类样本均衡审查)
set_summary():
1. 在 format 函数中,df的形状由于记录训练集 X 的特征数量,因此最后减 1。
2. head():显示指定数量(N)的前N条数据。
3. describe():显示数据最小值,均值,标准差,25%,50%,75%分位数,最大值。
4. dtypes():显示所有列的数据类型。(为后面的数据类型转换做准备)
na_summary():
1. df.isnull().any():判断指定轴是否含有缺失值。
2. df.count():统计每个特征非NA记录数。
3. na_lines 返回一个包含 True/False 的列表,使用 sum() 方法统计缺失值的数量。
def set_summary(df): """ 查看数据 :return: """ print('Data Overview') print('Records: {0}\tDimension{1}'.format(df.shape[0], (df.shape[1]-1))) # 打印数据集 X 的形状 print('-' * 30) print(df.head(2)) # 打印前两条数据 print('-' * 30) print('Data DESC') print(df.describe()) # 打印数据描述信息 print('Data Dtypes') print(df.dtypes) # 打印数据类型 print('-' * 60) def na_summary(df): """ 查看数据的缺失 :param df: :return: """ na_cols = df.isnull().any(axis=0) # 每一列是否有缺失值 print('NA Cols:') print(na_cols) # 查看具有缺失值的列 print('valid records for each Cols:') print(df.count()) # 每一列非NA的记录数 na_lines = df.isnull().any(axis=1) # 每一行是否有缺失值 print('Total number of NA lines is: {0}'.format(na_lines.sum())) # 查看缺失值的行总记录数 print('-' * 30) def label_summary(df): """ 查看每个类的样本量分布 :param df: :return: """ print('Label sample count:') print(df['value_level'].groupby(df['respone']).count()) # 以response为分类汇总维度对value_level列计数统计 print('-' * 60)
步骤3:数据预处理
type_con():转换目标列的数据为特定数据类型
1. items():以列表返回可遍历的(键, 值) 元组数组。
2. DataFrame.astype():将特定的列数据类型转换为另一种数据类型。不仅如此,还可以使用 Python 字典一次更改多个列类型。字典中的键与列名相对应,字典中的值与希望列属于的新数据类型相对应。
def type_con(df): """ 转换目标列的数据为特定数据类型 :param df: :return: """ val_list = {'edu': 'int32', 'user_level': 'int32', 'industry': 'int32', 'value_level': 'int32', 'act_level': 'int32', 'sex': 'int32', 'region': 'int32'} # 字典:定义要转换的列及其数据类型(key:列名,velue:新数据类型) for var, type in val_list.items(): df[var] = df[var].astype(type) print('Data Dtypes:') print(df.dtypes) return df
na_replce():将NA值替换
def na_replace(df): """ 将NA值使用自定义方法得替换 :param df: :return: """ na_rules = {'age': df['age'].mean(), 'total_pageviews': df['total_pageviews'].mean(), 'edu': df['edu'].median(), # median():中值 'edu_ages': df['edu_ages'].median(), 'user_level': df['user_level'].median(), 'industry': df['industry'].median(), 'act_level': df['act_level'].median(), 'sex': df['sex'].median(), 'red_money': df['red_money'].median(), 'region': df['region'].median() } df = df.fillna(na_rules) # 使用指定方法填充缺失值 print('Check NA exists:') print(df.isnull().any().sum()) # 查看是否还有缺失值 return df
symbol_con():将分类和顺序变量转换为二值化的标志变量
该函数做二值化的标志转换,主要用于将分类变量和顺序变量转换为二值化变量,值域为 0 和 1。
在转换过程中,由于涉及训练集(及测试集)和预测集两种状态,训练集需要使用转换对象 fit 方法训练,然后使用训练好的模型分别对训练集和预测集做转换,因此需要区分不同阶段。
具体实现过程:
定义需转换的列形成数据框 df_con
定义无需转换的列形成数据矩阵(数据框的值,通过 values 获得) df_org
df_org 是 Numpy 矩阵而非 Pandas 数据框,接下来使用 Numpy 方法做数据矩阵合并,原因是 OneHotEncoder 转换后的对象也是 Numpy 矩阵
接下来根据 train 的状态做判断:
为 True 时,用 OneHotEncoder 方法建立转换对象 enc,然后用 fit 方法做训练,接着用 transform 方法做转换 (这里 fit 和 transform 方法分开操作是 enc 对象要在 fit 之后传给预测集使用) 最后将未转换的数据与转换后的数据合并。
为 False 时,直接用训练阶段获得的对象 enc 做 transform,然后将结果合并。
def symbol_con(df, enc_object=None, train=True): """ 将分类和顺序变量转换为二值化的标志变量 :param df: :param enc_object: :param train: :return: """ convert_cols = ['edu', 'user_level', 'industry', 'value_level', 'act_level', 'sex', 'region'] # 选择要做标志转换的列(教育程度,用户等级,用户企业划分,用户价制度划分,用户活跃度划分,性别,地区) df_con = df[convert_cols] # 选择要做标志转换的数据 df_org = df[['age', 'total_pageviews', 'edu_ages', 'blue_money', 'red_money', 'work_hours']].values # 设置不做标志转换的列(df.column.values:以array形式返回指定column的所有取值) if train == True: # 如果数据处于训练阶段 enc = OneHotEncoder() # 建立标志转换模型对象 enc.fit(df_con) # 训练模型 df_con_new = enc.transform(df_con).toarray() # 转换数据并输出为数组格式 new_matrix = np.hstack(df_con_new, df_con) # 将未转换的数据与转换后的数据合并 return new_matrix, enc else: df_con_new = enc_object.transform(df_con).toarray() # 使用训练阶段获得转换对象转换数据并输出数组格式 new_matrix = np.hstack((df_con_new, df_org)) # 将未转换的数据与转换的数据合并 return new_matrix
1. enc_transform:Sklearn 的标志转换对象,该对象只有应用 fit 方法后才形成,因此训练阶段为空;在预测集应用阶段,直接使用该参数传入训练集时的对象即可。
2. train:判断是否为训练阶段,训练阶段为 True,预测阶段为 False。根据不同的状态判断是否需要做 fit,并返回不同的参数对象。
步骤4:构建模型并获得最佳模型参数
get_best_model():获得最佳模型参数
在该函数中,通过交叉检验的方法,自定义几种不同的检验指标,从中找到比较合适的参数。
def get_best_model(X, y): """ 结合交叉检验得到不同参数下的分类模型结果 :param X: 输入X :param y: 输出y :return: 特征选择模型对象 """ transform = SelectPercentile(f_classif, percentile=50) # 使用f_classif方法选择特征最明显的50%数量的特征 model_adaboost = AdaBoostClassifier() # 建立AdaBoostClassifier模型对象 model_pipe = Pipeline(steps=[('ANOVA', transform), ('model_adaboost', model_adaboost)]) # 建立由特征选择和分类模型构成的“管道”对象 cv = StratifiedKFold(5) # 设置交叉验证次数 n_estimators = [20, 50, 80, 100] # 设置模型参数 score_methods = ['accuracy', 'f1', 'precision', 'recall', 'roc_auc'] # 设置交叉检验指标 mean_list = list() # 建立空列表用于存放不同参数方法、交叉检验评估指标的均值列表 std_list = list() # 建立空列表用于存放不同参数方法、交叉检验评估指标的标准差列表 for parameter in n_estimators: # 循环读出每个参数值 t1 = time.time() # 记录训练开始的时间 score_list = list() # 建立空列表用于存放不同交叉检验下各个评估指标的详细数据 print ('set parameters: %s' % parameter) # 打印当前模型使用的参数 for score_method in score_methods: # 循环读出每个交叉检验指标 model_pipe.set_params(model_adaboost__n_estimators=parameter) # 通过“管道”设置分类模型参数 score_tmp = cross_val_score(model_pipe, X, y, scoring=score_method, cv=cv) # 使用交叉检验计算指定指标的得分 score_list.append(score_tmp) # 将交叉检验得分存储到列表 score_matrix = pd.DataFrame(np.array(score_list), index=score_methods) # 将交叉检验详细数据转换为矩阵,每行代表一个指标,每列代表一次交叉检验结果。 score_mean = score_matrix.mean(axis=1).rename('mean') # 计算每个评估指标的均值 score_std = score_matrix.std(axis=1).rename('std') # 计算每个评估指标的标准差 score_pd = pd.concat([score_matrix, score_mean, score_std], axis=1) # 将原始详细数据和均值、标准差合并 mean_list.append(score_mean) # 将每个参数得到的各指标均值追加到列表 std_list.append(score_std) # 将每个参数得到的各指标标准差追加到列表 print (score_pd.round(2)) # 打印每个参数得到的交叉检验指标数据,只保留2位小数 print ('-' * 60) t2 = time.time() # 计算每个参数下算法用时 tt = t2 - t1 # 计算时间间隔 print ('time: %s' % str(tt)) # 打印时间间隔 mean_matrix = np.array(mean_list).T # 建立所有参数得到的交叉检验的均值矩阵 std_matrix = np.array(std_list).T # 建立所有参数得到的交叉检验的标准差矩阵 mean_pd = pd.DataFrame(mean_matrix, index=score_methods, columns=n_estimators) # 将均值矩阵转换为数据框 std_pd = pd.DataFrame(std_matrix, index=score_methods, columns=n_estimators) # 将均值标准差转换为数据框 print ('Mean values for each parameter:') print (mean_pd) # 打印输出均值矩阵 print ('Std values for each parameter:') print (std_pd) # 打印输出标准差矩阵 print ('-' * 60) return transform
1. 数据降维。
这里使用 SelectPercentile 方法结合 f_classif 评估选择特征最明显的 50% 数量的特征。
SelectPercentile:按照指定方法选择特定数量或比例的特征变量。
f_classif:计算数据集的方差。
这两种方法结合起来形成 transform 模型对象,该对象没任何操作,只是一个空对象而已。
注意:这里的数据降维使用了特征选择方法,而不是 PCA、LDA 等方法。是因为在实际应用过程中,考虑业务方会对结果的特征重要性做分析。
2. 使用集成算法做模型训练。
AdaBoost 集成算法。
3. 使用 Pipeline 构建组合评估器
Pipeline 是一个复合评估器,将多个具有上下逻辑环节的过程连接起来形成一个符合对象。上述代码就是将特征选择和分类算法合并起来做交叉检验。
上述代码中
使用 StratifiedKFold 方法设置5次交叉检验使用的训练集和测试集;
n_estimators 是 Adaboost 的 n_estimators 参数用到的值列表,即弱学习器的个数;
score_methods 是评估分类好坏的指标,
上述代码的循环阶段
这里的训练包括两层逻辑,外层逻辑按照不同的 n_estimators 参数值做交叉检验,内层逻辑按照不同的检验指标(score_methods)做交叉检验。
外层循环中,遍历 n_estimators 参数,使用 t1 记录每个参数开始的时间戳,用来记录不同参数对应的算法运行时间;然后定义一个 score_list 空列表,用于记录每次 n_estimators 参数下的算法交叉检验结果。
内层循环中,遍历 score_methods 中每个评估方法,设置“管道”对象 model_pipe 的参数,
通过步骤名称+__(两个下划线)+参数对象的方法设置,这里的步骤名称是在建立管道时定义的字符串名称,__(两个下划线用来表示连接对应步骤对象的参数),后面接具体参数的名称,例如 model_adaboost__n_estimators=parameter 的意思是设置 model_adaboost的n_estimators 参数的值为 parameter
接着使用 cross_val_score 方法建立交叉检验过程,分别设置模型对象为管道,数据集 X 和 y 以及交叉检验评分方法和交叉检验方法 cv。最后将得到的结果使用 append 追加到 score_list 列表,获得每个参数下的评估结果。
score_matrix 矩阵,每行代表一个指标,每列代表一次交叉检验结果。
步骤5:导入数据
# 加载数据集 raw_data = pd.read_excel('order.xlsx', sheet_name=0) # 第一个sheet X = raw_data.drop('response', axis=1) # 分割X y = raw_data['response'] # 分割y
预测
predict_labels = pd.DataFrame(final_model.predict(new_X_final), columns=['labels']) # 获得预测标签 predict_labels_pro = pd.DataFrame(final_model.predict_proba(new_X_final), columns=['pro1', 'pro2']) # 获得预测概率 predict_pd = pd.concat((new_data, predict_labels, predict_labels_pro), axis=1) # 将预测标签、预测数据和原始数据X合并 print ('Predict info') print (predict_pd.head(2)) print ('-' * 60)
先使用 final_model(训练后的AdaBoostClassifier模型对象)的 predict 方法做分类预测,得到的结果转换为 pandas 数据框;然后读取 final_model 的 predict_proba 属性获取其预测为正例和负例的概率值并转换为数据框,最后将原始数据预测集、预测标签、预测概率数据框合并