JD-大数据竞赛心得

来源:互联网 发布:淘宝历史订单双11消失 编辑:程序博客网 时间:2024/04/30 19:43

特此声明:引用白虎QQ群的吉更大神

京东竞赛技术参考汇总


关键字
xgboost
高纬度特征的特征轮
交叉验证
网格搜索调参
单一模型
多模型融合
特征工程
描述统计量

ML指代机器学**,DM指代数据挖掘
DM流程通常分两个阶段
Step1. 数据清洗,数据格式调整
Step2. 特征构建,模型选择,效果评估
Step1.是整个流程中最耗时的,这点想必大家早有耳闻,DM界有句名言 garbage in ,garbage out ,可见清洗数据非常重要。从我的经验看,这部分工作跟实际处理的业务问题关系很大,比较dirty,也没有统一流程,所以本文重点放在Step2.
3. 前期准备
3.1. 数据变换
先把原始数据通过一定变换,变成通用的多列数据类型,作为ML模型的输入,也就是上面的Step1。用X代表样本及其特征集合,y代表样本标签集合,整个流程如下:
 
3.2. 问题分类
根据标签y的不同,可以把DM问题分为以下几类:
二分类问题(这种问题在工业界最为常见,比如广告点击率预估、推荐系统购买行为预测),此时y只有一维,取值只有两个(比如0-1),每个样本有唯一的标签。比如预测广告是否会被用户点击;用户是否会购买某种商品
多分类问题(比如微博用户情感分析、用户对理财产品偏好性分析),通常此时y有多维,每维代表一个类标签,取值只有两个(比如0-1),每个样本有唯一的标签;当然,y也可以只有一维,取值有多个,每个值代表一个类标签。比如通过微博分析出用户情感属于喜怒哀乐等哪类;将理财产品的用户群体分为偏好型/温和型/厌恶型
多标签问题(比如音乐的标签划分),y有多维,跟多分类的区别在于,样本可以同时属于多个标签。作为一枚钢琴爱好者,这里以钢琴作品举例,假设标签集合为{独奏,协奏,浪漫主义,印象主义},最爱之一的德彪西「月光」无疑属于{独奏,印象主义},朗总成名作柴一则可归为{协奏,浪漫主义},云迪家喻户晓的肖邦夜曲是{独奏,浪漫主义},而中国特色的「保卫黄河」可归为{协奏}
单回归问题(比如股价预测),y只有一维,取值为连续值。比如预测阿里明天的股价
多回归问题(比如天气预测),y有多维,取值连续。比如预测明天的气温、空气湿度、降雨量
3.3. 评价指标
预测结果的好坏需要用一些指标来衡量,通常不同类型的DM问题有不同的评价指标。对于二分类问题,很多时候类别本身不均衡(比如正样本很多负样本极少),所以我们通常用AUC值——即ROC曲线下的面积——来评价二分类结果;在多分类或者多标签问题中,我们通常选取评价指标为交叉熵(cross-entropy)或者log损失(log loss);对于回归问题,则可以选用MSE(mean square error)
3.4. 工具
我跟原博客作者一样,提倡使用python解决DM问题,因为python的第三方库非常齐全,以下是常见的、用于DM问题的python库:
pandas: 仿照了R语言的数据结构、数据操作,一般用来做数据预处理,特征工程,其DataFrame数据格式用起来相当便利
scikit-learn: 家喻户晓的ML库,各种聚类、分类、回归模型等,也可以用来做预处理
xgboost: 陈天奇大神的杰作,改进了传统的GBDT模型,在底层用一些trick加速模型训练,非常值得一试,可以取代其他ML库里的GBDT系列模型 (很早就听说过这个碉堡的库,但一直没有上手实践,实在汗颜…后面我会结合GBDT做特征工程,实践下效果,发布到公众号)
keras: 神经网络相关的库,可以选择基于tensorflow或theano,赶脚很强大,我也是刚接触
matplotlib: 作图必备,语言风格跟MATLAB很像,很好上手
tpdm: 我没听过,原作者提到的,感兴趣的童鞋可以了解下
3.5. 开发环境
这里我补充说一下python开发环境和上面几个库的安装方法。首先我跟原作者一样,因为追求自(装)由(逼),所以不用python IDE(比如Anaconda, Pycharm),当然,装IDE可能省很多事情,个人建议安装Pycharm。然后我自己的python开发环境(纯属个人**惯,仅供参考):
windows: notepad++及其插件nppexec/explorer,结合我昨天发布的『一个神奇的脚本,一键运行各类程序』,里面的nppexec脚本可一键执行Python。以及linux风格的shell: git bash (git bash是基于msys的,跟cygwin略有不同)
mac: sublime及其插件Package Control/anaconda,以及iTerm2,或者自带的terminal。(sublime中import某些python库,比如matplotlib/sklearn/tensorflow会出点bug,需要修改下环境变量啥的,遇到相关问题可以微信我,尽量帮你解决)
linux: vim(因为我一般在命令行模式下开发)。如果是界面linux,应该可以有其他选择
另外,jupyter notebook(前身是ipython notebook)是个好东西,可以逐步执行python代码片段,不依赖于平台,可在浏览器中打开,非常适合学**过程中练手。
再说库的安装,首先强烈建议安装64位python2.7,然后针对不同操作系统:
windows[不推荐]: 略蛋疼,64位的库大多没有官方版本,具体安装方式见我之前写过的一篇文章『在Windows下安装64位Python及数据挖掘相关库』(后续我会完善该文,但只发送给指定分组,具体见文末Bonus)。大多数库的安装都类似,但xgboost稍微复杂些,不能直接pip install,而是要装VS来编译其中相关文件,再安装,遇到问题可以微信我。另外tensroflow目前没有windows版本
mac[推荐]: 最新的python2.7一般都自带pip,所以装好python后,直接在terminal中 pip install 相关库就可以了,注意库的依赖关系,一般先安装numpy,scipy,matplotlib,再装其他库
linux[推荐]: 基本跟mac类似
4. DM问题框架
终于到了最核心的部分,原作者总结了一个他参加各类DM比赛常用的ML流程图,真是一图胜千言
 
