返回介绍

5.8 案例:基于AdaBoost的营销响应预测

发布于 2024-01-27 22:54:28 字数 26394 浏览 0 评论 0 收藏 0

5.8.1 案例背景

该案例介绍了有关会员营销预测的实际应用。会员部门在做会员营销时,希望能通过数据预测在下一次营销活动时,响应活动会员的具体名单和响应概率,以此来制定针对性的营销策略。

本案例是一个常规性应用模型,业务部门希望通过建立好的模型周期性自动执行,也满足营销需要。同时,业务部门还希望基于现有的辅助决策平台将会员数据筛选和查看功能跟该模型结合起来应用。

本节案例的输入源数据order.xlsx和源代码chapter5_code2.py位于“附件-chapter5”中,默认工作目录为“附件-chapter5”(如果不是,请cd切换到该目录下,否则会报“IOError:File order.xlsx does not exist”)。程序的输出预测数据写入本地文件order_predict_result.xlsx。

5.8.2 案例主要应用技术

本案例用到的主要技术包括:

数据预处理:二值化标志转换OneHotEncoder、基于方差分析的特征选择的数据降维SelectPercentile结合f_classif。

数据建模:管道方法Pipe、交叉检验cross_val_score配合StratifiedKFold和自定义得分计算方法、集成分类算法AdaBoostClassifier。

主要用到的库包括:time、Numpy、Pandas和Sklearn,其中Sklearn是数据建模的核心库。

本案例的技术应用重点是通过管道方法将多个数据处理环节结合起来,形成处理管道对象,然后针对该对象做交叉检验并得到不同参数下的检验结果,辅助于参数值设置。

5.8.3 案例数据

案例数据位于order.xlsx中,包括2个sheet:sheet1为本次案例的训练集,sheet2为本次案例的预测集。以下是数据概况:

特征变量数:13。

数据记录数:sheet1中的训练集数据记录数为39999,sheet2中的预测集数据记录数为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:性别,值域为1或0。

blue_money:历史订单的蓝券用券订单金额(优惠券的一种),整数型变量。

red_money:历史订单的红券用券订单金额(优惠券的一种),整数型变量。

work_hours:工作时间长度,整数型变量。

region:地区,分类型变量,值域[1,41]。

目标变量response,1代表用户有响应,0代表用户未响应。

5.8.4 案例过程

步骤1 导入库。

import time  # 导入自带时间库
import numpy as np  # numpy库
import pandas as pd  # pandas库
from sklearn.preprocessing import OneHotEncoder  # 导入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  # 准确率指标

本案例中用到的库相对较多,主要包括time时间库用来记录不同算法参数下模型的运行时间;numpy和pandas是常用的数据读取、展示、处理和数据保存库;在sklearn中用到以下库:

OneHotEncoder:将分类变量和顺序变量转换为二值化标志变量。

StratifiedKFold,cross_val_score:用来做交叉检验,前者用来将数据分为训练集和测试集;后者用来交叉检验。这里选择的StratifiedKFold能够有效结合分类样本标签做数据集分割,而不是完全的随机选择和分割。这种方式在应对分类样本不均衡时尤为有效。

SelectPercentile,f_classif:前者用来做特征选择的数量控制,后者用来确定特征选择的得分计算标准。

AdaBoostClassifier是集成算法,用来做分类模型训练。

Pipeline:是一个“管道”,目的是将不同的环节结合起来应用,这常用于多个流程和环节的反复性操作。例如本案例中,将特征选择和集成算法结合起来形成一个“管道”对象,然后针对该对象训练不同参数下对应交叉检验的结果。

accuracy_score:准确率评估指标,用于分类算法。

除了上述导入库或包以外,还有关于使用pandas写Excel需要的附属库。Pandas支持直接将数据框写入Excel,支持xls(97~2003)和xlsx(2007以上)两种格式,前者需要xlwt库支持,后者需要openpyxl支持。如果读者之前没有安装过这两个库,需要在系统终端命令行窗口使用pip install xlwt和pip install openpyxl完成安装。这两个库无需导入,在Pandas写入Excel时会自动调用。

步骤2 基本状态审查,包括基本状态查看、缺失值审查、类样本均衡审查等。

基本状态查看,用于查看数据集的记录数、维度数、前2条数据、描述性统计和数据类型等。

