Python数据分析与数据化运营:商品数据化运营6-案例:基于LogisticRegression、RandomForest、Bagging概率投票组合模型的异常检测

浏览: 2842

作者介绍:宋天龙(TonySong),资深大数据技术专家,历任软通动力集团大数据研究院数据总监、Webtrekk(德国最大的网站数据分析服务提供商)中国区技术和咨询负责人、国美在线大数据中心经理。


本文来自《Python数据分析与数据化运营》配套书籍第6章节内容,机械工业出版社华章授权发布,未经允许,禁止转载!

此书包含 50个数据工作流知识点,14个数据分析和挖掘主题,8个综合性运营案例。涵盖了会员、商品、流量、内容4大数据化运营主题,360°把脉运营问题并贴合数据场景落地。

书籍购买链接:https://item.jd.com/12254905.html

课程学习链接:网站数据分析场景和方法——效果预测、结论定义、数据探究和业务执行https://edu.hellobi.com/course/221


往期回顾:Python数据分析与数据化运营:会员数据化运营1-概述与关键指标 

                 Python数据分析与数据化运营:会员数据化运营2-应用场景与分析模型

                 Python数据分析与数据化运营:会员数据化运营3-分析小技巧

                 Python数据分析与数据化运营:会员数据化运营4-“大实话”

                 Python数据分析与数据化运营:会员数据化运营5-案例:基于RFM的用户价值度分析

                 Python数据分析与数据化运营:商品数据化运营1-概述与关键指标

                 Python数据分析与数据化运营:商品数据化运营2-应用场景与分析模型

                 Python数据分析与数据化运营:商品数据化运营3-分析小技巧

                 Python数据分析与数据化运营:商品数据化运营4-“大实话”

                 Python数据分析与数据化运营:商品数据化运营5-案例:基于超参数优化的Gradient Boosting的销售预测


6.8.1 案例背景

异常检测在之前介绍过,可以通过非监督式和非监督式两类算法实现。本案例是使用监督式算法中的分类算法实现的异常检测应用。

本节案例的输入数据new_abnormal_orders.csvabnormal_orders.txt源代码chapter6_code2.py位于“附件-chapter6”中,默认工作目录为“附件-chapter6”(如果不是,请cd切换到该目录下否则会“IOError: File new_abnormal_orders.csv does not exist”)。程序的输出预测数据直接打印输出,没有写入文件

6.8.2 案例主要应用技术

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

  • 基本预处理使用DictVectorizer字符串分类变量转换为数值型变量、使用SMOTE不均衡样本做过抽样处理
  • 数据建模:基于cross_val_score交叉检验、基于LogisticRegressionRandomForestBagging概率投票组合模型做分类

主要用到的库包括:numpypandassklearnimblearn其中sklearn数据建模的核心库。

本案例的技术应用重点部分

一部分是将原始数据集中的字符串分类转换为数值分类,便于参与建模运算;

一部分是通过概率投票方法,基于LogisticRegressionRandomForestBagging三个分类器建立一个组合分类器,用于实现组合投票和分类预测。

6.8.3 案例数据

案例数据某企业的部分订单,以下数据概况:

  • 特征变量数:13
  • 数据记录数:134190
  • 是否NA:有
  • 是否有异常值:

以下是本数据集13特征变量详细说

  • order_id:订单ID组合而成例如4283851335
  • order_date:订单日期,格式为YYYY-MM-DD例如2013-10-17
  • order_time:订单日期,格式为HH:MM:SS,例如12:54:44
  • cat:商品一级类别字符串,包含中文、英文。
  • attribution:商品所属的渠道来源字符串型,包含中文、英文。
  • pro_id:商品ID组合而成。
  • pro_brand:商品品牌,字符串,包含中文、英文。
  • total_money:商品销售金额,浮点型。
  • total_quantity:商品销售数量,整数型。
  • order_source:订单来源,从哪个渠道形成的销售,字符串型,包含中文、英文。
  • pay_type支付类型,字符串型,包含中文、英文
  • use_id用户ID由数字和字母组成的字符串。
  • city用户订单时的城市,字符串型,中文。

目标变量:abnormal_label代表该订单记录是异常订单。

6.8.4 案例过程

步骤1 导入

import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import numpy as np  # numpy库
import pandas as pd  # pandas库
from sklearn.feature_extraction import DictVectorizer  # 数值分类转整数分类库
from imblearn.over_sampling import SMOTE  # 过抽样处理库SMOTE
from sklearn.model_selection import StratifiedKFold, cross_val_score  # 导入交叉检验算法
from sklearn.linear_model import LogisticRegression  # 导入逻辑回归库
from sklearn.ensemble import VotingClassifier, RandomForestClassifier, BaggingClassifier  # 三种集成分类库和投票方法库