这里我擅自补充一下,这张图看着眼花缭乱,其实就两点,这两点也是DM比赛中最核心的两点:
特征工程(包括各种离散化、组合、选择)
模型选择、模型融合(即ensemble)
能把这两点做好,实属不易,但其实在工业界,特征工程和模型融合是否需要做到极致,是要看具体问题的。有些业务的数据维度本身就很稀少,并不足以支撑庞大的特征体系;有些业务需要很强的可解释性(比如金融领域),于是很多模型不能直接用;有些业务则要系统的实时性和稳定性,过于复杂的ensemble虽然能提升一点指标,但也许得不偿失。
上图当中的粉色部分是最常用的一些步骤,简单梳理一下:先确定DM问题的类型,然后对数据集划分,接着对常见的数值变量和类别变量做相应处理,可以进行特征选择,最后选择合适的模型做预测,评估模型并输出结果。下面将详细展开。
4.1. 问题定义
首先搞清楚要解决的问题属于哪一类,结合上节所讲,我们一般通过观察y标签类来定义DM问题的类型。
4.2. 数据集划分
在明确了问题的分类后,我们将对数据集划分成训练集(Training Data)和验证集(Validation Data)(补充:很多时候还要划分出测试集(Test Data),先用训练集验证集的交叉验证来寻找模型的最优超参数,模型调优完毕后,最终用测试集来评估模型最终效果,具体参考我之前在公众号发布的『新手数据挖掘中的几个常见误区』第二节)。划分方式如下:
 
这里我用自己本地的一个小数据集(名为toy_data.txt)做展示,获取方式见文末Bonus,加载以上小数据集的代码如下:
import pandas as pd
df = pd.read_csv("toy_data.txt",sep = "\t")
df.head()
运行结果:
 