def set_summary(df):
    '''
    查看数据集的记录数、维度数、前2条数据、描述性统计和数据类型
    :param 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))  # 打印前2条数据
    print ('-' * 30)
    print ('Data DESC')
    print (df.describe())  # 打印数据基本描述性信息
    print ('Data Dtypes')
    print (df.dtypes)  # 打印数据类型
    print ('-' * 60)

由于函数功能非常简单,因此不做详细描述,主要用法如下:

在format函数中,df的形状由于只记录训练集X的特征数量,因此最后会减去1。

head方法用来显示指定数量(N)的前N条数据。

describe方法用来显示数据最小值、均值、标准差、25%、50%、75%分位数数据以及最大值。

dtypes显示当前所有列的数据类型,输出结果可以用来辅助判断接下来要做的数据转换、处理等操作对数据类型的要求。

缺失值审查,用来查看数据集的缺失数据列、行记录数。

def na_summary(df):
    '''
    查看数据集的缺失数据列、行记录数
    :param df: 数据框
    :return: 无
    '''
    na_cols = df.isnull().any(axis=0)  # 每一列是否具有缺失值
    print ('NA Cols:')
    print (na_cols)  # 查看具有缺失值的列
    print ('-' * 30)
    print ('valid records for each Cols:')
    print (df.count())  # 查看每一列有效值(非NA)的记录数
    print ('-' * 30)
    na_lines = df.isnull().any(axis=1)  # 查看每一行是否具有缺失值
    print ('Total number of NA lines is: {0}'.format(na_lines.sum()))  # 查看具有缺失值的行总记录数
    print ('-' * 30)

本部分有以下几个重点方法:

使用df.isnull().any()判断指定轴是否含有缺失值,当设置axis=0时以列为判断依据,当设置axis=0时以行为判断依据。

使用df.count()统计每个特征有效值(非NA)的记录数,通过该方法可以获知每个特征缺失数据的情况是否严重。

由于na_lines返回的是一个包含True/False的列表,因此可以使用sum()方法统计其中缺失值的数量,统计时缺失值(True)会当1做统计,而False会作为0参与计算。

类样本均衡审查,查看每个类的样本量分布。如果样本分布不均衡则需要做样本均衡处理。有关样本均衡的具体方法请参见3.4节。

def label_summary(df):
    '''
    查看每个类的样本量分布
    :param df: 数据框
    :return: 无
    '''
    print ('Labesl samples count:')
    print (df['value_level'].groupby(df['response']).count())  # 以response为分类汇总维度对value_level列计数统计
    print ('-' * 60)

这里直接使用数据框的groupby方法以response(目标变量)为维度对value_level做计数统计。

步骤3 数据预处理。

经过步骤2的基本审查,对于数据的状态已经了然于心,在该步骤中需要做相应的数据预处理。该部分包含多个函数。

第一个函数变量类型转换,用来将分类和顺序变量数据类型转换为整数型。为什么要转换为整数型,而不是其他类型,例如字符串等?这是因为在接下来的数据变换过程中,我们会使用在3.2节中介绍的OneHotEncoder方法,该方法要求分类变量和顺序变量的值都必须是整数,如果是字符串等其他类型将报错。如果原始数据变量中存在字符型值,例如性别是M/F,那么需要先在该步骤中对这些值做替换即可。

def type_con(df):
    '''
    转换目标列的数据为特定数据类型
    :param df: 数据框
    :return: 类型转换后的数据框
    '''
    var_list = {'edu': 'int32',
                'user_level': 'int32',
                'industry': 'int32',
                'value_level': 'int32',
                'act_level': 'int32',
                'sex': 'int32',
                'region': 'int32'
                }  # 字典:定义要转换的列及其数据类型
    for var, type in var_list.iteritems():  # 循环读出列名和对应的数据类型
        df[var] = df[var].astype(type)  # 数据类型转换
    print ('Data Dtypes')
    print (df.dtypes)  # 打印数据类型
    print ('-' * 30)
    return df

在功能实现中,先定义了一个字典,字典的Key和Value分别是变量特征名和对应的数据类型,这里数值类型定义为int32;接着使用for循环读出字典的可迭代对象iteritems属性值,每次读出的属性值是包括Key和Value的键值对;然后应用astype对特征变量类型对转换,最后打印输出转换后的数据框的数据类型。

第二个函数是NA值替换,用来将不同列的缺失值替换为指定数值。我们在3.1节讲过很多种缺失值替换方法,这里使用的是根据每一列自动指定缺失值的方法。

def na_replace(df):
    '''
    将数据集中的NA值使用自定义方法替换
    :param df: 数据框
    :return: NA值替换后的数据框
    '''
    na_rules = {'age': df['age'].mean(),
                'total_pageviews': df['total_pageviews'].mean(),
                'edu': df['edu'].median(),
                'edu_ages': df['edu_ages'].median(),
                'user_level': df['user_level'].median(),
                'industry': df['user_level'].median(),
                'act_level': df['act_level'].median(),
                'sex': df['sex'].median(),
                'red_money': df['red_money'].mean(),
                'region': df['region'].median()
                }  # 字典:定义各个列数据转换方法
    df = df.fillna(na_rules)  # 使用指定方法填充缺失值
    print ('Check NA exists:')
    print (df.isnull().any().sum())  # 查找是否还有缺失值
    print ('-' * 30)
    return df

功能实现过程中,先定义一个包括变量维度名称和对应的缺失值替换方法的字典,这里主要用到了均值mean和中位数median,其中均值用于数值型变量,中位数用于字符串变量;接着用df.fillna方法使用自定义的每列的不同方法批量替换缺失值;然后查看数据框中是否还存在缺失值并打印输出。

第三个函数做二值化的标志转换,主要用于将分类变量和顺序变量转换为二值化的标志0和1为值域的变量。在转换过程中,由于涉及训练集(及测试集)和预测集两种状态,训练集需要使用转换对象的fit方法训练,然后使用训练好的模型分别对训练集和预测集做转换,因此需要区分不同阶段。

def symbol_con(df, enc_object=None, train=True):
    '''
    将分类和顺序变量转换为二值化的标志变量
    :param df: 数据框
    :param enc_transform: sklearn的标志转换对象,训练阶段设置默认值为None;预测阶段使用从训练阶段获得的转换对象
    :param train: 是否为训练阶段的判断状态,训练阶段为True,预测阶段为False
    :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  # 设置不做标志转换的列
    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_org))  # 将未转换的数据与转换后的数据合并
        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