案例主要用到了以下库:

  • sys:导入sys并设置字符编码为utf-8目的是为了显示中文不乱码。
  • numpy:基本数据处理。
  • pandas数据读取、审查、异常值处理、缺失值审查和处理
  • DictVectorizer用于将定义好的字符串值和数值键值对做转换
  • SMOTE:用于处理样本不均衡的过抽样处理库。
  • StratifiedKFold:适用于有标签数据集的交叉检验数据集划分方法。
  • cross_val_score:通过交叉检验方法做模型效果评估。
  • LogisticRegressionRandomForestClassifierBaggingClassifier逻辑回归随机森林、Bagging方法的分类库。
  • VotingClassifier:用于分类的投票组合模型方法库。

步骤2 数据审查预处理函数

基本状态查看,查看数据集后2条数据、数据类型、描述性统计。

def set_summary(df):
    '''
    查看数据集后2条数据、数据类型、描述性统计
    :param df: 数据框
    :return: 无
    '''
    print ('{:*^60}'.format('Data overview:'))
    print (df.tail(2))  # 打印原始数据后2条
    print ('{:*^60}'.format('Data dtypes:'))
    print (df.dtypes)  # 打印数据类型
    print ('{:*^60}'.format('Data DESC:'))
    print (df.describe().round(2).T)  # 打印原始数据基本描述性信息

函数主要功能定义如下:

  • 使用tail(2)方法查看数据集最后2数据览,跟head(2)方法相对应。
  • 使用dtypes打印输出所有列数据类型
  • 通过describe().round(2).T描述性统计,并保留2小数,最后做数据转置(目的是便于查看

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

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

函数主要功能定义如下:

  • isnull().any()查看指定轴是否具有缺失值,axis=0指定沿列查看,axis=1指定按行查看。
  • sum()数据中所有为True数据求和

类样本均衡审查,查看每个类的样本量分布

def label_samples_summary(df):
    '''
    查看每个类的样本量分布
    :param df: 数据框
    :return: 无
    '''
    print ('{:*^60}'.format('Labesl samples count:'))
    print (df.ix[:, 1].groupby(df.ix[:, -1]).count())

函数主要功能定义如下:

  • df.ix[:, 1].groupby(df.ix[:, -1]).count()使用.ix方法指定以最后一列为维度,对第一做计数统计。

字符串分类转整数分类,用于将分类变量中的字符串转换为数值索引分类,这是本书新提到的用于预处理的函数。很多情况下,数据库中存储是字符串型的数据例如性别是M/F),为了能够进行矩阵计算,需要把这些字符串型的分类变量转换为数值型分类变量例如M/F转换0/1

def str2int(set, convert_object, unique_object, training=True):
    '''
    用于将分类变量中的字符串转换为数值索引分类
    :param set: 数据集
    :param convert_object:  DictVectorizer转换对象,当training为True时为空;当training为False时则使用从训练阶段得到的对象
    :param unique_object: 唯一值列表,当training为True时为空;当training为False时则使用从训练阶段得到的唯一值列表
    :param training: 是否为训练阶段
    :return: 训练阶段返回model_dvtransform,unique_list,traing_part_data;预测应用阶段返回predict_part_data
    '''
    convert_cols = ['cat', 'attribution', 'pro_id', 'pro_brand', 'order_source', 'pay_type', 'use_id',
                    'city']  # 定义要转换的列
    final_convert_matrix = set[convert_cols]  # 获得要转换的数据集合
    lines = set.shape[0]  # 获得总记录数
    dict_list = []  # 总空列表,用于存放字符串与对应索引组成的字典
    if training == True:  # 如果是训练阶段
        unique_list = []  # 总唯一值列表,用于存储每个列的唯一值列表
        for col_name in convert_cols:  # 循环读取每个列名
            cols_unqiue_value = set[col_name].unique().tolist()  # 获取列的唯一值列表
            unique_list.append(cols_unqiue_value)  # 将唯一值列表追加到总列表
        for line_index in range(lines):  # 读取每行索引
            each_record = final_convert_matrix.iloc[line_index]  # 获得每行数据,是一个Series
            for each_index, each_data in enumerate(each_record):  # 读取Series每行对应的索引值
                list_value = unique_list[each_index]  # 读取该行索引对应到总唯一值列表列索引下的数据(其实是相当于原来的列做了转置成了行,目的是查找唯一值在列表中的位置)
                each_record[each_index] = list_value.index(each_data)  # 获得每个值对应到总唯一值列表中的索引
            each_dict = dict(zip(convert_cols, each_record))  # 将每个值和对应的索引组合字典
            dict_list.append(each_dict)  # 将字典追加到总列表
        model_dvtransform = DictVectorizer(sparse=False, dtype=np.int64)  # 建立转换模型对象
        model_dvtransform.fit(dict_list)  # 应用分类转换训练
        traing_part_data = model_dvtransform.transform(dict_list)  # 转换训练集
        return model_dvtransform, unique_list, traing_part_data
    else:  # 如果是预测阶段
        for line_index in range(lines):  # 读取每行索引
            each_record = final_convert_matrix.iloc[line_index]  # 获得每行数据,是一个Series
            for each_index, each_data in enumerate(each_record):  # 读取Series每行对应的索引值
                list_value = unique_object[each_index]  # 读取该行索引对应到总唯一值列表列索引下的数据(其实是相当于原来的列做了转置成了行,目的是查找唯一值在列表中的位置)
                each_record[each_index] = list_value.index(each_data)  # 获得每个值对应到总唯一值列表中的索引
            each_dict = dict(zip(convert_cols, each_record))  # 将每个值和对应的索引组合字典
            dict_list.append(each_dict)  # 将字典追加到总列表
        predict_part_data = convert_object.transform(dict_list)  # 转换预测集
        return predict_part_data