最后一个字段Label就是我们要预测的y,在我的数据集里取值0或1,所以是一个二分类问题。
对于分类问题,要根据标签来划分数据集,比如每种标签采样多少,这样才能保证训练集跟验证集的样本标签分布接近,另外采样方式也不限于随机采样,可以根据实际业务问题选择合适的采样方式。这里我们可以借助scikit-learn来实现分层的K折交叉验证,代码如下
X = df.ix[:,0:-1]
y = df.ix[:,-1]
from sklearn.cross_validation import StratifiedKFold
kf = StratifiedKFold(y,3) # 三折交叉验证
用以下代码验证一下训练集和验证集中的正样本的占比:
idx_train, idx_valid = next(iter(kf))
print float(sum(y[idx_train]))/len(idx_train)
print float(sum(y[idx_valid]))/len(idx_valid)
结果为0.69713 0.69565,两者非常接近。
注意,不太推荐使用iter(kf),这里只是为了展示标签分布,具体我会在本文第五节『实战』中介绍如何高效地使用交叉验证。
如果是回归问题,则不存在分类问题中类别标签分布不均的情况,所以我们只需采用普通的K折交叉验证即可:
from sklearn.cross_validation import KFold
kf = KFold()
4.3. 特征工程
毫不夸张地说,特征工程是DM重要的一环,也是决定DM比赛的关键因素。纵观DM比赛,几年间已由追求模型是否fancy转向无尽的特征工程,主要得益于越来越标准化的ML模型,以及更好的计算能力。
特征工程可以做的很复杂很庞大,但受限于本人目前的水平,这里只结合原博客内容讲解一些最基本(也是最经典)的处理方法。
4.3.1. 处理类别变量
类别变量(categorial data)是一种常见的变量,在我之前写的『新手数据挖掘中的几个常见误区』 一文的第三节中讨论过为何要对类别变量编码
在toy_data当中,字段Continent, Country, Product, Brand, TreeID, Industry, Saler都可以看做是类别变量。处理类别变量一般是先标签化,然后再二值化编码。标签化的目的是将字段的原始值(如字符串、不连续的数字等)转换成连续的整数值,再对整数值二值化编码,如果原始值是整数,则直接二值化即可
我们拿toy_data前几个样本的Continent字段举例,对其进行编码:
mapper = skp.DataFrameMapper([
('Continent', sklearn.preprocessing.LabelBinarizer()),
])
tempX = df[['Continent']].head()
print tempX
print mapper.fit_transform(tempX.copy())
运行结果如下
 
可以看到,原来的一列Continent字段变成了三列,分别代表[ 'AM', 'EP', 'LA' ],取值1表明是,取值0表明否。这就是常说的one-hot编码。如果类别变量的取值是整数,则直接用sklearn.preprocessing.OneHotEncoder()即可,把上面代码中LabelBinarizer()替换掉
注意我们必须将对训练集上的变换原封不动的作用到测试集,而不能重新对测试集的数据做变换(详见我之前写的『新手数据挖掘中的几个常见误区』第一节)。
4.3.2. 处理数值变量
一般而言,数值变量不用做太多处理,只需做正规化(normalization)和标准化(standardization)即可,分别对应scikit-learn中的Normalizer和StandardScaler。不过对于稀疏变量,在做标准化的时候要注意,选择不去均值。
其实数值型变量最好也进行离散化,离散手段从基本的等距离散法、按分隔点人为指定,到聚类、输入树模型等,手段很多,在此不详细展开,我会在后续文章中提及。
4.3.3. 处理文本变量
文本在实际问题中很常见,比如用户评论、新闻摘要、视频弹幕等等。我们用的toy_data不包含文本变量,所以这里我参考了scikit-learn的文档,一个小的corpus作为我们的训练数据集。
corpus = [
'This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?',
]
corpus有四句话,可以看做是四个样本。接下来我们先用一个简单的方法处理文本变量——统计corpus中每个词出现次数,代码如下:
from sklearn.feature_extraction.text import CountVectorizer
vectorizer1 = CountVectorizer(min_df=1)
temp1 = vectorizer1.fit_transform(corpus)
print vectorizer1.get_feature_names()
print temp1.toarray() # temp1是sparse类型, 转换成ndarray方便查看
运行结果:
 
第一行是corpus中所有词,下面的ndarray每行代表该词在该样本中出现次数,比如第2行第6列的2代表second这个词在第二句话中出现了2次。一般我们不会直接用这个结果,而是会将每行归一化之类。
这种处理方式简单粗暴,没有考虑词与词之间的关系。我们改进一下这个方法,除了考虑单个词之外,还考虑corpus中成对出现的词(类似NLP里n-gram的 bi-gram,具体请自行Google),代码如下
vectorizer2 = CountVectorizer(ngram_range=(1, 2))
temp2 = vectorizer2.fit_transform(corpus)
print vectorizer2.get_feature_names()
print temp2.toarray()
运行结果:
 
然而,这还不够,像a is this这类的助词、介词等,词频将非常高(在NLP中又叫停止词 stop word),所以需要减小他们的权重。一种做法是,不再简单统计该词在文档中出现的词频,而且还要统计 出现该词的文档的占比,这在NLP中叫tfidf。说的有点绕,具体到我们的例子中可以写成如下表达式:
某单词x的tfidf = x在一个样本中出现的次数/出现x的文档占比
分子即tf,分母即1/idf,有时需要用log sqrt之类的函数作用在tf或者 1/idf上,以减弱某项的影响。同样,我们可以:
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer3 = TfidfVectorizer(ngram_range=(1, 2))
temp3 = vectorizer3.fit_transform(corpus)
print vectorizer3.get_feature_names()
print temp3.toarray()
运行结果:
 