该函数的参数除了数据框外,还有两个参数:

enc_transform:Sklearn的标志转换对象,该对象只有应用fit方法之后才能形成,因此训练阶段为空;在预测集应用阶段,直接使用该参数传入训练集时的对象即可。

train:是否为训练阶段的判断状态,训练阶段为True,预测阶段为False。根据不同的状态判断是否需要做fit,并返回不同的参数对象。

在实现过程中:

先通过convert_cols定义要转换的特征变量名称列表,然后形成数据框df_con。

再使用df_org定义出无需转换的数据矩阵(数据框的值,使用values方法获取),df_org是一个Numpy矩阵而非Pandas数据框,接下来我们要使用Numpy方法做数据矩阵合并,原因是使用OneHotEncoder转换后的对象也是Numpy矩阵,这样就不需要再做转换了。

接下来要根据train的状态做判断,当状态为True时,先使用OneHotEncoder方法建立转化对象enc,然后应用fit方法做训练,再使用transform方法做转换并将结果使用toarray方法转换为数组并赋值给df_con_new。这里将fit和transform方法分开操作目的是enc对象要在fit之后传给预测集使用,如果直接使用fit_transform则无法实现。最后通过numpy的hstack方法将两个矩阵合并。当状态为False时,直接使用从训练阶段获得的对象enc做transform,然后将结果合并返回。

相关知识点:使用Numpy的hstack和vstack做矩阵合并

在之前的章节中我们使用了Pandas的merge和contact方法将多个数据框合并。在本节中使用了Numpy的hstack做矩阵合并。hstack是将矩阵以列为单位做合并,与之相对应的是以行为单位做合并的方法vstack,这两个方法的参数都是一个元组。

假如b=a=np.arange(6).reshape(2,3),a和b都是2行3列矩阵:

当使用numpy.hstack((a,b))做列合并时,合并后的矩阵是2行6列,此时该方法等价于numpy.concatenate((a,b),axis=1)

当使用numpy.vstack((a,b))做行合并时,合并后的矩阵是4行3列,此时该方法等价于numpy.concatenate((a,b),axis=0)