函数的功能定义略显复杂,主要包括以下过程:

首先定义要转换的数据,通过convert_cols定义转换的列名并在此基础上新建转换数据集final_convert_matrix

接下来获取总记录数,便于按行做循环。建立一个空列表用于存储字符串与对应索引的字典。由于我们要使用sklearnDictVectorizer方法做转换,它要求转换对象是一个由字典组成的列表,字典的键值对是字符串及其对应的数字映射,我们的目的是唯一字符集取出其value和对应的index作为该键值对的组合。

通过training做条件判断,然后分别应用不同计算逻辑。训练阶段比预测集的应用阶段主要多了两部分内容,一是唯一值的列表,二是训练好DictVectorizer对象

处于训练阶段时training == True),先建立unique_list,用于存储每个列的唯一值列表。

使用for循环读取每个列名,单独获取该列数据并使用unique方法获取唯一值,由于该唯一值的结果是一个数组,因此需要使用tolist方法转换为列表,便于后期应用。最后每个列的唯一值列表追加到总唯一值列表中。

获得所有列的唯一值组合后,下面开始每条记录的具体值跟在唯一值列表索引做映射。例如唯一值结果是['1','2','3','4','5']如果该列中有一个'4'那么需要将字符串转换为其对应的索引值3(注意索引从0开始)。

由于转换是按行实现的,因此使用for循环读取每行索引,然后使用iloc方法获取对应行数据,该数据一个Series格式的列表;下面要做的是将每列表中的value映射到唯一值列表中的index

使用一个for循环结合enumerate读取列表的每对应索引,索引结合unique_list[each_index] 用于从唯一值总列表中找到原始所处的列的唯一列表,值用于从unique_list[each_index]中匹配出对应的索引使用的是列表的index(value)方法,得到的索引再替换掉原始字符串数据。子循环结束后,每条记录已经是转换为数值型分类的列表,使用dict结合zip方法将其列名转换为字典

上述循环完成后,我们已经dict_list中存储了所有的数据记录,每天数据记录以一个字典的形式存储整个数据集是字典组成的列表。

下面开始转换。先建立DictVectorizer转换模型对象,这里设置了2参数,sparse=False指定转换后的数据集是一个数组,否则默认会是压缩后的稀疏矩阵,这样设置的原因是后续很多步骤和模型不支持直接基于压缩后的稀疏矩阵做转换和建模;dtype=np.int64用于设置转换后的数据类型是整数型,否则默认是浮点型。使用fit方法训练,使用tranform方法做转换应用。最后返回转换模型对象,唯一值总列表和转换后的数据。

相关知识点:使用DictVectorizer字符串映射为指定数值变量

之前章节我们提到过使用OneHotEncoder方法将数值型变量转换为二值化的标志变量DictVectorizer可以看作是OneHotEncoder方法的上游步骤它可以基于用户指定的字符串与任意数值映射列表(通过字典的形式做映射),对字符串做转换。

假如现在有一个数组6-12

6-12 原始字符串数据

image.png

