基于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 属性获取其预测为正例和负例的概率值并转换为数据框,最后将原始数据预测集、预测标签、预测概率数据框合并

 

posted @ 2020-06-09 18:39  做梦当财神  阅读(551)  评论(0编辑  收藏  举报