由于需要对数据转换的方式保持一致,因此只能使用一次fit方法,分别对训练集和预测集做转换。在应用fit方法时,是针对训练数据进行的,这就要求训练集的数据值分布要比较完整,这样预测集应用才有效。例如,假如变量X1的训练集值域是1/2/3,预测集的值域是1/2,那么使用测试集的fit方法形成的对象应用到预测集做转换没有问题;但如果变量X的训练集值域是1/2,预测集的值域是1/2/3,由于应用fit方法时没有3这个值域分布,从而会导致应用到预测值时无法转换出来(简单点说就是训练集中没有这个值域,因此转换模式超过训练范畴)。

第四个函数是获得最佳模型参数,用于按照指定的参数训练方法得到每次交叉检验不同评估指标的分类模型结果。交叉检验是做模型效果评估的最佳方法,分类模型中一般都会使用该方法。在该函数中,我们通过交叉检验的方法,自定义了几种不同的检验指标,然后对模型的不同参数做训练并得到不同评估指标的结果,通过每次的输出结果以及运行时间的评估,从中找到比较合适的参数。

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_esti-mators)  # 将均值矩阵转换为数据框
    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

在该函数的实现过程中,先要做数据降维,目的是降低数据计算量。这里使用SelectPercentile方法结合f_classif评估选择特征最明显的50%数量的特征。SelectPercentile方法用来按照指定方法选择特定数量或比例的特征变量,f_classif是计算数据集的方差P值方法。这两种方法结合起来后形成的transform模型对象,该对象目前没有做任何操作,只是一个空对象而已。

这里的数据降维使用了特征选择方法,而不是像PCA、LDA等方法做数据转换。在实际应用过程中,由于考虑到业务方可能对结果的特征重要性要做分析,因此这里不使用转换方法。除了f_classif方法外,sklearn.feature_selection这对分类模型还提供了chi2、f_classif、f_regression、mutual_info_classif、mutual_info_regression等评估指标用于分类和回归检验。而SelectPercentile方法能够按照上述指定的评分方法对所有特征变量做评估,然后选择设置好的特征数量或比例。

然后通过AdaBoostClassifier方法建立分类模型对象,这两个模型对象要作为下面“管道”的两个步骤。

相关知识点:使用集成算法做模型训练

AdaBoost是一种非常流行的集成算法,其核心思想是针对同一个训练集训练不同的分类器(弱分类器),然后把这些弱分类器集合起来,构成一个强分类器。sklearn提供两类集成算法:

第一类是使用平均方法的集成算法,这类算法的基本原理是构建一些小的模型器然后基于每个模型器的结果做均值计算得到最终结果。这种组合方法单个模型的效果更好,因为其方差小。典型代表是Bagging方法和Random Forest。

第二类是使用提升方法的集成算法,在这种集成方法中,每个模型器顺序参与模型评估,并试图降低组合模型器的偏差。这是一种组合多个模型器来形成强大模型器的有效方法。典型代表是AdaBoost、Gradient Tree Boosting等。

集成算法相对于单个算法的显而易见的好处是其准确率较高,且鲁棒性强。我们在“4.2回归分析”中使用过GradientBoostingRegressor做回归预测,已经见识过它的效果。

接着使用Pipeline方法建立一个“管道”,目的是将上述两个过程(特征选择和分类算法)合并起来做交叉检验。该步骤实际上简化了交叉检验的过程,后续只需要对管道对象做参数设置、训练和预测即可,而无需分别配置特征选择和分类训练过程。

相关知识点:使用Pipeline构建组合评估器

sklearn中的Pipeline(翻译为管道)是一个复合评估器,用来将多个具有上下逻辑环节的过程连接起来形成一个复合对象。

在数据处理和计算算法过程中,经常会用到数据标准化、数据转换、特征降维等方法,然后再对处理后的数据集做模型训练和评估,这个过程可以使用pipeline(例如sklearn.pipeline.Pipeline)形成具有序列步骤的组合评估器。

Pipeline(steps)只有一个参数“steps”,该参数是一个由名称和模型对象组成的元组的列表。这在这个列表中,不同的元组之间是有明确的先后关系,并且最后一个元组一定是一个评估算法。

当使用pipe组合对象时,可应用的方法包括fit、predict、fit_predict、fit_transform等。当对其使用predict(以及包含predict的其他方法,例如predict_proba、fit_predict等)会自动调用最后一个评估算法的predict方法做预测。

Pipeline(steps)方法本身是用来为交叉检验提供算法对象不同参数下的效果,进而得到最优模型效果参数的方法。