我们可以将每条记录转换为字典的形式(参照本节的方法),上述数据可以表示为[{'sex':1,'edu':1},{'sex':1,'edu':10},{'sex':2,'edu':5}]

上述每个变量的唯一值所对应的数值可以任意指定,例educationUniversity可以设置为110001312任意数字。也是这种指定方法的灵活性所在,在本案例中我们是设置为所在列表的索引。

使用DictVectorizer转换具体过程为:

from sklearn.feature_extraction import DictVectorizer
model_dv = DictVectorizer(sparse=False)
data =[{'sex':1,'edu':1},{'sex':1,'edu':10},{'sex':2,'edu':5}]
data_new = model_dv.fit_transform(data)
print (model_dv.feature_names_)
print (data_new)

上述输出结果为:

['edu', 'sex']
[[  1.   1.]
 [ 10.   1.]
 [  5.   2.]]

 处于预测阶段时training == False),由于在训练阶段已经获取了唯一值总列表以及转换模型对象,这里只需要做转换即可。整个过程中,相比于训练阶段主少了两个步骤:

  • 无需再次计算唯一值总列表
  • 无需DictVectorizer模型对象应用fit方法tranform时,直接使用训练阶段的对象

时间属性拓展,用于将日期和时间数据拓展出其他属性,例如星期几、周几、小时、分钟等。由于时间数据无法作为有效数据集参与分类模型计算,因此这里将其转换出多种属性。

def datetime2int(set):
    '''
    将日期和时间数据拓展出其他属性,例如星期几、周几、小时、分钟等。
    :param set: 数据集
    :return: 拓展后的属性矩阵
    '''
    date_set = map(lambda dates: pd.datetime.strptime(dates, '%Y-%m-%d'),
                   set['order_date'])  # 将set中的order_date列转换为特定日期格式
    weekday_data = map(lambda data: data.weekday(), date_set)  # 周几
    daysinmonth_data = map(lambda data: data.day, date_set)  # 当月几号
    month_data = map(lambda data: data.month, date_set)  # 月份
 
    time_set = map(lambda times: pd.datetime.strptime(times, '%H:%M:%S'),
                   set['order_time'])  # 将set中的order_time列转换为特定时间格式
    second_data = map(lambda data: data.second, time_set)  # 秒
    minute_data = map(lambda data: data.minute, time_set)  # 分钟
    hour_data = map(lambda data: data.hour, time_set)  # 小时
 
    final_set = []  # 列表,用于将上述拓展属性组合起来
    final_set.extend((weekday_data, daysinmonth_data, month_data, second_data, minute_data, hour_data))  # 将属性列表批量组合
    final_matrix = np.array(final_set).T  # 转换为矩阵并转置
    return final_matrix

函数的功能主要使用map配合lambda指定的数据集应用定义的功能,具体包括以下过程:

通过date_set = map(lambda dates: pd.datetime.strptime(dates, '%Y-%m-%d'),set['order_date'])将数据集中的order_date字符串转换为指定日期格式的数据。有关日期格式的转换,我们之前已经分别介绍在导入数据时定义一个匿名函数date_parse = lambda dates: pd.datetime.strptime(dates,'%m-%d-%Y')配合导入参数date_parser=date_parse解析的方法以及直接使用pd.to_datetime()指定列做转换的方法。这里介绍的方法应用格式类似,只是用了不同的实现过程。接下来分别通过weekday()daymonth获取转换后日期数据的周几、当月几号和月份。

使用相同的方法对数据集中的order_time做转换,然后分别应用secondminutehour获得其秒、分钟、小时数据。

上述两个转换过程完成后,通过列表的extend方法批量添加到一个列表,并转换为数组后再转置,最后返回该数组

 

相关知识点:使用map配合lambda实现自定义功能应用

lambda匿名函数“4.6 时间序列分析已经介绍过,在此重点介绍map函数。

mapPython内置标准函数,它的作用是为一个序列对象每个元素应用指定的函数功能,然后返回一个列表。语法map(function, sequence[, sequence, ...])

其中:

  • function可以是任意函数,一般情况下是针对单个元素实现的具体映射功能
  • sequence是要应用的列表

map()函数的特点是小巧灵活,再配合lambda可以应用到很多小型的迭代过程中,例如日期格式转换、大小写转换、对每个元素切分、数值计算等以个体为单位功能中。

 

样本均衡,使用SMOTE方法对不均衡样本做过抽样处理。