仔细观察不难发现,is this这类的停止词在变换后数值变小了。
!!注意!!跟处理类别变量、数值变量一样,我们在处理文本变量时,必须将训练集上的变换方式原封不动地作用到验证集或测试集上,而不能重新对验证集或者测试集做变换。比如在得到上面的vectorizer3后,我们将其作用在一个新的样本 ['a new sentence']上,代码如下
print vectorizer1.transform(['a new sentence']).toarray()
我们可以看到,结果是 [[0 0 0 0 0 0 0 0 0]],因为这个样本里的三个词从未出现在训练集corpus中,这是正确的结果!
为了方便将变换作用在未来的测试集,我们可以先把vectorizer3用pickle保存到本地,用的时候再load,保存方式如下:
import cPickle as pickle
pickle.dump(vectorizer3, open('vectorizer3.pkl','w'))
用的时候再 vectorizer = pickle.load(open('vectorizer3.pkl','r'))即可。
4.3.4. 特征融合
ToDo 区别对待稠密特征和稀疏特征,
4.3.4. 特征降维
ToDo PCA等
4.3.5. 特征选择
ToDo
4.4. 模型选择和模型融合
ToDo



时间序列和数据挖掘 
前言
首先我们来看下一些常见的数据挖掘场景:
广告点击率预测: 根据用户历史上的浏览、点击、停留时间等行为,预测用户会不会点击该广告
推荐系统: 根据用户历史的购买记录、点击行为,以及商品描述文本、用户画像等等一些系列特征,预测用户未来的购买需求
命名实体识别: NLP (自然语言处理) 的经典问题,识别出一句有序的文本里,特定类别的词组
股价预测: 预测某支股票或某些股票,未来一段时间的价格,或者粗糙一点——涨跌趋势
图像识别: 比如人脸识别、医疗图像分析
视频情感分析: 根据一段视频(可以看做是一系列时间上连续的图像),分析出这个视频表达的情感倾向
我们会发现,这些问题很多都跟『时间序列』有关(除了图像识别),并且也是数据挖掘比赛的常见赛题,以天猫14年的推荐大赛、支付宝15年的资金流入流出预测为例,这俩比赛有非常完整的前十强团队答辩ppt公布,我们会发现,处理这些问题的选手中,主要分俩流派:机器学习流派和统计学流派。14年的天猫推荐大赛是机器学习流派占主流:做特征、训练分类器、模型融合,把时间信息完全融入到特征中去;而15年的支付宝比赛是统计学流派占上风:用各种 AR (auto regression) 模型,从时间维度上建模,直接考虑时间上的连续性。
这些看似迥异的做法,其实都可以归结到一个大的理论中去,而这个理论,正是本文的核心。
基础
首先,我认为所有『有监督学习』问题,本质上都是在建立一个函数映射 f ,可以用一个式子来刻画:
f(X)->Y
这个式子是我自己想的,但其实只要有一定机器学习基础的人,都很容易想到这一点。这里的 X 是样本的特征,Y 是样本的标签,X 可以是向量、矩阵甚至多维矩阵,相应的,Y 可以是单个标签,也可以是一组标签。我们待会儿结合实例讲讲 X 和 Y 在具体情形下的具体形式
于是:
按照 Y 的取值是连续的,还是离散的,可以把有监督学习问题分为『回归』和『分类』,可以参考我的前作『分类和回归的本质』,给你一个不同的视角看待经典问题。
按照 Y 是单个标签,还是前后有关联的序列化标签,可以把有监督学习问题分为『时间序列』和『非时间序列』问题,而本文今天的主角,就是这个『时间序列』
上面两种划分方式的两两组合,会产生四种情况,比如:时间序列的回归问题,非时间序列的分类问题,等等。这四种情况基本涵盖了所有的机器学习模型,或者说,所有实际问题,都可以通过各种方式,最后转化成这四种情况里的一种。
其实,严格来讲,『时间序列』这个说法并不准确,比如在 NLP 中,一句话的每个单词组成一个序列,但他们并不代表有时间标签,只是有前后关系,所以这类问题其实叫『事件序列(event sequences)』。我们在本文当中为了简单起见,将这种带有前后顺序关系的序列,统称为『时间序列』,特此声明。
正文
接下来,我们将正式探讨『时间序列』问题的常见处理手段
从统计学的角度
处理时间序列最自然的方法,莫过于 AR 系列的模型 (ARIMA, ARMAX, …), 以及我之前在一篇很水的论文里用过的一阶马尔可夫链模型。这类模型的的最大特点,就是直接从预测变量 Y 的角度来考虑问题,比如现在要预测 y(T),比如T 时刻的股价,那我们就会用 {y(1),…,y(t)…, y(T-1) } 这些历史信息,做一阶差分、二阶差分、均值、方差等等等,得到各种统计量(有点特征工程的意思),然后来预测 y(T)。这种模型有两个显而易见的弊端:
只用到了预测变量本身,无法利用到其他信息如影响股价的政策因素等(不过统计学里已经有一些模型在 handle 这类问题了)
看似在时间维度上建立模型,但其实是用以往的记录分别来预测 y(T), y(T+1),…, 并没有考虑这些预测变量本身的时间相关性
能处理的历史记录时长有限
从经典机器学习的角度
依然是预测 y(T) ,这时候我们会对利用上所有的其他变量 X,比如上面说的,影响股价的政策因素等,而且与统计学的做派不同,我们也对历史记录提取不同的特征,但我们并不 care 是哪类统计量,比如什么一阶差分啥的,也不太 care 统计指标,我们只管拼命做特征,开脑洞做特征,用 CNN 自动做特征,反正就是特征工程搞得飞起,然后得到一个庞大无比的 X,用来预测 y(T)。其实不难发现,这种做法,就是我们在上一节提到的『非时间序列问题』,即把一个本来是『时间序列』的问题,转换成非时间序列问题。这种做法的好处是通过完善的特征工程,我们能把过去历史信息尽可能多地建模在 X 里,当然弊端也很明显,跟 AR 系列模型一样。
割裂了 y(T), y(T+1) 之间的时间相关性
处理的历史记录的时长有限
从RNN等模型的角度
先总结下,前面两种处理『时间序列』的方法,看似杂乱无章,实则可以简洁地纳入我们上一节提到的公式
f(X)->Y
具体到单个样本的预测,就是
f( X(T) ) -> Y(T)
比如,在统计学的方法中,那些从 {y(1),…,y(t)…, y(T-1) } 历史记录上提取的特征,其实就是这里的 X(T);而在经典机器学习方法中,我们脑洞打开所做的各类特征,依然可以归结为—— X(T)。
所以问题就来了,既然这两大类方法,都割裂了 y 之间的时间相关性,那有没有一个『大一统』模型能建立 y 之间的相关性呢? 我们转化成数学语言就是
f( X(1), X(2), …, X(T) ) -> Y(1), Y(2), …, Y(T)
这样,上面的 f( X(T) ) -> Y(T) 就可以看做是它的一个特例,即当样本只包含一个时间点的时候。
答案是肯定的,这样的模型是存在的,那就是——RNN,HMM,CRF 等等,这类模型在建模时显示地建立了 Y 之间的关系。但一般情况下,HMM、CRF 会有马尔可夫性假设,直白地说就是假设 Y(T) 只跟 Y(T-1) 有关系,所以相比而言,从理论上看,RNN 更完美(注意,只是理论上讲,具体效果还要看实际应用),而且,RNN 理论上能处理无限时长的时间序列,即上面的 T 可以无穷大,但因为训练时梯度在时间维度上的连乘操作,RNN 存在梯度消失和爆炸的风险,取而代之的是 GRU, LSTM 等,这又是另一个话题了,在此不表。
好,下面我们用更浅显明白的语言,来叙述这个『大一统』框架。这时候我们不得不搬出这张神图了:


这张图阐释了 RNN 所能处理的所有可能情形,绿色框是 RNN 模型,红色是 输入 X, 蓝色是预测目标 Y。我们重点关注第一个 one to one 和 最后一个many to many。不难发现,对于非时间序列模型,他们看待『时间序列』的角度是单一的,即他们建立的是针对样本 (x(T),y(T))的映射,就是这里的 one to one,而在时间序列模型里,一个样本天然的是 (sequenceX, sequenceY ),这里的 sequenceX 就是上面公式里的 X(1), X(2), …, X(T),同理 sequenceY 是 Y(1), Y(2), …, Y(T),对应的就是 many to many 的情形。
不过这里要提一下两个重要的补充
尽管理论上 RNN 可以建模无穷时间序列,可以处理不同时间长度的样本,不需要像其他方法一样,只能统计一个时间窗口内的历史信息,但在实际工程实现上,还是要尽量统一历史时间步长的,否则将对编程造成很大麻烦,这点在 Keras, Tensorflow, Theano 等框架里都有体现。
虽然把 统计学方法 和 经典机器学习方法 都纳入了这个『大一统』框架,看做是一个特例,但其实,RNN 并不需要像他们一样,做很复杂的特征工程,因为这些工作早已隐含在神经网络的各个非线性模块的各种映射里(这也是为什么说 神经网络能自动提取特征),并嵌入到了隐层输出 h 中,作为下一时刻的输入,所以下一个时刻的输入 X(T) 可以不用做那么多特征工程,这正是神经网络吸引人的地方。
一点发散
写到这儿的时候,我的脑子里突然闪过研究生期间学的『预测控制』理论。预测控制就是利用以往的系统状态、输出信号,来生成未来 N 步的控制率,但区别在于,这里的模型是已知的线性或非线性模型,所以是一种『机理建模』,而用 RNN、RF、GBDT 等,则是完全的数据驱动的黑箱建模。
总结
虽然讨论的是『时间序列』问题,但其实是借助 RNN 模型的理念,把处理所有『有监督学习』问题的思路都理清了
当处理的是非时间序列问题时(比如人脸识别),收集到的是一系列样本 (X, Y),只要建立 f(X) -> Y 的映射。X可以做特征工程,也可以交由 CNN 这类模型自动学出来
当样本是时间序列时,有两种做法:
仿照非时间序列问题的处理方法,收集一系列样本 (X(t),Y(t))建立 f(X(t))->Y(t) 的映射,但其实是『大一统』框架的一个特烈
更通用的『大一统』框架,则是从 RNN 的角度看待这个问题,建立 f( X(1), X(2), …, X(T) ) -> Y(1), Y(2), …, Y(T) 的映射
额外补充一点:在图像处理、语音识别中,输入特征的维度(像素点、语音波形)之间的相关度很大,可以交给神经网络自动提取特征,而在推荐系统、nlp等问题中,输入特征的维度(user的id、性别、年龄、词的词形)之间的相关性并不那么强,所以神经网络提取特征的效果,未必比得上人工特征。我认为这就是为何深度学习最先在语音和图像领域打开市场的主要原因。
十大误区:
 1
  太关注训练(Focus on Training)
  就像体育训练中越来越注重实战训练,因为单纯的封闭式训练常常让训练时状态神勇,比赛时却一塌糊涂。
  实际上,只有样本外数据上的模型评分结果才真正有用!(否则的话,直接用参照表好了!)
  例如:
癌症检测(Cancer detection):MD Anderson的医生和研究人员(1993)使用神经网络来进行癌症检测,惊奇地发现,训练时间越长(从几天延长至数周),对训练集的性能改善非常轻微,但在测试集上的性能却明显下降。
机器学习或计算机科学研究者常常试图让模型在已知数据上表现最优,这样做的结果通常会导致过度拟合(overfit)。
  解决方法:
解决这个问题的典型方法是重抽样(Re-Sampling)。重抽样技术包括:bootstrap、cross-validation、jackknife、leave-one-out…等等。
 
  2
  只依赖一项技术(Rely on One Technique)
  这个错误和第10种错误有相通之处,请同时参照其解决方法。没有对比也就没有所谓的好坏,辩证法的思想在此体现无疑。
  “当小孩子手拿一把锤子时,整个世界看起来就是一枚钉子。”要想让工作尽善尽美,就需要一套完整的工具箱。
  不要简单地信赖你用单个方法分析的结果,至少要和传统方法(比如线性回归或线性判别分析)做个比较。
  研究结果:
  按照《神经网络》期刊的统计,在过去3年来,只有1/6的文章中做到了上述两点。也就是说,在独立于训练样本之外的测试集上进行了开集测试,并与其它广泛采用的方法进行了对比。
  解决方法:
使用一系列好的工具和方法。(每种工具或方法可能最多带来5%~10%的改进)。
 
  3
  提错了问题(Ask the Wrong Question)
  一般在分类算法中都会给出分类精度作为衡量模型好坏的标准,但在实际项目中我们却几乎不看这个指标。为什么?因为那不是我们关注的目标。
  (1)项目的目标:一定要锁定正确的目标
  例如:
欺诈侦测(关注的是正例)(长途电话上的分析):不要试图在一般的通话中把欺诈和非欺诈行为分类出来,重点应放在如何描述正常通话的特征,然后据此发现异常通话行为。
  (2)模型的目标:让计算机去做你希望它做的事
  大多数研究人员会沉迷于模型的收敛性来尽量降低误差,这样让他们可以获得数学上的美感。但更应该让计算机做的事情应该是如何改善业务,而不是仅仅侧重模型计算上的精度。
  4
  只靠数据来说话(Listen (only) to the Data)
  “让数据说话”没有错,关键是还要记得另一句话:兼听则明,偏听则暗!如果数据+工具就可以解决问题的话,还要人做什么呢?
  (1)投机取巧的数据:数据本身只能帮助分析人员找到什么是显著的结果,但它并不能告诉你结果是对还是错。
  (2)经过设计的实验:某些实验设计中掺杂了人为的成分,这样的实验结果也常常不可信。
 
  5
  使用了未来的信息(Accept Leaks from the Future)
  看似不可能,却是实际中很容易犯的错误,特别是你面对成千上万个变量的时候。认真、仔细、有条理是数据挖掘人员的基本要求。
预报(Forecast)示例:预报芝加哥银行在某天的利率,使用神经网络建模,模型的准确率达到95%。但在模型中却使用了该利率作为输入变量。
金融业中的预报示例:使用3日的移动平均来预报,但却把移动平均的重点设在今天。
  解决方法:
要仔细查看那些让结果表现得异常好的变量,这些变量有可能是不应该使用,或者不应该直接使用的。
给数据加上时间,避免被误用。
  6
  抛弃了不该忽略的案例(Discount Pesky Cases)
  到底是“宁为鸡头,不为凤尾”,还是“大隐隐于市,小隐隐于野”?不同的人生态度可以有同样精彩的人生,不同的数据也可能蕴含同样重要的价值。
  异常值可能会导致错误的结果(比如价格中的小数点标错了),但也可能是问题的答案(比如臭氧洞)。所以需要仔细检查这些异常。
  研究中最让激动的话语不是“啊哈!”,而是“这就有点奇怪了……”
  数据中的不一致性有可能会是解决问题的线索,深挖下去也许可以解决一个大的业务问题。
  例如:
  在直邮营销中,在对家庭地址的合并和清洗过程中发现的数据不一致,反而可能是新的营销机会。
  解决方法:
可视化可以帮助你分析大量的假设是否成立。
 
  7
  轻信预测(Extrapolate)
  依然是辩证法中的观点,事物都是不断发展变化的。人们常常在经验不多的时候轻易得出一些结论。即便发现了一些反例,人们也不太愿意放弃原先的想法。
  维度咒语:在低维度上的直觉,放在高维度空间中,常常是毫无意义的。
  解决方法:
进化论。没有正确的结论,只有越来越准确的结论。
  8
  试图回答所有问题(Answer Every Inquiry)
  有点像我爬山时鼓励自己的一句话“我不知道什么时候能登上山峰,但我知道爬一步就离终点近一步。”
  “不知道”是一种有意义的模型结果。
  模型也许无法100%准确回答问题,但至少可以帮我们估计出现某种结果的可能性。
  9
  随便地进行抽样(Sample Casually)
  (1)降低抽样水平。例如,MD直邮公司进行响应预测分析,但发现数据集中的不响应客户占比太高(总共一百万直邮客户,其中超过99%的人未对营销做出响应)。于是建模人员做了如下抽样:把所有响应者放入样本集,然后在所有不响应者中进行系统抽样,即每隔10人抽一个放入样本集,直到样本集达到10万人。但模型居然得出如下规则:凡是居住在Ketchikan、Wrangell和Ward Cove Alaska的人都会响应营销。这显然是有问题的结论。(问题就出在这种抽样方法上,因为原始数据集已经按照邮政编码排序,上面这三个地区中不响应者未能被抽取到样本集中,故此得出了这种结论)。
  解决方法:“喝前摇一摇!”先打乱原始数据集中的顺序,从而保证抽样的随机性。
  (2)提高抽样水平。例如,在信用评分中,因为违约客户的占比一般都非常低,所以在建模时常常会人为调高违约客户的占比(比如把这些违约客户的权重提高5倍)。建模中发现,随着模型越来越复杂,判别违约客户的准确率也越来越高,但对正常客户的误判率也随之升高。(问题出在数据集的划分上。在把原始数据集划分为训练集和测试集时,原始数据集中违约客户的权重已经被提高过了)
  解决方法:
先进行数据集划分,然后再提高训练集中违约客户的权重。
 
  10
  太相信最佳模型(Believe the Best Model)
  还是那句老话-“没有最好,只有更好!”
  可解释性并不一定总是必要的。看起来并不完全正确或者可以解释的模型,有时也会有用。
  “最佳”模型中使用的一些变量,会分散人们太多的注意力。(不可解释性有时也是一个优点)
  一般来说,很多变量看起来彼此都很相似,而最佳模型的结构看上去也千差万别,无迹可循。但需注意的是,结构上相似并不意味着功能上也相似。
  解决方法:
把多个模型集装起来可能会带来更好更稳定的结果。
【1. 特征变换】
(特别感谢 @寒小阳 @retanoj )


误区:
对训练集和测试集分别做变换,比如标准化、归一化、降维等


正解:
此误区是如此之习以为常,以至于已写进 libsvm 的官方文档(http://www.csie.ntu.edu.tw/~cjlin/papers/guide/guide.pdf)的某一节中:
 
事实上测试集的特征变换,应该使用跟训练集同样的因子(均值、标准差、PCA的变换矩阵w等等),我个人的直观理解是,这样能保证变换后的测试集样本与训练集样本处于同一个样本空间,从而基于训练集样本空间的 model,在测试集上也 work(更深层的可能涉及 learning theory,希望能有高人作补充)


有个简单的例子可以辅助理解:
现有总体样本 x={-1,1, 7,9},对应标签y={0,1,1,1},我们可以认为分类器为:
f(x>=0)=1
f(x<0)=0
若现令训练集为 x={-1,1},y={0,1},要预测测试集 x={7,9}的标签值
训练集的均值=0,所以做去均值变换后还是 x={-1,1},我们基于训练集能训练出上述的分类器。此时对测试集做变换,如果我们采用训练集的均值(=0),则 测试集没变化,用上述分类器能准确预测出 y={1,1},但如果我们对测试集去除其自身的均值(=(7+9)/2=8),我们将得到变换后的测试集 x={-1,1},采用上述分类器,将得到 y={0,1},显然预测失准




【2. Cross Validation】
(特别感谢 @寒小阳 @面包包包包包包)


误区:
CV (Cross Validation) 训练出来的最优模型,可以直接用于线上预测
正解:
CV只是确定了模型的最优超参数(就是没法通过模型学习、只能手动调整或者grid search之类的那类参数,像svm的gamma和c,kmeans的聚类数k、LDA的alpha和beta)
一旦 model 的最优超参数通过cv确定下来之后,还要再次对 model 用整个线下数据集做 training,然后将 model 用于线上预测
具体做法可以参考下图
 


【3. Dummy Variables】
( 特别感谢 @Matrix  @chrispy  @被吓坏的煜儿 )


误区:
处理类别特征的时候(比如类别:“color”={red, green, blue}),直接对不同类别赋予不同数值(比如:1,2,3),并把“季节”特征直接扔到模型里


正解:
对于无序类别特征(比如“color”),本身不存在数值上的大小关系,所以最好不要直接对其赋值,而是将类别特征“因子化”,变成“Dummy Varaibles”(这种处理手段也叫 one-hot),从一个特征("color")变成三个0-1特征(“是否red”,“是否green”, “是否blue”)
 
对于有序类别特征,(比如我在支付宝处理的购买力模型,有个特征是“城市等级”={1,2,3,4}),也可以用同样的方法因子化。
这么做的原因是什么呢?因为对于大部分模型,尤其是线性模型,对数值类特征都比较敏感,无法挖掘非线性关系。我们不妨以 LR 为例,假如我们给  color = {green, red, blue} 分别赋予 {1,2,3},而标签是 {冷色,暖色},显然对于 color 而言,这是个非线性映射, 数值为 1 和 3 时是冷色,2 是暖色,但 LR 训练的结果,只能给 color 这个特征赋予同一个权重 w,所以无法得到这种非线性关系,但若将 color 像上面提到的那样因子化,那我们训练出的 LR,可以给 red 赋予正向的权重, blue 和 green 赋予负向的权重,从而得到这种单个特征上的非线性映射。
不过对于决策树系列的模型,也许因子化就不十分必要了,因为决策树能学出非线性分割面,同样对 color 分 暖色、冷色的问题,也许决策树能学西出:
if color<=1 
y=冷色
else if 1<color<=3
y=暖色
else
y=冷色
优雅高效地数据挖掘:基于Python的sklearn_pandas库
来源:数据挖掘机养成记 时间:2016-08-31 14:17:19 作者:穆文
目录
前言
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 内部机制是先将指定的 DataFrame 的列转换成 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)
0 0
原创粉丝点击