由于本案例中使用了很多新的用法,因此可能涉及的相关知识点有点多,对案例内容的连贯性产生影响。接着回到本书内容,使用StratifiedKFold方法设置5次交叉检验使用的训练集和测试集;n_estimators是Adaboost的n_estimators参数用到的值列表,score_methods是交叉检验指标列表,有关更多自定义交叉检验的方法请参照4.2.6节中的表4-3;然后分别建立mean_list和std_list用于用于存放不同参数方法、交叉检验评估指标的均值和标准差。

接下来进入到循环阶段。这里的训练包括2层逻辑,外层逻辑是按照不同的n_estimators参数值做算法交叉检验,内层逻辑是按照不同的交叉检验指标做交叉检验。

外层循环中,使用for循环遍历每个n_estimators参数,使用t1记录一个该参数下开始的时间戳,用来记录不同参数对应的算法运行时间;然后定义一个score_list空列表,用于记录每次n_estimators参数下的算法交叉检验结果。

内层循环中,使用for循环遍历score_methods中的每个交叉检验得分评估方法,设置组合模型器(“管道”)对象model_pipe的参数,通过步骤名称+__(两个下划线)+参数对象的方法设置,这里的步骤名称是在建立管道是定义的字符串名称,__(两个下划线用来表示连接对应步骤对象的参数),后面接具体参数的名称,例如model_adaboost__n_estimators=parameter的意思是设置model_adaboost的n_estimators参数的值为parameter(parameter为从外层循环读取的值)。接着使用cross_val_score方法建立交叉检验过程,分别设置模型对象为管道,数据集X和y以及交叉检验方法评分方法和交叉检验方法cv。最后将得到的结果使用append追加到score_list列表,获得每个参数下的评估结果。

在内层循环结束后,将每个参数下交叉检验详细数据转换为矩阵score_matrix,目的是便于接下来基于不同的交叉检验指标计算均值和标准差,并格式化为Pandas数据框做输出。然后分别基于score_matrix的行(每行代表一个指标,每列代表一次交叉检验结果)计算均值和标准差。接着使用Pandas的concat方法做Pandas对象合并,得到一个包含原始详细交叉检验指标数据和均值、标准差的总体数据集。最后,将每个参数下的均值和标准差数据使用append方法追加到mean_list和std_list,便于评估不同参数的效果;使用round方法打印输出保留两位小数的数据框,并得到最后时间戳并输出时间间隔。

步骤4 数据应用。

上面定义完各种功能函数后,本部分开始正式应用。

# 加载数据集
raw_data = pd.read_excel('order.xlsx', sheetname=0)  # 读出Excel的第一个sheet
X = raw_data.drop('response', axis=1)  # 分割X
y = raw_data['response']  # 分割y

使用Pandas的read_excel方法读取第一个sheet数据,由于从Excel读取数据会比较慢,这里可能稍微要等一点时间(大概5秒钟),然后分割出X和y用于做后续处理。

# 数据审查和预处理
set_summary(raw_data) # 基本状态查看

基本状态查看结果如下:

Data Overview
Records: 39999 Dimension13
------------------------------
    age  total_pageviews  edu  edu_ages  user_level  industry  value_level  \
0  39.0          77516.0  1.0      13.0         1.0       1.0            1   
1  50.0          83311.0  1.0      13.0         2.0       2.0            2   
   act_level  sex  blue_money  red_money  work_hours  region  response  
0        1.0  1.0        2174        0.0          40     1.0         0  
1        1.0  1.0           0        0.0          13     1.0         0  
------------------------------
Data DESC
                age  total_pageviews           edu      edu_ages  \
count  39998.000000     3.999800e+04  39998.000000  39998.000000   
mean      38.589654     1.895136e+05      2.511626     10.076754   
std       13.663490     1.053109e+05      1.638110      2.573384   
min       17.000000     1.228500e+04      1.000000      1.000000   
25%       28.000000     1.175282e+05      2.000000      9.000000   
50%       37.000000     1.783410e+05      2.000000     10.000000   
75%       48.000000     2.372685e+05      2.000000     12.000000   
max       90.000000     1.484705e+06     10.000000     16.000000
......
Data Dtypes
age                float64
total_pageviews    float64
edu                float64
edu_ages           float64
user_level         float64
industry           float64
value_level          int64
act_level          float64
sex                float64
blue_money           int64
red_money          float64
work_hours           int64
region             float64
response             int64
dtype: object