def sample_balance(X, y):
    '''
    使用SMOTE方法对不均衡样本做过抽样处理
    :param X: 输入特征变量X
    :param y: 目标变量y
    :return: 均衡后的X和y
    '''
    model_smote = SMOTE()  # 建立SMOTE模型对象
    x_smote_resampled, y_smote_resampled = model_smote.fit_sample(X, y)  # 输入数据并作过抽样处理
    return x_smote_resampled, y_smote_resampled

函数定义过程比较简单:先建立SMOTE()模型对象,然后使用其fit_sample方法做训练后过抽样处理,最后返回处理后的数据集。

步骤3读取数据从这里开始进入到正式的数据应用过程。

# 定义特殊字段数据格式
dtypes = {'order_id': np.object,
          'pro_id': np.object,
          'use_id': np.object}
raw_data = pd.read_table('abnormal_orders.txt', delimiter=',', dtype=dtypes)  # 读取数据集

定义dtypes用来为特定指定数据类型为字符串型,该列表训练数据和预测数据读取时都会用到。使用pandasread_table方法读取训练并指定分隔符指定列的数据类型。

步骤4 数据审查

基本状态查看,执行set_summary(raw_data) 从返回的结果中分析

通过基本状态输出的概览,未发现数据有录入异常。

***********************Data overview:***********************
          order_id  order_date order_time       cat attribution      pro_id  \
134188  4285770012  2013-09-19   23:55:06      家居日用          GO  1000335947   
134189  4285770056  2013-05-20   23:58:59  生活电器厨卫电器          GO  1000009280   
       pro_brand  total_money  total_quantity order_source pay_type  \
134188       炊大师         79.0               1           抢购     合并支付   
134189        海尔        799.0               1           抢购     合并支付   
            use_id city  abnormal_label  
134188      shukun  东莞市               0  
134189  544975322_  海口市               0  

通过数据类型输出,发现我们指定的几个列以及其他包含有字符串的列都别识别为字符串型,其他类型识别正常。

************************Data dtypes:************************
order_id           object
order_date         object
order_time         object
cat                object
attribution        object
pro_id             object
pro_brand          object
total_money       float64
total_quantity      int64
order_source       object
pay_type           object
use_id             object
city               object
abnormal_label      int64
dtype: object

通过描述性统计,发现total_moneytotal_quantity中存在了极大值。但是我们选择不任何处理,因本节的主题就是针对异常值的分类检测。

*************************Data DESC:*************************
                   count    mean      std  min   25%   50%    75%       max
total_money     134189.0  660.11  2901.21  0.5  29.0  98.4  372.0  766000.0
total_quantity  134190.0    1.20     3.23  1.0   1.0   1.0    1.0    1000.0
abnormal_label  134190.0    0.21     0.41  0.0   0.0   0.0    0.0       1.0

提示 使用describe描述性统计时,默认情况下只针对数值型数据做统计,如果要对全部数据做统计,那么需要使用describe(include='all')

缺失值审查,执行na_summary(raw_data),从返回的结果分析有三列值存在缺失,总的缺失记录为1429占总体的样本量的1%1429/134189)左右

**************************NA Cols:**************************
order_id          False
order_date        False
order_time        False
cat                True
attribution       False
pro_id            False
pro_brand          True
total_money        True
total_quantity    False
order_source      False
pay_type          False
use_id            False
city               True
abnormal_label    False
dtype: bool
Total number of NA lines is: 1429

