优雅高效地数据挖掘——基于Python的sklearn
目录
前言
1. 关于DataFrameMapper
2. 用DataFrameMapper做特征工程
2.2. 单列变换
2.3. 多列变换
2.3.1. 多列各自用同样的变换
2.3.2. 多列整体变换
2.4. 对付稀疏变量
2.5. 保留指定列
2.6. 自定义列变换
2.7. 小小的总结
3. 实战
3.1. 数据探查
3.1.1. 缺失值处理
3.1.2. 长尾特征
3.2. 特征工程
3.2. 交叉验证
3.3. 预测
4. 思考
5. 参考资料
打赏
Bonus
前言
在 数据挖掘 流程中,特征工程是极其重要的环节,我们经常要结合实际数据,对某些类型的数据做特定变换,甚至多次变换,除了一些常见的基本变换(参考我之前写的『数据挖掘比赛通用框架』)外,还有很多非主流的奇技淫巧。所以,尽管有 sklearn.pipeline 这样的流水线模式,但依然满足不了一颗爱折腾数据的心。好在,我找到了一个小众但好用的库——sklearn_pandas,能相对简洁地进行特征工程,使其变得优雅而高效。
目前这个项目还在维护,大家有什么想法可以到 sklearn_pandas 的 github 主页提问题,以及获取最新的版本。
1. 关于 DataFrameMapper
sklearn_pandas 起初是为了解决这样一个问题:在 sklearn 的旧版本中,很多常见模块(特征变换器、分类器等)对 pandas 的 DataFrame 类型不支持,必须先用 DataFrame 自带的 .values、.as_matrix 之类的方法,将 DataFrame 类型转换成 numpy 的 ndarray 类型,再输入到 sklearn 的模块中,这个过程略麻烦。因此 sklearn_pandas 提供了一个方便的转换接口,省去自己转换数据的过程。
但当我花了几天时间探索了 sklearn_pandas 的库及其跟 pandas、sklearn 相应模块的联系后,我发现 sklearn 0.16.0 向后的版本对 DataFrame的兼容性越来越好,经我实际测试,现在最新的 0.17.1 版本中, model、preprocessing等模块的大部分函数已完全支持 DataFrame 类型的输入,所以我认为:
sklearn_pandas 的重点不再是数据类型转换,而是通过其自创的 DataFrameMapper 类,更简洁地、把 sklearn 的 transformer 灵活地运用在 DataFrame 当中,甚至可以发挥你的聪明才智,将几乎大部分特征变换在几行代码内完成,而且一目了然。
sklearn_pandas 官方文档提供的例子比较少,我看了下它的源码,有以下重要发现
-
DataFrameMapper 继承自 sklearn 的 BaseEstimator 和 TransformerMixin ,所以 DataFrameMapper 可以看做 sklearn 的 TransformerMixin 类,跟 sklearn 中的其他 Transformer 一样,比如可以作为 Pipeline 的输入参数
-
DataFrameMapper 内部机制是先将指定的 D ataFrame 的列转换成 ndarray 类型,再输入到 sklearn 的相应 transformer 中
-
DataFrameMapper 接受的变换类型是 sklearn 的 transformer 类,因而除了 sklearn 中常见的变换 (标准化、正规化、二值化等等)还可以用 sklearn 的 FunctionTransformer 来进行自定义操作
本文先介绍下如何用 DataFrameMapper 类型进行特征工程,再将 skleanr_pandas、sklearn、pandas 这三个库结合,应用到一个具体的数据挖掘案例中。
2. 用 DataFrameMapper 做特征工程
[注意]在正式进入本节前,建议先阅读本人之前写的『[scikit-learn]特征二值化编码函数的一些坑』,了解 sklearn 和 pandas 常见的二值化编码函数的特性和一些注意点。
若输入数据的一行是一个样本,一列是一个特征,那简单的理解,『特征工程』就是列变换。本节将讲解如何用 DataFrameMapper 结合 sklearn 的 Transformer 类,来进行列变换
首先import本文将会用到的所有类(默认已装好 scikit-learn, pandas, sklearn_pandas 等库)
import random
import sklearn
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# frameworks for ML
from sklearn_pandas import DataFrameMapper
from sklearn.pipeline import make_pipeline
from sklearn.cross_validation import cross_val_score
from sklearn.grid_search import GridSearchCV
# transformers for category variables
from sklearn.preprocessing import LabelBinarizer
from sklearn.preprocessing import MultiLabelBinarizer
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
# transformers for numerical variables
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import Normalizer
# transformers for combined variables
from sklearn.decomposition import PCA
from sklearn.preprocessing import PolynomialFeatures
# user-defined transformers
from sklearn.preprocessing import FunctionTransformer
# classification models
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
# evaluation
from sklearn.metrics import scorer
我们以如下的数据为例
2.2. 单列变换
『单列』可以是 1-D array,也可以是 2-D array,为了迎合不同的 transformer,但最终输出都是 2-D array,具体我们看以下例子
我们分别对这三列做了二值化编码、最大最小值归一化等,但要注意,OneHotEncoder接受的是 2-D array的输入,其他是 1-D array,具体请参考我之前写的『[scikit-learn]特征二值化编码函数的一些坑』。上面代码的运行结果如下
分别对应三种变换,前三列和后五列是pet和age的二值化编码,第四列是age的最大最小值归一化。
同样,我们也可以将这些变换『级联』起来(类似 sklearn 里的 pipeline ):
将 age 列先最大最小值归一化,再标准化,输出结果:
array([[ 0.20851441],
[ 1.87662973],
[-0.62554324],
[-0.62554324],
[-1.4596009 ],
[-0.62554324],
[ 1.04257207],
[ 0.20851441]])
2.3. 多列变换
除了上面的单列变换, DataFrameMapper 也能处理多列
2.3.1. 多列各自用同样的变换
有时候我们要对很多列做同样操作,比如二值化编码、标准化归一化等,也可以借助于 DataFrameMapper ,使得执行更高效、代码更简洁。
这里同时对 age 和 salary 进行归一化,结果如下
同样,这些变换也可以级联
2.3.2. 多列整体变换
多列变换时,除了分别对每列变换,我们有时还需要对某些列进行整体变换,比如 降维(PCA, LDA) 和 特征交叉等,也可以很便捷地借助 DataFrameMapper 实现
以上我们对 age 和 salary 列分别进行了 PCA 和生成二次项特征
2.4. 对付稀疏变量
(写完此文后发现该功能并不是很work)
sklearn 中 OneHotEncoder 类和某些处理文本变量的类(比如 CountVectorizer )的默认输出是 sparse 类型,而其他很多函数输出是普通的 ndarray , 这就导致数据拼接时可能出错。为了统一输出, DataFrameMapper 提供 sparse 参数来设定输出稀疏与否,默认是 False 。
2.5. 保留指定列
(稳定版 1.1.0 中没有此功能,development 版本中有 )
从上面的实验中我们可以看到,对于我们指定的列, DataFrameMapper 将忠诚地执行变换,对于未指定的列,则被抛弃。
而真实场景中,对于未指定的列,我们可能也需要做相应处理,所以DataFrameMapper提供default参数用于处理这类列:
False : 全部丢弃(默认)
None : 原封不动地保留
other transformer : 将 transformer 作用到所有剩余列上
2.6. 自定义列变换
不难发现,上面我们利用 DataFrameMapper 所做的列变换,大多是调用 sklearn 中现有的模块( OneHotEncoder,MinMaxEncoder , PCA 等),那如果遇到一些需要自己定义的变换,该怎么做呢?比如常见的对长尾特征做 log(x+1) 之类的变换?
对 sklearn 熟悉的同学开动一下脑筋,答案马上就有了——那就是 FunctionTransformer ,该函数的具体参数细节可参考 sklearn 的官方文档,这里简单给个例子
以上我们将 numpy 中的函数 log1p (作用等同于 log(x+1 ))通过 FunctionTransformer 包裹成一个 sklearn 的 transformer 类,就能直接作用在不同列上啦。
动手能力强的同学还可以自己定义函数,提示一下,用 numpy 的 ufunc ,这里就不赘述了,留给大家探索吧。
2.7. 小小的总结
基于以上内容,以及我对 sklearn、pandas 相关函数的了解,我总结了以下对比表格:
至此, DataFrameMapper 的精髓已悉数传授,想必大家已摩拳擦掌跃跃欲试了吧。OK,接下来进入实战!
3. 实战
在进入实战前,先结合本人前作——『新手数据挖掘的几个常见误区』,简单梳理一下数据挖掘的流程:
数据集被分成训练集、验证集、测试集,其中训练集验证集进行交叉验证,用来确定最佳超参数。在最优参数下,用整个训练集+验证集上进行模型训练,最终在测试集看预测结果
我们这里结合一个实际的业务数据集来进行流程讲解。首先加载数据集
数据集字段如下
这是一个常见的时间序列数据集,所以我们按照时间上的不同,将其划分为训练集(1~5月)和测试集(6月)
3.1. 数据探查
3.1.1. 缺失值处理
常见的缺失值处理手段有
-
填充
-
丢弃
-
看做新类别
我们先简单统计一下每个字段的空值率
这组数据比较理想,只有 Saler 字段是缺失的,所以我们只需要看下 Saler 和目标变量之间的关系
结果如下
以上结果表明空值对预测结果似乎有些影响,所以我们暂且将空值看做一类新的类别:
3.1.2. 长尾特征
长尾分布也是一种很常见的分布形态,常见于数值类型的变量,最简单的方法是用 log(x+1) 处理。在我们的数据集当中, Cost 这个字段便是数值类型,我们看下它的分布:
log 变化的效果还是不错的,变量的分布相对均衡了。
3.2. 特征工程
通过上面简单的数据探查,我们基本确定了缺失值和长尾特征的处理方法,其他类别变量我们可以做简单的 One-hot 编码,整个策略如下
在确定好特征工程的策略后,我们便可以上我们的大杀器—— DataFrameMapper 了,把所有的变换集成到一起
3.2. 交叉验证
特征工程完毕后,便是交叉验证。交叉验证最重要的目的是为了寻找最优的超参数(详见本人前作『新手数据挖掘的几个常见误区』),通常我们会借助 sklearn 中的 KFold ,train_test_split, metric.score 等来进行交叉验证,这里简化起见,我们直接用 GridSearchCV, 但要注意的是, GridSearchCV 对 FunctionTransformer 类的支持不好,尤其有 lambda 函数时。所以为简化起见,我们注释掉上面使用了 lambda 函数的 FunctionTransformer 类(有兴趣的同学可以尝试抛弃 GridSearchCV ,手动进行交叉验证)。
这里我们选用最常见的 LogisticRegression ,并调整它的超参数——正则系数C和正则方式 penalty (对此不熟悉的同学赶紧补下『逻辑回归』的基础知识)。同时如前面所讲,我们用 pipeline 把特征工程和模型训练都流程化,输入到 GridSearchCV 中:
我们定义了三折交叉验证(cv = 3),并选用准确率(scoring = ‘accuracy’)作为评估指标,运行结果如下:
最佳超参数是取 L2 正则,并且正则系数为 0.1
3.3. 预测
在得到模型的最优超参数后,我们还需要在训练集+验证集上进行特征变换,并在最优超参数下训练模型,然后将相应特征变换和模型施加到测试集上,最后评估测试集结果。
而现在,这一系列流程被 GridSearchCV 大大简化,只需两行代码即可搞定:
最后结果为 0.6166666666666667 ,即测试集上的分类准确率。
4. 思考
行文至此,洋洋洒洒千言,但依然只是完成了数据挖掘中最基本的流程,所做的特征变换和选用的模型也都非常简单,所以还有很大的提升空间。
此处以下留两个点,可以动手实践,也欢迎在群里探讨(群二维码见第6节『Bonus』)
-
当选用的 model 不是 sklearn 中的模块时(比如 xgboost),特征工程还可以用 sklearn_pandas 的 DataFrameMapper, 但 sklearn 中傻瓜模式的 pipeline 就无从作用了,必须自己搭建 cross validation 流程
-
bad case 也有分析的价值
-
从单模型到模型的 ensemble
5. 参考资料
-
sklearn_pandas 官方文档、源码及 github 上的 issues
-
pandas、scikit-learn 官方文档
-
寒小阳的博客(http://blog.csdn.net/han_xiaoyang/article/details/49797143)
相关链接
>>>数据挖掘比赛通用框架
注:本文系数据观转自数据挖掘机养成记,作者穆文,版权著作权属原创者所有。数据观整理分享此文并非商业用途,以上内容并不代表数据观观点,如涉著作权等事宜请联系小编更正。数据观微信公众号(ID:cbdioreview) ,欲了解更多大数据行业相关资讯,可搜索数据观(中国大数据产业观察网www.cbdio.com)进入查看。
责任编辑:陈卓阳