上述结果中的特征数量、数据记录数、前2条数据样本查看以及数值型数据的描述性统计结果都比较简单。这里重点说下Dtypes输出,从输出结果可以看到,要做分类的特征包括edu、user_level、industry、act_level、sex、region都会识别为浮点型,而后面要做的二值化标志转换需要转换为int型,这里的结果为下面做转换提供了参考依据。

na_summary(raw_data) # 缺失值审查

以下是缺失值审查结果:

NA Cols:
age                 True
total_pageviews     True
edu                 True
edu_ages            True
user_level          True
industry            True
value_level        False
act_level           True
sex                 True
blue_money         False
red_money           True
work_hours         False
region              True
response           False
dtype: bool
------------------------------
valid records for each Cols:
age                39998
total_pageviews    39998
edu                39998
edu_ages           39998
user_level         39998
industry           39997
value_level        39999
act_level          39998
sex                39998
blue_money         39999
red_money          39998
work_hours         39999
region             39997
response           39999
dtype: int64
------------------------------
Total number of NA lines is: 12

从NA Cols的结果可以看到除了value_level、blue_money和work_hours外,其他特征都有缺失值;从valid records for each Cols结果中分析发现,这些列的缺失值不多,完整数据记录是39999条而数据记录缺失最多的特征也只有2条而已;从Total number of NA lines is得到总的具有缺失值的记录是12条,由此我们判断缺失值情况不严重。

label_summary(raw_data) # 类样本均衡审查

以下是样本均衡审查结果:

Labesl samples count:
response
0    30415
1     9584
Name: value_level, dtype: int64

从结果中发现,二分类的样本分布相对均匀,没有出现量级上的巨大差异,因此无需做类样本均衡处理。

接下来先做缺失值(NA)替换,为什么要先做替换而不是先做数据类型转换?这是因为NA数据无法转换为整数型,转换必须针对非NA数据才行。

X_t1 = na_replace(X) # 替换缺失值

缺失值替换之后,检查含有NA值的样本数量为0。

Check NA exists:
0

接着做数据类型转换。

X_t2 = type_con(X_t1) # 数据类型转换

数据类型转换之后的结果符合预期:

Data Dtypes
age                float64
total_pageviews    float64
edu                  int32
edu_ages           float64
user_level           int32
industry             int32
value_level          int32
act_level            int32
sex                  int32
blue_money           int64
red_money          float64
work_hours           int64
region               int32
dtype: object

标志转换没有打印输出结果,只有功能项。读者可以自行通过X.shape和X_new.shape打印输出发现原始数据集X和转换后的数据集X_new的特征变量数量分别为13和92。

X_new,enc = symbol_con(X_t2,enc_object=None,train=True) # 将分类和顺序数据转换为标志

下面是分类模型训练过程。

# 分类模型训练
transform = get_best_model(X_new, y)  # 获得最佳分类模型参数信息
transform.fit(X_new, y)  # 应用特征选择对象选择要参与建模的特征变量
X_final = transform.transform(X_new)  # 获得具有显着性特征的特征变量
final_model = AdaBoostClassifier(n_estimators=100)  # 从打印的参数均值和标准差信息中确定参数并建立分类模型对象
final_model.fit(X_final, y)  # 训练模型

首先通过get_best_model获得最佳分类模型参数信息并返回转换对象,该过程会非常耗时,原因是我们在设置不同的算法参数时的同时又设置了不同的交叉检验指标。笔者的工作环境下大概需要3分钟的时间完成该步骤。该步骤输出每个参数下的数据示例如下:

set parameters: 100
              0     1     2     3     4  mean   std
accuracy   0.86  0.86  0.87  0.87  0.86  0.86  0.00
f1         0.67  0.67  0.69  0.70  0.69  0.68  0.01
precision  0.76  0.77  0.78  0.79  0.76  0.77  0.01
recall     0.60  0.60  0.61  0.63  0.62  0.61  0.01
roc_auc    0.92  0.92  0.92  0.92  0.92  0.92  0.00
------------------------------------------------------------
time: 110.470000029

当设置n_estimators=100时,意味着Adaboost会使用的最大单一评估模型(小模型)的数量为100个。该步骤耗时将近2分钟。

这里设置的n_estimators的值指的是会使用小模型器的最大数量,这意味着当模型结果已经足够好时,将不会使用更多的模型器,因此当继续加大该参数的数量时,可能会无法产生更好的结果。