类样本分布审查,执行label_samples_summary(raw_data),从结果中发现数据存在一定程度的不均衡,异常值记录(label1非异常值的比例为1:3.7左右结果可以处理也可以不处理,这里我们选择处理

*******************Labesl samples count:********************
abnormal_label
0    105733
1     28457
Name: order_date, dtype: int6

步骤5 数据预处理,经过上面的基本分析,我们对特定问题基本有初步的判断,该步骤着手进行处理。

drop_na_set = raw_data.dropna()  # 丢弃带有NA值的数据行
X_raw = drop_na_set.ix[:, 1:-1]  # 分割输入变量X,并丢弃订单ID列和最后一列目标变量
y_raw = drop_na_set.ix[:, -1]  # 分割目标变量y
model_dvtransform, unique_object, str2int_data = str2int(X_raw, None, None, training=True)  # 字符串分类转整数型分类
datetime2int_data = datetime2int(X_raw)  # 拓展日期时间属性
combine_set = np.hstack((str2int_data, datetime2int_data))  # 合并转换后的分类和拓展后的日期数据集
constant_set = X_raw[['total_money', 'total_quantity']]  # 原始连续数据变量
X_combine = np.hstack((combine_set, constant_set))  # 再次合并数据集
X, y = sample_balance(X_combine, y_raw)  # 样本均衡处理

NA:由于样本足够大,因此我们处理会选择丢弃缺失值这是一种大数据下的缺失值问题。使用drop方法丢弃形成不含有NAdrop_na_set

分割输入变量X:在drop_na_set基础上,使用ix方法获取第二列开始到倒数第二列,形成输入变量集合X_raw第一列为订单ID,该列用于区别每个订单,因此唯一区别值不具有规律特征;最后一列是目标变量y

分割目标变量ydrop_na_set基础上,使用ix方法获取最后一列数据形成目标数据集y_raw

字符串分类转整数型分类:直接调用str2int方法对X做转换,返回model_dvtransformunique_objectstr2int_data分别训练后的DictVectorizer对象、唯一值总列表和转换后的数值型分类。

拓展日期时间属性:直接调用datetime2int方法对X做转换,形成结果集datetime2int_data

合并转换后的分类和拓展后的日期数据集:使用numpyhastck方法将分类和拓展后的日期数据集沿列合并形成合并数据集combine_set

原始数据集中的total_moneytotal_quantity数据集提取出来,然后再次使用numpyhstack方法combine_set做合并,至此形成了完整的输入变量X_combine

最后调用sample_balance函数X_combiney_raw过抽样处理,形成最终结果集Xy

步骤6组合分类模型交叉检验,这是本节内容的核心。

model_rf = RandomForestClassifier(n_estimators=20, random_state=0)  # 随机森林分类模型对象
model_lr = LogisticRegression(random_state=0)  # 逻辑回归分类模型对象
model_BagC = BaggingClassifier(n_estimators=20, random_state=0)  # Bagging分类模型对象
estimators = [('randomforest', model_rf), ('Logistic', model_lr), ('bagging', model_BagC)]  # 建立组合评估器列表
model_vot = VotingClassifier(estimators=estimators, voting='soft', weights=[0.9, 1.2, 1.1], n_jobs=-1)  # 建立组合评估模型
cv = StratifiedKFold(8)  # 设置交叉检验方法
cv_score = cross_val_score(model_vot, X, y, cv=cv)  # 交叉检验
print ('{:*^60}'.format('Cross val socres:'))
print (cv_score)  # 打印每次交叉检验得分
print ('Mean scores is: %.2f' % cv_score.mean())  # 打印平均交叉检验得分
model_vot.fit(X, y)  # 模型训练

建立多个分类模型对象通过RandomForestClassifier方法建立随机森林分类模型对象model_rf,设置分类器数量为20,目的是希望通过更多的分类器达到更好的分类精度;设置随机状态0目的控制每次随机的结果相同。然后按照类似的步骤分别建立逻辑回归分类模型对象model_lrBagging分类模型对象model_BagC

提示 RandomForestBagging以及之前我们用到的AdaBoost、Gradient Boosting都是常用的集成方法。除了这些外,sklearn.ensemble提供extra-treesIsolation Forest多种集成方法。这些集成方法大多数都既有分类器又有回归器,意味着可以用于分类和回归。

建立一个模型对象名称和模型对象组合的元组的列表estimators其中:对象名称为了区分和识别使用,任意字符串都可以;模型对象是上面建立的三个分类器对象。列表用于组合投票模型器的参数设置。

使用VotingClassifier方法建立一个基于投票方法的组合分类模型器,具体参数如下:

  • estimators:模型组合为上面建立的estimators列表
  • voting:投放方法设置soft意味着使用个分类器的概率做投票统计,最终按投票概率选出还可以设置为hard,意味着通过每个分类器的label得票最多的label预测输出。
  • weights设置三个分类器对应的投票权重,这样可以将分类概率和权重做加权求和。
  • n_jobs:设置-1意味着计算时使用所有的CPU

注意 设置voting参数时,如果设置为soft,要求每个模型器必须都支持predict_proba方法,否则只能使用hard方法。例如使用SVC(SVM的分类器)时,就只能设置为hard。

使用StratifiedKFold(8)设置一个8交叉检验方法,这是一个按照目标变量样本比例进行随机抽样的方法,尤其适合分类算法的交叉检验。

通过cross_val_score方法做交叉检验模型,设置的参数分别为上述的组合分类模型器交叉检验方法打印输出每次交叉检验结果以及均值如下:

*********************Cross val socres:**********************
[ 0.77178407  0.91971669  0.97473201  0.9724732   0.92316233  0.90960257
  0.91006203  0.91538403]
Mean scores is: 0.91

交叉检验结果看出,8交叉检验除了第一次结果略差以外,其他7都比较稳定,整体交叉检验得分(准确率达到91%,说明了其准确率和鲁棒性相对不错。

在检验之后,我们直接对组合分类模型器model_vot应用fit方法模型训练,形成可用预测的模型对象。

步骤7新数据集做预测,该部分跟训练集时的思路相同仅少了分割目标变量y和样本均衡两个环节,在此不做赘述

X_raw_data = pd.read_csv('new_abnormal_orders.csv', dtype=dtypes)  # 读取要预测的数据集
X_raw_new = X_raw_data.ix[:, 1:]  # 分割输入变量X,并丢弃订单ID列和最后一列目标变量
str2int_data_new = str2int(X_raw_new, model_dvtransform, unique_object, training=False)  # 字符串分类转整数型分类
datetime2int_data_new = datetime2int(X_raw_new)  # 日期时间转换
combine_set_new = np.hstack((str2int_data_new, datetime2int_data_new))  # 合并转换后的分类和拓展后的日期数据集
constant_set_new = X_raw_new[['total_money', 'total_quantity']]  # 原始连续数据变量
X_combine_new = np.hstack((combine_set_new, constant_set_new))  # 再次合并数据集
y_predict = model_vot.predict(X_combine_new)  # 预测结果
print ('{:*^60}'.format('Predicted Labesls:'))
print (y_predict)  # 打印预测值。

最后使用model_votpredict方法做预测并打印结果值如下:

*********************Predicted Labesls:*********************
[1 0 0 0 0 0 0]

6.8.5 案例数据结论

案例中,91%准确率是一个比较高的结果,结果无论是预测的准确率或者多数据集的鲁棒性都表现突出。这首先得益于各个分类评估器本身的性能比较稳定,尤其是集成方法的随机森林和Bagging方法其次是基于预测概率的投票方法配合经验的权重分配,使得经验与数据完美结合,也会产生相互叠加效应,在正确配置的前下,会进一步增强组合模型分类准确率。

6.8.6 案例应用和部署

对于该类型的案例,通常情况下的应用分类两种情况

异常订单的分析

包括异常订单的主要特征品类集中度、重点客户等尤其是可以将异常订单联系人加入黑名单,以降低其对公司正常运营的干扰

订单实时检测

订单实时检测后会结果为异常的订单记录发送到审核部门,然后由审核部门做进一步审查这时需要额外做两部分工作

一部分是将训练过程与预测过程分离,目的是训练阶段与预测阶段形成两个并行进程。实时检测前端属于预测阶段的实时调用,仅应用本案例的步骤7这个过程中还有一步非常重要的内容是将训练过程中用到的模型对象(包括DictVectorizer对象和VotingClassifier对象等)持久化保存服务器硬盘,这样的好处是预测应用调用不同的模型对象时一次引用,多次调用,功能分离的同时又会大大提高运行效率

一部分是在训练阶段做增量更新,而不必每次都跑所数据,这样也能提高实时运算效率

限于篇幅,本书暂时不过多介绍订单的实时检测提到的两部分内容。

6.8.7 案例注意点

本案例中有几个关键点需要读者注意:

关于耗时

以笔者的工作环境,完整运行一次大约需要需要15分钟时间,这里面有两个主要耗时的环节:

  • 训练阶段的字符串分类转整数型分类str2int该过程需要对矩阵中的每个值做映射。
  • 训练阶段的交叉检验,该过程由于是8交叉检验并且里面2集成方法,再加上使用组合投票的分类器的应用,导致整个交叉检验耗时较长。

因此,如果读者侧重于效率的话,那么这种基于组合投票以及集成分类方法的实现思路将不是优先选择。

关于输入特征变量

本案例应用两个特殊字段pro_iduse_id,这ID一般作为关联主键或者数据唯一ID而很少用于模型训练本身。这里使用的原因是希望能从中找到是否异常订单也会集中在某些品类或某些客户上。笔者经过测试,如果把这两个维度去掉,整个模型的准确率会下降到70%以下。

关于样本均衡

由于本案例中的两类数据差异并没有特别大例如1:10甚至更大,因此均衡处理不是必须的。本案例中由于运营对于异常的定义比较宽松,因此才会形成大量异常名单范围。但实际上异常检测在很多情况下的记录是比较少的,因此样本均衡操作通常必不可少。

6.8.8 案例引申思考

更高的模型准确率或更低的模型误差

本案例的核心建模思路是通过组合多个单一或集成模型器的方法,实现高层次的手动集成分类模型用。我们6.7 案例-基于超参数优化的Gradient Boosting的销售预测”中提到了自动参数优化方法,该方法可以跟本案例的组合模型器相结合,这样更会提高模型效果。另外我们在5.8 案例-基于AdaBoost的营销响应预测”也介绍过通过交叉检验得到不同参数的模型效果值,并手动做最优参数配置方法。三种方法,尤其是前两种在追求高准确率或低误差的应用场景中非常有效

有关二值标志转换的问题

虽然本案例中没有用到二值标志转换操作,由于用到了两个具有特殊意义的字段pro_iduse_id,在此讨论下如果使用了这类唯一性特别强或分类特别多的变量,会遇到哪些问题。这类字段通常产生海量的唯一值如果其做二值化标志转换,那么会出现两个主要问题:

一是海量的商品ID用户ID使得数据稀疏特征非常明显,数据将主要受到这两个特征所转换的海量特征的影响,而使得整个数据集的类别划分效果变得非常差。

二是如果通过现有的二值化转换方法OneHotEncoder来得到的数组结果,面临海量唯一值报错“ValueError: array is too big; `arr.size * arr.dtype.itemsize` is larger than the maximum possible size.”,原因

在32位Python版本下由于系统限制,即使内存再多,Python进程最多只能占用2G内存(如果使用IMAGE_FILE_LARGE_ADDRESS_AWARE方法将二值化结果做压缩,最多也只能占用4G内存,实际上大多数32位Python都没有做该设置);
即使在64位Python版本下,Numpy(sklearn基于numpy)的最大数据类型是complex128,其最大可占用的内存空间大约有5G左右,而ID对象的唯一值可能有无限大,例如将销售周期拉长,会发现销售SKU能达到几千万甚至上亿。这种有限的对象长度将无法承载无限增长的ID

当然,可能有读者会想到通过二值化转换形成压缩稀疏矩阵,默认的OneHotEncoder方法也是提供基于csc(一种稀疏矩阵压缩方式)压缩稀疏矩阵,scipy.sparse也提供了相关方法可以基于压缩稀疏矩阵做数据合并等处理。这种想法没错,但是大多数的数据处理的的输入都要求是数组或矩阵转换为数组几乎是建模的前置条件压缩矩阵目前则完全行不通。

字符串分类转整数型分类

字符串分类转整数型分类以及后续的二值化标志问题,应用的前提是训练集中被转换的唯一值域必须是固定的,否则在预测集转换时遇到数据时就会报错,这点在之前提过。在这里再次提出希望读者注意。

有关数据集中NA

数据处理的一开始,我们就已经将NA排除了。但读者是否想过,如果预测应用时,再次出现NA该如何处理?

我们分析下输入变量在订单信息生成时,是否允许出现缺失值attributioncatpro_idpro_brand几个字段都是根据数据库中商品信息自动匹配的,因此不应该出现缺失值;order_idorder_dateorder_timetotal_moneytotal_quantityorder_sourceuse_idcity订单时生成的必填信息,不应该有缺失;而关于pay_type这个要看具体业务部门如何定义:

  • 如果基于已经支付的订单做异常分类检测,那么该字段不应为空;
  • 如果基于全部订单做异常分类检测,那么该字段会经常出现为空的情况;

基于上述分析,我们会有如下对应策略:

  • 针对attributioncatpro_idpro_brand字段,只要有pro_id(商品ID),就可以从商品库中匹配出这些信息来
  • 针对order_idorder_dateorder_timetotal_moneytotal_quantityorder_sourceuse_idcity字段,只要有order_id就可以从订单库中匹配出这些信息来。

那如果商品库和订单库没有两个ID或者即使匹配回来的数据仍然有NA如何处理?由于这些数据理论上不应该为空,建议将其筛选出来单独存储(当然不作预测)然后跟IT部门沟通分析到底为什么会出现缺失值制定补足策略,该策略会应用到缺失值处理过程中。

如果缺失值无法预测、也无法避免,那么可以通过条件判断如果数据记录中有缺失值则不作检测毕竟缺失值1%不到;如果读者认为有必要,可以将缺失值作为一种特殊值分布形态,以具体值(例如0填充,用于后续数据处理和建模使用这也是一种行之有效的方法。

提示 通常很多数据处理环节对NA是“无法容忍,例如OneHotEncoder无法NA值转换为二值化矩阵,因为NA不是整数型数据。此时NA值以特定值填充转换是一种变通思路。

推荐 0
本文由 Python爱好者社区 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册