然后对比不同参数下的各交叉检验的均值和标准差,结果如下:

Mean values for each parameter:
                20        50        80        100
accuracy   0.853946  0.859922  0.862647  0.863672
f1         0.656329  0.672173  0.679723  0.682772
precision  0.753126  0.764994  0.770094  0.771560
recall     0.582011  0.599542  0.608411  0.612376
roc_auc    0.908324  0.914994  0.918613  0.919941
Std values for each parameter:
                20        50        80        100
accuracy   0.005260  0.004636  0.004463  0.004834
f1         0.009512  0.012836  0.011527  0.011929
precision  0.023295  0.010208  0.011250  0.012753
recall     0.013767  0.016128  0.013591  0.013184
roc_auc    0.003201  0.002865  0.002629  0.002652

但是对比各方面的评估指标,我们发现参数设置为80时的结果相对于100基本一致(其实小数点后面还有更多的位数能够体现出更多小模型器的结果会更好,但这种效果提升已经不明显)。

然后使用tranform对象对数据集X_new做特征选择得到最终建模特征数据X_final,最后使用AdaBoostClassifier模型以及最佳参数80做算法训练。

经过上述步骤,我们完成了模型训练并确定了最佳模型Adaboost的参数,接下来对新数据集做预测。

# 新数据集做预测
new_data = pd.read_excel('order.xlsx', sheetname=1)  # 读取要预测的数据集
final_reponse = new_data['final_response']  # 获取最终的目标变量值
new_data = new_data.drop('final_response', axis=1)  # 获得预测的输入变量X
set_summary(new_data)  # 基本状态查看
na_summary(new_data)  # 缺失值审查
new_X_t1 = na_replace(new_data)  # 替换缺失值
new_X_t2 = type_con(new_X_t1)  # 数据类型转换
new_X_t3 = symbol_con(new_X_t2, enc_object=enc, train=False)  # 将分类和顺序数据转换为标志
new_X_final = transform.transform(new_X_t3)  # 对数据集做特征选择

先使用跟训练集相同的方法做数据读取,这里通过sheetname=1设置读取第二个sheet。

使用read_excel方法读取Excel时,可以通过参数sheetname定义要读取哪个sheet的数据,其值可以定义为4种,数值、字符串、数值和字符串的混合列表以及Noen。其中数值代表sheet的索引值,0代表第一张sheet;字符串代表要读取的sheet名称;数值和字符串的混合允许将索引值和sheet名混合起来应用,例如[0,1,"Sheet5"];而None则默认读取第1张sheet的内容。

由于上述过程跟训练集相同,在此不作赘述。其中一个final_reponse变量的用途会在下文讲到。

接下来做预测。

# 输出预测值以及预测概率
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))  # 打印前2条结果
print ('-' * 60)

先使用final_model(训练后的AdaBoostClassifier模型对象)的predict方法做分类预测,得到的结果转换为pandas数据框;然后读取final_model的predict_proba属性获得其预测为正例和负例的概率值并转换为数据框,最后将原始数据预测集、预测标签、预测概率数据框按列合并,打印前2条数据,如下所示:

Predict info
   age  total_pageviews  edu  edu_ages  user_level  industry  value_level  \
0   61           243019   10         1         2.0       7.0            2   
1   33           215596    4         5         2.0       7.0            2   
   act_level  sex  blue_money  red_money  work_hours  region  labels  \
0          1    1           0          0          40     1.0       0   
1          5    1           0          0          40     6.0       0   
       pro1      pro2  
0  0.504053  0.495947  
1  0.507486  0.492514  

预测得到的最终结果,我们写到一个Excel中便于业务做对照和进一步整理分析。

# 将预测结果写入Excel
writer = pd.ExcelWriter('order_predict_result.xlsx')  # 创建写入文件对象
predict_pd.to_excel(writer, 'Sheet1')  # 将数据写入sheet1
writer.save()  # 保存文件

先使用pandas的ExcelWriter方法创建一个写入文件,名称为order_predict_result.xlsx;然后直接使用目标数据框predict_pd的to_excel方法对文件对象写入数据到sheet1中,最后保存。

使用pandas的写入Excel功能时,需要先根据写入Excel扩展名的不同安装xlwt或openpyxl。具体参照本节开始的导入库部分的介绍。

后续与实际效果的比较:

由于本案例在运营活动结束后统计了最终结果,其值位于final_reponse中,笔者将预测结果与实际结果进行了对比:

print ('final accuracy: {0}'.format(accuracy_score(final_reponse, predict_labels)))

输出结果为:final accuracy:0.862490105168。准确率跟做交叉检验时非常一致,这也说明了该模型的鲁棒性非常强。

5.8.5 案例数据结论

由于本案例是一个预测性质的应用,其结果直接给到业务部门,因此没有分析型的数据结论。从最终的预测结果与真实应用结果的对照来看,业务部门对86.2%的准确率表示基本满意,符合预期。

5.8.6 案例应用和部署

本案例的结果给到业务部门后,前期业务部门有以下动作:

制定了营销响应率不低于80%的KPI作为本次营销活动的绩效考核目标。

针对80%的基准线结合已经产生订单的历史数据算出了相应订单金额、订单数量等基本数据,作为本次活动的预期收益;同时跟此次活动的预算相结合,制定了ROI目标(但不作为考核)。

基于预期的订单金额和订单数量,以及关联的用券数量和金额,向公司申请了对应的优惠券用于促销用户购买转化。

由于该模型是一个常规性应用,基本上是每月一次的频率,而在应用过程中一般没有特殊参数调整或算法变动。因此,笔者将该模型经过优化并配合其他部门做调整后,由后端部门将其封装为固定程序执行。主要调整内容包括:

数据源从Excel调整为从MySQL数据库直接取数,有关MySQL取数的具体方法请参照2.2.3节的内容。

数据源的周期,默认取最近1年的数据,数据根据不同取数时间自动滚动。

预测集由于是变动的,需要业务部门每次根据不同的情况选择特定的会员列表。会员部门已经可以通过辅助决策平台自助做会员筛选,然后将选择列表加入到模型中作为预测集。

模型的运行以业务部门的触发为条件,当业务部门选择会员列表加入模型对象后便触发执行操作,而非自动执行。

模型由于在服务器上执行,因此数据结果生成后直接Excel链接的形成提供给会员部门,会员部门可直接下载。

在从数据库取出数据后,跟DBA(数据主管人员)反复沟通了关于数据基本状态的问题,然后针对性地优化了关于数据查缺补全的策略,包括空值、异常值、缺失值等的数据清洗操作和步骤。

经过多次测试,在最耗时的“获得最佳模型参数”阶段已经基本确认了最佳参数,无需每次运行,因此该步骤在后期已经省略,直接应用模型和最佳参数。

5.8.7 案例注意点

本案例中有以下几个点需要读者重点关注:

由于涉及二值化标志转换的问题,如果训练集比较小,无法完全覆盖完整的值域分布,那么笨案例中的二值化转化将无法应用。

本案例中的特征选择的数量是影响分类结果评估准确与否的关键因素之一,特征数量越多最终分类准确率越高的可能性越大(还要取决于分类算法的可靠性),如图5-6所示是在digits数据集结合方差分析和SVM的管道在不同特征数量下的准确率对比。

图5-6 特征比例对于准确率的影响

Adaboost应用时小分类器的数量(n_estimators参数值)将严重影响模型计算时间,因此对于有时间性要求的场景和应用要谨慎设置该参数(调低数值)或选择其他更快的方法。

交叉检验的几个指标具有非常高的相关性,相关系数超过0.99,因此读者在实际应用中选择其中一个使用即可,无需多次“重复”证明。

5.8.8 案例引申思考

案例中使用的“管道”用来组合多个序列步骤,但实际上组合方法其实有更多用途,例如:

在构建模型过程中,假如希望使用多个算法协同完成工作,例如做分类时将SVM、随机森林、Adaboost组合起来使用,形成一个更强大的“组合模型”,此时也可以使用pipeline(例如sklearn.pipeline.make_pipeline)做组合。

在做数据降维时,我们可能希望使用多个方法将不同方法评估得到的特征组合起来来时,此时可以使用pipeline(例如pipeline.FeatureUnion)构建一个组合特征选择器,这是非常有效的一种方法。

总体上,基于特征数量(比例或绝对数量值)来选择特征的方法存在一定风险性,主要原因是无法确定多少特征为最佳数量。在这方面,使用PCA的方法的可靠性要好于特征选择方法,因此PCA这类方法可以基于解释方差的比例选择主成分的数量,而非固定主成分。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文