月度归档:2017年10月

第13章、神经网络

13.1简介

神经网络是一门重要的机器学习技术。它是目前最火热的研究方向--深度学习的基础。学习神经网络不仅可以让你掌握一门强大的机器学习方法,同时也可以更好地帮助你理解深度学习技术。
目前,深度学习(Deep Learning,简称DL)在人工智能领域可谓是大红大紫,现在不只是互联网、人工智能,在教育、医疗、智慧城市、出行、司法、安全、金融等众多领域,都能反映出深度学习引起巨大变化。要学习深度学习,首先要熟悉深度学习的源头--神经网络(Neural Networks,简称NN)。又称为人工神经网络(Artificial Neural Networks,简称ANN)。
神经网络最早是人工智能领域的一种算法或者说是模型,目前神经网络已经发展成为一类多学科交叉的学科领域,它也随着深度学习取得的进展重新受到重视和推崇。
其实,神经网络最为一种算法模型很早就已经开始研究了,最早可以回溯到1946年,到现在近70年了。一路走来,经历了很多波折,其中比较代表性的是其三次大起大落:
1)20世纪40年代到60年代,期间人们陆续发明了第一款的感知神经网络软件和聊天软件,证明了数学定理。不过,很快到了70年代后期,人们发现过去的理论和模型,只能解决一些非常简单的问题,很快人工智能进入了第一次的冬天。
2)20世纪80年代到90年代,随着1982年Hopfield神经网络和BT训练算法的提出,大家发现人工智能的春天又来了。80年代又兴起一拨人工智能的热潮,包括语音识别、语音翻译计划,以及日本提出的第五代计算机。不过,到了90年代后期,人们发现这种东西离我们的实际生活还很遥远。大家都有印象IBM在90年代的时候提出了一款语音听写的软件叫IBM Viavoice,在演示当中效果不错,但是真正用的时候却很难使用。因此,在2000年左右第二次人工智能的浪潮又破灭了。
3)直到2006年,随着2006年Hinton提出的深度学习的技术,以及在图像、语音识别以及其他领域内取得的一些成功,大家认为经过了两次起伏,神经网络、深度学习开始进入了真正爆发的前夜。
我们可以通过一个图来总结这三次浪潮:

本图片选自:http://www.cnblogs.com/subconscious/p/5058741.html
图中的起伏线,我觉得表达人们对神经网络情感的起落,但从技术上角度来说,应该是条递增曲线。
本章以神经网络为主,着重介绍一些相关的基础知识,然后在此基础上引出深度学习的基本概念。

13.2神经元

1943年,心理学家McCulloch和数学家Pitts参考了生物神经元的结构(请看下图),发表了抽象的神经元模型MP。

一个神经元模型包含输入,输出与计算功能的模型。下图是一个典型的神经元模型:包含有3个输入,1个输出,以及2个计算功能(一个求和,一个函数f)。

其间的箭头线称为“连接”。每个连接上有一个“权值”,如上图〖的w〗_i,权重是最重要的东西。一个神经网络的训练算法就是让权重的值调整到最佳,以使得整个网络的预测效果最好。
我们使用x来表示输入,用w来表示权值。一个表示连接的有向箭头可以这样理解:在初端,传递的信号大小仍然是x,端中间有加权参数w,经过这个加权后的信号会变成x*w,因此在连接的末端,信号的大小就变成了x*w。
输出y是在输入和权值的线性加权和叠加了一个函数f的值。在MP模型里,函数f又称为激活函数。
从机器学习的角度,我们习惯把已知的属性称之为特征,未知的属性称之为目标。假设特征与目标之间确实是线性关系,并且我们已经得到表示这个关系的权值w1,w2,w3。那么,我们就可以通过神经元模型预测新样本的目标。

11.3 单层神经网络(感知器)

1958年,计算科学家Rosenblatt提出了由两层神经元组成的神经网络。他取名为“感知器”(Perceptron)感知器是当时首个可以学习的人工神经网络。Rosenblatt现场演示了其学习识别简单图像的过程,在当时的社会引起了轰动。
人们认为已经发现了智能的奥秘,许多学者和科研机构纷纷投入到神经网络的研究中。美国军方大力资助了神经网络的研究,并认为神经网络比“原子弹工程”更重要。这段时间直到1969年才结束,这个时期可以看作神经网络的第一次高潮。
单层神经网络在原来MP模型的“输入”位置添加神经元节点,标志其为“输入单元”。其余不变,于是我们就有了下图:

在“感知器”中,有两个层次。分别是输入层和输出层。输入层里的“输入单元”只负责传输数据,不做计算。输出层里的“输出单元”则需要对前面一层的输入进行计算。
假如我们要预测的目标不再是一个值,而是一个向量,例如[0,1]。那么可以在输出层再增加一个“输出单元”。
下图显示了带有两个输出单元的单层神经网络,其中输出单元f1的计算公式如下图。


f(W * X) = Y;
这个公式就是神经网络中从前一层计算后一层的矩阵运算。其中f是激活函数,激活函数一般特点是:
1)非线性:为提高模型的学习能力,如果是线性,那么再多层都相当于只有两层效果。
2)可微性:基于梯度的模型最优化方法。
3)单调性:保证模型简单
与神经元模型不同,感知器中的权值是通过训练得到的,它类似一个逻辑回归模型,可以做线性分类任务,但感知器只能做简单的线性分类任务。不过,当时的人们并没有认识到这一点,感知器刚出来一段时间,人们热情高涨,以为找到了打开人工智能的钥匙,当人工智能领域的巨擘Minsky指出其局限性时,事态就发生了变化。
Minsky在1969年出版了一本叫《Perceptron》的书,里面用详细的数学证明了感知器的弱点,尤其是感知器对XOR(异或)这样的简单分类任务都无法解决。如果将计算层增加到两层,计算量则过大,而且没有有效的学习算法。所以,他认为研究更深层的网络是没有价值的。或许由于Minsky的巨大影响力以及书中呈现的悲观态度,让很多学者和实验室纷纷放弃了神经网络的研究,神经网络的研究陷入了第一次低谷。

13.4 两层神经网络(多层感知器)

Minsky说过单层神经网络无法解决异或问题。但是当增加一个计算层以后,两层神经网络不仅可以解决异或问题,而且具有非常好的非线性分类效果。不过两层神经网络的计算是一个问题,没有一个较好的解法。
1986年,Hinton和Rumelhar等人提出了反向传播(Backpropagation,BP)算法,解决了两层神经网络所需要的复杂计算量问题,从而带动了业界使用两层神经网络研究的热潮。目前,大量的教授神经网络的教材,都是重点介绍两层(带一个隐藏层)神经网络的内容。

两层神经网络除了包含一个输入层,一个输出层以外,还增加了一个中间层。此时,中间层(又称为隐含层)和输出层都是计算层,如下图:

现在,我们的权值矩阵增加到了两个,我们用上标来区分不同层次之间的变量。


由此可见,使用矩阵运算来表达是很简洁的,而且也不会受到节点数增多的影响(无论有多少节点参与运算,乘法两端都只有一个变量)。因此神经网络的教程中大量使用矩阵运算来描述。
不知大家是否注意到,到目前为止,我们对神经网络的结构图的讨论中都没有提到偏置节点。事实上,这些节点一般默认存在的。它本质上是一个只含有存储功能,且存储值永远为1的单元。在神经网络的每个层次中,除了输出层以外,都会含有这样一个偏置单元。正如线性回归模型与逻辑回归模型中的一样。
偏置单元与后一层的所有节点都有连接,我们设这些参数值为向量b,称之为偏置。如下图:

可以看出,偏置节点很好认,因为其没有输入(前一层中没有箭头指向它)。有些神经网络的结构图中会把偏置节点明显画出来,有些不会。一般情况下,我们都不会明确画出偏置节点。
在考虑了偏置以后的一个神经网络的矩阵运算如下:


事实上,神经网络的本质就是通过参数与激活函数来拟合特征与目标之间的真实函数关系。
前面我们讲了,单层网络只能做线性分类任务,无法对非线性数据集分类。两层神经网络能对非线性数据集分类吗?可以的。
与单层神经网络不同。理论证明,两层神经网络可以无限逼近任意连续函数。那它是如何做到的呢?
从公式(13.4)、(13.5)我们可以看出,从输入层到隐藏层时,为矩阵和向量相乘,本质上就是对向量的坐标空间进行一个变换,使其可以被线性分类,然后输出层的决策分界划出了一个线性分类分界线,对其进行分类。
这里需要强调一下,如果只引入隐含层无法保证可以处理非线性数据。除了增加隐含层,还一个关键要素是隐含层神经元引入激活函数,而且这些激活函数必须非线性。如果这些激活函数是线性,那么可以证明(具体证明大家可参考黄安埠编写的《深入浅出深度学习》),即使增加了隐含层,那么多层神经网络,其效果与单层神经网络没有区别,即,不管增加多少层隐含层,也只能处理线性数据,无法处理非线性数据集。
在设计一个神经网络时,输入层的节点数需要与特征的维度匹配,输出层的节点数要与目标的维度匹配。而中间层的节点数,却是由设计者指定的。节点数设置是否合理,会影响到整个模型的效果。
我们该如何决定隐含层的节点数呢?目前业界没有完善的理论来指导这个决策。一般是根据经验来设置。较好的方法就是预先设定几个可选值,通过切换这几个值来看整个模型的预测效果,最后选择效果较好的那个。

13.5 前向传播与反向传播详解

以下我们通过一个实例来具体说明神经网络的前向传播与反向传播的实现方法,理解神经网络的信息传播(包括误差传播)对理解深度学习中很多技术非常有帮助。如深度学习中梯度消失、梯度爆炸等原因将一目了然。

13.5.1 前向传播
在神经网络中,前向传播是利用当前的权重参数和输入数据,从下到上(即从输入层到输出层),求取预测结果,并利用预测结果与真实值求解损失函数的值。如下图:

具体步骤如下:
1、有输入层到隐含层


2、有隐含层到输出层

13.5.2 反向传播
在神经网络中,反向传播是利用前向传播求解的损失函数,从上到下(即从输出层到输入层),求解网络的参数梯度或新的参数值,经过前向和反向两个操作后,完成了一次迭代过程,如下图:

具体步骤如下:
1、计算总误差


2、由输出层到隐含层,假设我们需要分析权重参数w5对整个误差的影响,可以用整体误差对w5求偏导求出:这里利用微分中的链式法则,涉及过程包括:f(Z_O1)--->Z_O1----->W5


根据公式11.10--11.16,不难求出各部分的偏导数。
最后,调整后的权重为w5’,具体计算公式如下:

3、由隐含层到输入层,假设我们需要分析权重参数w1对整个误差的影响,可以用整体误差对w1求偏导求出,涉及过程包括:f(Z_h1)--->Z_h1----->W1,不过而f(Z_h1)会接受E_O1和E_O2两个地方传来的误差,所以这个地方两个都要计算。


13.6 两层神经网络的优势与局限

两层神经网络在多个地方的应用说明了其效用与价值。10年前困扰神经网络界的异或问题被轻松解决。神经网络在这个时候,已经可以发力于语音识别,图像识别,自动驾驶等多个领域。
历史总是惊人的相似,神经网络的学者们再次登上了《纽约时报》的专访。人们认为神经网络可以解决许多问题。就连娱乐界都开始受到了影响,当年的《终结者》电影中的阿诺都赶时髦地说一句:我的CPU是一个神经网络处理器,一个会学习的计算机。
但是神经网络仍然存在若干的问题:尽管使用了BP算法,一次神经网络的训练仍然耗时太久,而且困扰训练优化的一个问题就是局部最优解问题,这使得神经网络的优化较为困难。同时,隐藏层的节点数需要调参,这使得使用不太方便,工程和研究人员对此多有抱怨。
90年代中期,由Vapnik等人发明的SVM(Support Vector Machines,支持向量机)算法诞生,很快就在若干个方面体现出了对比神经网络的优势:无需调参;高效;全局最优解。基于以上种种理由,SVM迅速打败了神经网络算法成为主流。神经网络的研究再次陷入低谷。

                                                                                                              Vladimir Vapnik

11.7 多层神经网络与深度学习

我们延续两层神经网络的方式来设计一个多层神经网络。
在两层神经网络的输出层后面,继续添加层次。原来的输出层变成中间层,新加的层次成为新的输出层。所以可以得到下图:

当然,我们也可在这个基础继续添加新层、最终得到一个几十、几百、甚至上千层的神经网络。随着层数的不断增多,也出现很多新问题,如维度爆炸、过拟合、梯度消失、梯度爆炸等等,对这些新出现的问题,该如何解决?这里就展开来说,后面我们介绍深度学习时,会涉及到。
2006年,Hinton在《Science》和相关期刊上发表了论文,首次提出了“深度信念网络”的概念。与传统的训练方式不同,“深度信念网络”有一个“预训练”(pre-training)的过程,这可以方便的让神经网络中的权值找到一个接近最优解的值,之后再使用“微调”(fine-tuning)技术来对整个网络进行优化训练。这两个技术的运用大幅度减少了训练多层神经网络的时间。他给多层神经网络相关的学习方法赋予了一个新名词--“深度学习”。

很快,深度学习在语音识别领域暂露头角。接着,2012年,深度学习技术又在图像识别领域大展拳脚。Hinton与他的学生在ImageNet竞赛中,用多层的卷积神经网络成功地对包含一千类别的一百万张图片进行了训练,取得了分类错误率15%的好成绩,这个成绩比第二名高了近11个百分点,充分证明了多层神经网络识别效果的优越性。


深度学习有很多方法,如卷积神经网络(CNN),循环神经网络(RNN)等,这些神经网络涉及内容比较多,这里就不展开说了,后面在介绍深度学习内容时将详细介绍。这里我们只涉及全连接的深度学习。
增加更多的层次有什么好处?更深入的表示特征,以及更强的函数模拟能力。
更深入的表示特征可以这样理解,随着网络的层数增加,每一层对于前一层次的抽象表示更深入。在神经网络中,每一层神经元学习到的是前一层神经元值的更抽象的表示。例如第一个隐藏层学习到的是“边缘”的特征,第二个隐藏层学习到的是由“边缘”组成的“形状”的特征,第三个隐藏层学习到的是由“形状”组成的“图案”的特征,最后的隐藏层学习到的是由“图案”组成的“目标”的特征。通过抽取更抽象的特征来对事物进行区分,从而获得更好的区分与分类能力。
关于逐层特征学习的例子,可以参考下图:


这一个特征不断抽象的过程:像素==>边==>器官==>人脸
这种对特征逐层抽象的思想,在深度学习中非常普遍而且重要。以下是利用深度学习模型识别猫或狗的训练步骤:

13.8多层网络训练方法

在单层神经网络时,我们使用的激活函数符号函数,如sgn函数。到了两层神经网络时,我们使用的最多的是sigmoid函数。而到了多层神经网络时,通过一系列的研究发现,ReLU函数在训练多层神经网络时,更容易收敛,并且预测性能更好。因此,目前在深度学习中,最流行的非线性函数是ReLU函数。ReLU函数不是传统的非线性函数,而是分段线性函数。其表达式非常简单,就是y=max(x,0)。简而言之,在x大于0,输出就是输入,而在x小于0时,输出就保持为0。这种函数的设计启发来自于生物神经元对于激励的线性响应,以及当低于某个阈值后就不再响应的模拟。
在多层神经网络中,训练的主题仍然是优化和泛化。当使用足够强的计算芯片(例如GPU图形加速卡)时,梯度下降算法以及反向传播算法在多层神经网络中的训练中仍然工作的很好。目前学术界主要的研究既在于开发新的算法,也在于对这两个算法进行不断的优化,例如,增加了一种带动量因子(momentum)的梯度下降算法。
在深度学习中,泛化技术变的比以往更加的重要。这主要是因为神经网络的层数增加了,参数也增加了,表示能力大幅度增强,很容易出现过拟合现象。因此正则化技术就显得十分重要。目前,Dropout技术,以及数据扩容(Data-Augmentation)技术是目前使用的最多的正则化技术。
目前,深度神经网络在人工智能界占据统治地位。但凡有关人工智能的产业报道,必然离不开深度学习。神经网络界当下的四位引领者除了前文所说的Ng,Hinton以外,还有CNN的发明人Yann Lecun,以及《Deep Learning》的作者Bengio。

随着层数的不断增加,训练的方法也在不断变化,下图说明期间的一些主要变化:

13.9神经网络分类

神经网络其实是一个非常宽泛的称呼,它包括两类,一类是用计算机的方式去模拟人脑,这就是我们常说的ANN(人工神经网络),另一类是研究生物学上的神经网络,又叫生物神经网络。对于我们计算机人士而言,肯定是研究前者。神经网络大致分类,可参考下图:

在人工神经网络之中,又分为前馈神经网络和反馈神经网络这两种。那么它们两者的区别是什么呢?这个其实在于它们的结构图。我们可以把结构图看作是一个有向图。其中神经元代表顶点,连接代表有向边。对于前馈神经网络中,这个有向图是没有回路的。你可以仔细观察本文中出现的所有神经网络的结构图,确认一下。而对于反馈神经网络中,结构图的有向图是有回路的。反馈神经网络也是一类重要的神经网络。其中Hopfield网络就是反馈神经网络。深度学习中的RNN也属于一种反馈神经网络。
具体到前馈神经网络中,就有了本文中所分别描述的三个网络:单层神经网络,双层神经网络,以及多层神经网络。深度学习中的CNN属于一种特殊的多层神经网络。另外,在一些Blog中和文献中看到的BP神经网络是什么?其实它们就是使用了反向传播BP算法的两层前馈神经网络。也是最普遍的一种两层神经网络。

参考:http://www.cnblogs.com/subconscious/p/5058741.html

第4章 用Python分析股票数据

本文将使用Python来可视化股票数据,比如绘制K线图,并且探究各项指标的含义和关系,最后使用移动平均线方法初探投资策略。

4.1数据导入

这里将股票数据存储在stockData.txt文本文件中(下载数据),我们使用pandas.read_table()函数将文件数据读入成DataFrame格式。
其中参数usecols=range(15)限制只读取前15列数据,parse_dates=[0]表示将第一列数据解析成时间格式,index_col=0则将第一列数据指定为索引。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

%matplotlib inline
%config InlineBackend.figure_format = 'retina'
%pylab inline
pylab.rcParams['figure.figsize'] = (10, 6) #设置绘图尺寸

#读取数据
stock = pd.read_table('./data/stockdata.txt', usecols=range(15), parse_dates=[0], index_col=0)
stock = stock[::-1] #逆序排列
stock.head()


以上显示了前5行数据,要得到数据的更多信息,可以使用.info()方法。它告诉我们该数据一共有20行,索引是时间格式,日期从2015年1月5日到2015年1月30日。总共有14列,并列出了每一列的名称和数据格式,并且没有缺失值。

stock.info()

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 20 entries, 2015-01-05 to 2015-01-30
Data columns (total 14 columns):
open 20 non-null float64
high 20 non-null float64
close 20 non-null float64
low 20 non-null float64
volume 20 non-null float64
price_change 20 non-null float64
p_change 20 non-null float64
ma5 20 non-null float64
ma10 20 non-null float64
ma20 20 non-null float64
v_ma5 20 non-null float64
v_ma10 20 non-null float64
v_ma20 20 non-null float64
turnover 20 non-null float64
dtypes: float64(14)
memory usage: 2.3 KB

在观察每一列的名称时,我们发现'open'的列名前面似乎与其它列名不太一样,为了更清楚地查看,使用.columns得到该数据所有的列名如下:

stock.columns

Index([' open', 'high', 'close', 'low', 'volume', 'price_change','p_change', 'ma5', 'ma10', 'ma20', 'v_ma5', 'v_ma10', 'v_ma20','turnover'], dtype='object')
于是发现'open'列名前存在多余的空格,我们使用如下方法修正列名。

stock.rename(columns={' open':'open'}, inplace=True)

至此,我们完成了股票数据的导入和清洗工作,接下来将使用可视化的方法来观察这些数据。

4.2 数据观察

首先,我们观察数据的列名,其含义对应如下:

这些指标总体可分为两类:
• 价格相关指标
o 当日价格:开盘、收盘价,最高、最低价
o 价格变化:价格变动和涨跌幅
o 均价:5、10、20日均价
• 成交量相关指标
o 成交量
o 换手率:成交量/发行总股数×100%
o 成交量均量:5、10、20日均量
由于这些指标都是随时间变化的,所以让我们先来观察它们的时间序列图。

4.3 时间序列图

以时间为横坐标,每日的收盘价为纵坐标,做折线图,可以观察股价随时间的波动情况。这里直接使用DataFrame数据格式自带的做图工具,其优点是能够快速做图,并自动优化图形输出形式。

stock['close'].plot(grid=True)


如果我们将每日的开盘、收盘价和最高、最低价以折线的形式绘制在一起,难免显得凌乱,也不便于分析。那么有什么好的方法能够在一张图中显示出这四个指标?答案下面揭晓。

4.4 K线图

相传K线图起源于日本德川幕府时代,当时的商人用此图来记录米市的行情和价格波动,后来K线图被引入到股票市场。每天的四项指标数据用如下蜡烛形状的图形来记录,不同的颜色代表涨跌情况。

Matplotlib.finance模块提供了绘制K线图的函数candlestick_ohlc(),但如果要绘制比较美观的K线图还是要下点功夫的。下面定义了pandas_candlestick_ohlc()函数来绘制适用于本文数据的K线图,其中大部分代码都是在设置坐标轴的格式。

from matplotlib.finance import candlestick_ohlc
from matplotlib.dates import DateFormatter, WeekdayLocator, DayLocator, MONDAY

def pandas_candlestick_ohlc(stock_data, otherseries=None):

# 设置绘图参数,主要是坐标轴
mondays = WeekdayLocator(MONDAY)
alldays = DayLocator()
dayFormatter = DateFormatter('%d')

fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
if stock_data.index[-1] - stock_data.index[0] < pd.Timedelta('730 days'):
weekFormatter = DateFormatter('%b %d')
ax.xaxis.set_major_locator(mondays)
ax.xaxis.set_minor_locator(alldays)
else:
weekFormatter = DateFormatter('%b %d, %Y')
ax.xaxis.set_major_formatter(weekFormatter)
ax.grid(True)

# 创建K线图
stock_array = np.array(stock_data.reset_index()[['date','open','high','low','close']])
stock_array[:,0] = date2num(stock_array[:,0])
candlestick_ohlc(ax, stock_array, colorup = "red", colordown="green", width=0.4)

# 可同时绘制其他折线图
if otherseries is not None:
for each in otherseries:
plt.plot(stock_data[each], label=each)
plt.legend()

ax.xaxis_date()
ax.autoscale_view()
plt.setp(plt.gca().get_xticklabels(), rotation=45, horizontalalignment='right')

plt.show()

运行定义的函数

pandas_candlestick_ohlc(stock)


这里红色代表上涨,绿色代表下跌。

4.5 相对变化量

股票中关注的不是价格的绝对值,而是相对变化量。有多种方式可以衡量股价的相对值,最简单的方法就是将股价除以初始时的价格。

stock['return'] = stock['close'] / stock.close.iloc[0]
stock['return'].plot(grid=True)


第二种方法是计算每天的涨跌幅,但计算方式有两种:

这两者可能导致不同的分析结果,样例数据中的涨跌幅使用的是第一个公式,并乘上了100%。

stock['p_change'].plot(grid=True).axhline(y=0, color='black', lw=2)


为了解决第二种方法中的两难选择,我们引入第三种方法,就是计算价格的对数之差,公式如下:

close_price = stock['close']
log_change = np.log(close_price) - np.log(close_price.shift(1))
log_change.plot(grid=True).axhline(y=0, color='black', lw=2)

4.6 相关关系

在观察了价格的走势之后,我们来看看各指标之间的关系。下面挑选了部分代表性的指标,并使用pandas.scatter_matrix()函数,将各项指标数据两两关联做散点图,对角线是每个指标数据的直方图。

small = stock[['close', 'price_change', 'ma20','volume', 'v_ma20', 'turnover']]
_ = pd.scatter_matrix(small)


图中可以明显发现成交量(volume)和换手率(turnover)有非常明显的线性关系,其实换手率的定义就是:成交量除以发行总股数,再乘以100%。所以下面的分析中我们将换手率指标去除,这里使用了相关性关系来实现数据降维。
上面的散点图看着有些眼花缭乱,我们可以使用numpy.corrcof()来直接计算各指标数据间的相关系数。

small = stock[['close', 'price_change', 'ma20','volume', 'v_ma20']]
cov = np.corrcoef(small.T)
cov
array([[ 1. , 0.30308764, 0.10785519, 0.91078009, -0.37602193],
[ 0.30308764, 1. , -0.45849273, 0.3721832 , -0.25950305],
[ 0.10785519, -0.45849273, 1. , -0.06002202, 0.51793654],
[ 0.91078009, 0.3721832 , -0.06002202, 1. , -0.37617624],
[-0.37602193, -0.25950305, 0.51793654, -0.37617624, 1. ]])

如果觉得看数字还是不够方便,我们继续将上述相关性矩阵转换成图形,如下图所示,其中用颜色来代表相关系数。我们发现位于(0,3)位置的相关系数非常大,查看数值达到0.91。这两个强烈正相关的指标是收盘价和成交量。

img = plt.matshow(cov,cmap=plt.cm.winter)
plt.colorbar(img, ticks=[-1,0,1])
plt.show()


以上我们用矩阵图表的方式在多个指标中迅速找到了强相关的指标。接着做出收盘价和成交量的折线图,因为它们的数值差异很大,所以我们采用两套纵坐标体系来做图。
stock[['close','volume']].plot(secondary_y='volume', grid=True)

观察这两个指标的走势,在大部分时候股价上涨,成交量也上涨,反之亦然。但个别情况下则不成立,可能是成交量受到前期的惯性影响,或者还有其他因素。

4.7 移动平均线

吴军老师曾讲述他的投资经验,大意是说好的投资方式不是做预测,而是能在合适的时机做出合适的应对和决策。同样股市也没法预测,我们能做的是选择恰当的策略应对不同的情况。
好的指标是能驱动决策的。在上面的分析中我们一直没有使用的一类指标是5、10、20日均价,它们又称为移动平均值,下面我们就使用这项指标来演示一个简单的股票交易策略。(警告:这里仅仅是演示说明,并非投资建议。)
为了得到更多的数据来演示,我们使用pandas_datareader直接从雅虎中下载最近一段时间的谷歌股票数据。

import datetime
import pandas_datareader.data as web
#import pandas.io.data as web
# 设置股票数据的时间跨度
start = datetime.datetime(2016,10,1)
end = datetime.date.today()

# 从yahoo中获取google的股价数据。
goog = web.DataReader("GOOG", "yahoo", start, end)

#修改索引和列的名称,以适应本文的分析
goog.index.rename('date', inplace=True)
goog.rename(columns={'Open':'open', 'High':'high', 'Low':'low', 'Close':'close'}, inplace=True)

goog.head()

【备注】:pandas_datareader 库如果不存在,可用pip 安装。

数据中只有每天的价格和成交量,所以我们需要自己算出5日均价和10日均价,并将均价的折线图(也称移动平均线)与K线图画在一起。

goog["ma5"] = np.round(goog["close"].rolling(window = 5, center = False).mean(), 2)
goog["ma20"] = np.round(goog["close"].rolling(window = 20, center = False).mean(), 2)
goog = goog['2017-01-01':]

pandas_candlestick_ohlc(goog, ['ma5','ma20'])


观察上图,我们发现5日均线与K线图较为接近,而20日均线则更平坦,可见移动平均线具有抹平短期波动的作用,更能反映长期的走势。比较5日均线和20日均线,特别是关注它们的交叉点,这些是交易的时机。移动平均线策略,最简单的方式就是:当5日均线从下方超越20日均线时,买入股票,当5日均线从上方越到20日均线之下时,卖出股票。
为了找出交易的时机,我们计算5日均价和20日均价的差值,并取其正负号,作于下图。当图中水平线出现跳跃的时候就是交易时机。

goog['ma5-20'] = goog['ma5'] - goog['ma20']
goog['diff'] = np.sign(goog['ma5-20'])
goog['diff'].plot(ylim=(-2,2)).axhline(y=0, color='black', lw=2)


为了更方便观察,上述计算得到的均价差值,再取其相邻日期的差值,得到信号指标。当信号为1时,表示买入股票;当信号为-1时,表示卖出股票;当信号为0时,不进行任何操作。

goog['signal'] = np.sign(goog['diff'] - goog['diff'].shift(1))
goog['signal'].plot(ylim=(-2,2))


从上图中看出,从今年初到现在,一共有两轮买进和卖出的时机。到目前为止,似乎一切顺利,那么让我们看下这两轮交易的收益怎么样吧。

trade = pd.concat([
pd.DataFrame({"price": goog.loc[goog["signal"] == 1, "close"],
"operation": "Buy"}),
pd.DataFrame({"price": goog.loc[goog["signal"] == -1, "close"],
"operation": "Sell"})
])

trade.sort_index(inplace=True)
trade


上述表格列出了交易日期、操作和当天的价格。但很遗憾地发现,这两轮交易的卖出价都小于买入价,实际上按上述方法交易我们亏本了!!!
你是否很愤怒呢?原来分析到现在,都是假的呀!我之前就警告过,这里的分析只是演示移动平均线策略的思想,而并非真正的投资建议。股票市场是何其的复杂多变,又如何是一个小小的策略所能战胜的呢?
那么这个策略就一无是处吗?非也!如果考虑更长的时间跨度,比如5年、10年,并考虑更长的均线,比如将20日均线和50日均线比较;虽然过程中也有亏损的时候,但赢的概率更大。也就是说,在更长的时间尺度上该策略也是可行的。但即使你赚了,又能跑赢大盘吗?这时候还需用到其他方法,比如合理配置投资比例等。
还是那句话,股市有风险,投资需谨慎。本文不是分析股票的文章,而是借用股票数据来说明数据分析的基本方法,以及演示什么样的指标是好的指标。

参考博客:http://www.jianshu.com/p/ce0e0773c6ec#

第3章 数据处理

在介绍MySQL时,我们了解了其查询功能非常强大,既可单表查询、也可多表查询、还可以实现子查询等等,而且实现起来还非常方便,写几个SQL语句就可轻松搞定。假如某天你想参加大数据方面的竞赛,主办方给你的数据不是数据库的表,而是几个数据文件(这样的情况非常普遍),要求你基于这些数据文件(如文本文件、excel文件等)进行预测或分类等,此时你该如何处理呢?把这些文件导入数据库,然后用SQL进行处理?有时也是方法之一,但,假如主办方不提供数据库呢?实际也没关系!我们可以就数据文件进行处理、进行合并、聚合等操作,甚至可以比SQL做更多事情,如何处理呢?这就是本章将介绍的内容。
本章主要内容:
 合并数据文件
 旋转数据
 新增指标
 聚合数据

3.1 合并数据

在MySQL的多表查询章节中,我们介绍了两表关联问题,具体请看如下图形;
(图3-1 多表关联)

如果现在把表换成数据文件或数据集,该如何实现呢?Python中有类似方法,把连接的关键字有JION改为MERGE一下就可,其它稍作修改,具体请看下表:
(表3-2 两个DataFrame数据集关联格式表)
关联方式 关联语句

下面同时示例来说明以上命令的具体使用。

In [1]: import numpy as np
In [2]: import pandas as pd
In [3]: from pandas import DataFrame
In [4]: import MySQLdb

In [5]: df1=DataFrame({'key':['a','a','b','c','c'],'data1':range(5)},columns=['key','data1'])

In [6]: df2=DataFrame({'key':['a','b','d'],'data2':range(3)},columns=['key','data2'])

In [7]: df1
Out[7]:
key data1
0 a 0
1 a 1
2 b 2
3 c 3
4 c 4

In [8]: df2
Out[8]:
key data2
0 a 0
1 b 1
2 d 2
###内连接的几种方式
In [9]: df3=pd.merge(df1,df2,on='key')
In [10]: df4=pd.merge(df1,df2,on='key',how='inner')
In [11]: df5=pd.merge(df1,df2,left_on='key',right_on='key',how='inner')

In [12]: df5
Out[12]:
key data1 data2
0 a 0 0
1 a 1 0
2 b 2 1
###df1与df2左连接
In [13]: df_l=pd.merge(df1,df2,on='key',how='left')
In [14]: df_l
Out[14]:
key data1 data2
0 a 0 0.0
1 a 1 0.0
2 b 2 1.0
3 c 3 NaN
4 c 4 NaN
###df1与df2右连接
In [15]: df_r=pd.merge(df1,df2,on='key',how='right')
In [16]: df_r
Out[16]:
key data1 data2
0 a 0.0 0
1 a 1.0 0
2 b 2.0 1
3 d NaN 2
###df1与df2进行全连接
In [17]: df_a=pd.merge(df1,df2,on='key',how='outer')
In [18]: df_a
Out[18]:
key data1 data2
0 a 0.0 0.0
1 a 1.0 0.0
2 b 2.0 1.0
3 c 3.0 NaN
4 c 4.0 NaN
5 d NaN 2.0

两个集合连接以后,有些值可能为空或为NaN,NaN值有时计算不方便,我们可以把NaN修改为其它值,如为0值等,如果要修改或补充为0值,该如何操作呢?非常方便,利用DataFrame的fillna函数即可。其使用方法如下:

In [20]: df_a.fillna(0) ##把NaN修改为0
Out[20]:
key data1 data2
0 a 0.0 0.0
1 a 1.0 0.0
2 b 2.0 1.0
3 c 3.0 0.0
4 c 4.0 0.0
5 d 0.0 2.0

3.2 离散数据

上节我们介绍了有时便于数据分析,需要把两个或多个数据集合并在一起,这在大数据的分析或竞赛中是经常干的事。不过有时为便于分析,需要把一些连续性数据离散化或进行拆分,这也是数据分析常用方法,如对年龄字段,可能需转换成年龄段,这样可能更好地对数据的进行分类或预测,这种处理方式往往能提升分类或预测性能,这种方法又称为离散化或新增衍生指标等。这方面的实例在Spark中将介绍。
如何离散化连续性数据?在一般开发语言中,可以通过控制语句来实现,但如果分类较多时,这种方法不但繁琐,效率也比较低。在Pandas中是否有更好方法?如果有,又该如何实现呢?
pandas有现成方法,如cut或qcut等,不需要编写代码,至于如何使用还是通过实例来说明。

In [1]: import numpy as np
In [2]: import pandas as pd
In [3]: from pandas import DataFrame
In [4]: df9=DataFrame({'age':[21,25,30,32,36,40,45,50],'type':['1','2','1','2','1','1','2','2']},columns=['age','type'])

In [5]: level=[20,30,40,50] ##划分为(20,30],(30,40],(40,50]
In [6]: groups=['A','B','C'] ##对应标签为A,B,C
In [7]: df9['age_t']=pd.cut(df9['age'],level,labels=groups) ##新增字段为age_t
In [8]: df10=df9[['age','age_t','type']]
In [9]: df10
Out[9]:
age age_t type
0 21 A 1
1 25 A 2
2 30 A 1
3 32 B 2
4 36 B 1
5 40 B 1
6 45 C 2
7 50 C 2

对连续性字段进行离散化是机器学习常用方法,此外,对一些类型字段,如上例中type字段,含有1,2两种类型,实际上1,2两种类型是平等,它们只是代表不同类型,并无大小区别,如果在回归分析中如果用1、2代入算法中,则与业务含义就不相符了,对这种情况,我们该如何处理呢?
在机器学习中通常把这些分类变量或字段转换为“指标矩阵”或“哑变量矩阵”,具体做法就是,假设该字段或变量有k种取值(上例中type只有2中取值),则可派生出一个k列矩阵,矩阵值为0或1,0表示无对应分类,1表示有对应分类。我们可以编写程序实现,也可用Pandas中get_dummies函数来实现,具体实现请看以下示例。

In [10]: dummies=pd.get_dummies(df10['type'],prefix='type') ##新增列名加前缀
In [11]: dummies
Out[11]:
type_1 type_2
0 1.0 0.0
1 0.0 1.0
2 1.0 0.0
3 0.0 1.0
4 1.0 0.0
5 1.0 0.0
6 0.0 1.0
7 0.0 1.0

In [12]: df10.join(dummies) ###通过位置索引与df10进行关联
Out[12]:
age age_t type type_1 type_2
0 21 A 1 1.0 0.0
1 25 A 2 0.0 1.0
2 30 A 1 1.0 0.0
3 32 B 2 0.0 1.0
4 36 B 1 1.0 0.0
5 40 B 1 1.0 0.0
6 45 C 2 0.0 1.0
7 50 C 2 0.0 1.0

【说明】
这种方法,在SparkML中有专门的算法--独热编码(OneHotEncoder),独热编码将标签指标映射为二值向量,其中最多一个单值。这种编码被用于将种类特征使用到需要连续特征的算法,如逻辑回归等。

3.3 行列旋转

我们平常看到的数据格式大多像数据库中的表,如购买图书的基本信息:
(表3-1 客户购买图书信息)
书名 书代码 购买客户 购买数量

这样的数据比较规范,比较适合于一般的统计分析。但如果我们欲依据这些数据来对客户进行推荐或计算客户的相似度,就需要把以上数据转换为如下格式:
(表3-2 客户购买图书的对应关系)

我们观察一下不难发现,把表3-1中书代码列旋转为行就得到表3-2数据。如何实现行列的互换呢?编码能实现,但比较麻烦,还好,pandas提供了现成的方法或函数,如stack、unstack、pivot_table函数等。以下以pivot_table为例说明具体实现。

In [1]: import numpy as np
In [2]: import pandas as pd
In [3]: from pandas import DataFrame
In [4]: df=DataFrame({'book_code':['p211','p211','sp2','sp2','hd28','hd28'],'user':['A','B','A','B','A','C'],'nums':[1,1,1,1,1,1]},columns=['book_code','user','nums'])

In [5]: df
Out[5]:
book_code user nums
0 p211 A 1
1 p211 B 1
2 sp2 A 1
3 sp2 B 1
4 hd28 A 1
5 hd28 C 1
###实现行列互换,把book_code列转换为行或索引
In [6]: pd.pivot_table(df,values='nums',index='user',columns='book_code')
Out[6]:
book_code hd28 p211 sp2
user
A 1.0 1.0 1.0
B NaN 1.0 1.0
C 1.0 NaN NaN
####转换后,出现一些NaN值或空值,我们可以把NaN修改为0
In [7]: pd.pivot_table(df,values='nums',index='user',columns='book_code',fill_value=0)
Out[7]:
book_code hd28 p211 sp2
user
A 1 1 1
B 0 1 1
C 1 0 0

3.4数据分组与聚合

MySQL中我们介绍了分组聚合计算,具体通过函数Group by对数据进行分组统计,参与分组的字段可以一个或多个,分组后可以进行分组汇总、求平均值等计算。在Pandas的DataFrame格式的数据该如何进行分组及聚合呢?与MySQL非常类似,我们可以像操作数据库中表一样操作DataFrame格式数据。这或许得益于DataFrame结构类似于数据库中表。
具体如何实现,我们还是通过示例来说明。

In [4]: df=DataFrame({
...: 'type1': ['a','b','a','c','b'],
...: 'type2': ['mon','tues','wed','mon','wed'],
...: 'data1': np.random.randn(5),
...: 'data2': np.random.randn(5)
...: })

In [5]: df
Out[5]:
data1 data2 type1 type2
0 -0.188844 -0.011052 a mon
1 0.514468 0.529642 b tues
2 0.798027 0.536333 a wed
3 0.512078 -1.364139 c mon
4 0.013959 1.224628 b wed

In [6]: df.groupby('type1').mean() ##根据type1分组,然后求平均值
Out[6]:
data1 data2
type1
a 0.304591 0.262641
b 0.264213 0.877135
c 0.512078 -1.364139
##根据type1,type2分组,然后求平均值
In [7]: df.groupby(['type1','type2']).mean()
Out[7]:
data1 data2
type1 type2
a mon -0.188844 -0.011052
wed 0.798027 0.536333
b tues 0.514468 0.529642
wed 0.013959 1.224628
c mon 0.512078 -1.364139

第2章 操作数据库

人工智能最重要的动力就是数据,没有充分数据的人工智能是毫无意义的,数据越大人工智能将越聪明,所以数据是非常重要的。因此整合各种数据的能力也是各种开发工具非常重视的,Python在这方面功能很强大。这一章我们将介绍如何利用Python存取数据库中的数据。
目前企业数据大都存储在数据库中,如MySQL、Oracle、DB2等关系型数据库,这些数据库目前使用非常广泛,由于其独特优势,未来还将大量使用;但随着数据量、数据种类的不断增长,目前非关系型数据库也越来越普及了,如MongoDB、Redis、HBase等等,这些数据库有时又称为NoSQL型数据库。所有这些数据是目前大数据的主要源头,因此,如何使用抽取、整合、处理及分析这些数据是非常重要。
这章主要内容:
 操作MySQL
 操作MongoDB

2.1 连接MySQL

Python使用MySQL非常简单,先导入python有关mysql的驱动模块,然后建立与数据库的连接即可。
以下通过实例来说明。

In [2]: import numpy as np
In [3]: import pandas as pd
In [4]: from pandas import DataFrame
In [5]: import MySQLdb

In [6]: conn= MySQLdb.connect(host='slave02',port=3306,user='feigu', passwd='feigu', db='testdb',charset='utf8')

In [7]: data= pd.read_sql('select * from stud_score', conn)
In [8]: df=DataFrame(data)

In [9]: df.count()

2.2 存取MongoDB

MongoDB 是一个基于分布式文件存储的数据库。由C++语言编写。它是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。 它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo可以有多个数据库,每个数据库中数据或文档被分组存储在数据集合(Collection)中。每个集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档。集合的概念类似关系型数据库(RDBMS)里的表(table),不同的是它不需要定义任何模式(schema)。
以下通过一个实例来说明,如何连接mongodb,然后创建集合,并往集合中插入记录,并查询该条记录或文档。

In [1]: from pymongo import MongoClient
In [2]: conn=MongoClient(host='localhost',port=27017)
In [3]: testdb=conn.testdb
In [4]: coll=testdb.items
In [5]:
item={
"type":"operation",
"itemno":"10000",
"contents":{
"python":"2.7.11",
"spark":"2.0"
},
"date":"201611"
}
In [6]: coll.insert(item)

In [7]: coll.find_one()
Out[7]:
{u'_id': ObjectId('5832b9bfeb0a1957669cdf68'),
u'contents': {u'python': u'2.7.11', u'spark': u'2.0'},
u'date': u'201611',
u'itemno': u'10000',
u'type': u'operation'}

当然也可同时往集合插入多条记录:

###多条记录格式[{},{}]
In [8]: item1=[{
...: "type":"operation",
...: "itemno":"20000",
...: "contents":{
...: "Hadoop":"2.7.0",
...: "HBase":"2.0"
...: },
...: "date":"201611"
...: } ,
...: {
...: "type":"operation",
...: "itemno":"30000",
...: "contents":{
...: "Jave":"1.8.0",
...: "Scala":"2.10.0"
...: },
...: "date":"201611"
...: }
In [9]: coll.insert(item1) ###插入多条记录
Out[9]: [ObjectId('5833c36ceb0a197a4306abea'), ObjectId('5833c36ceb0a197a4306abeb')]
In [10]: coll.find({"type":"operation"})
Out[10]: In [10]: for doc in coll.find({"type":"operation"}):
print doc ###查询多条记录
....:
{u'date': u'201611', u'itemno': u'10000', u'_id': ObjectId('5832b9bfeb0a1957669cdf68'), u'type': u'operation', u'contents': {u'python': u'2.7.11', u'spark': u'2.0'}}
{u'date': u'201611', u'itemno': u'20000', u'_id': ObjectId('5833c36ceb0a197a4306abea'), u'type': u'operation', u'contents': {u'HBase': u'2.0', u'Hadoop': u'2.7.0'}}
{u'date': u'201611', u'itemno': u'30000', u'_id': ObjectId('5833c36ceb0a197a4306abeb'), u'type': u'operation', u'contents': {u'Jave': u'1.8.0', u'Scala': u'2.10.0'}}

关系型数据可以使用DataFrame函数转换为DataFrame格式,Mongodb格式的数据能否转换?如果能,该如何转换呢?可以的,而且方式与关系型数据库类型,请看以下示例:

In [11]: import numpy as np
In [12]: from pandas import Series,DataFrame

In [13]: query1=coll.find({"type":"operation"}) ##定义查询结果
In [14]: column=['type','itemno','contents','date'] ##定义显示列名(关键字)
In [15]: df1=DataFrame(list(query1),columns=column)

In [16]: df1
Out[16]:
type itemno contents date
0 operation 10000 {u'python': u'2.7.11', u'spark': u'2.0'} 201611
1 operation 20000 {u'HBase': u'2.0', u'Hadoop': u'2.7.0'} 201611
2 operation 30000 {u'Jave': u'1.8.0', u'Scala': u'2.10.0'} 201611

第12章 化繁为简的高手----正则化方法


正则化在机器学习、深度学习中运用非常广泛,机器学习中在回归、降维时经常使用,在目前很火的深度学习中为避免过拟合也常使用。为何它作用如此之大,它的大致原理和作用是啥?接下来我们来做个简单介绍。

12.1 正则化简介

在机器学习中,很多被显式地设计用来减少测试误差的策略,统称为正则化。正则化旨在减少泛化误差而不是训练误差。目前有许多正则化策略。有些策略向机器学习模型添加限制参数值的额外约束,如L1、L2等;有些策略向目标函数增加额外项对参数值进行软约束,如作为约束的范数惩罚等。这些惩罚和约束通常用来使模型更简单,以便提高模型的泛化能力,当然也有些正则化,如集成方法,是为了结合多个假说来更好解释训练数据。
正则化是机器学习领域的中心问题之一,其重要性只有优化问题才能与之匹敌。
正则化的作用、原理,我们还是先从几张图来说明吧,图形直观明了,易说明问题。

 

(图1 测试误差与训练误差)

(图2 根据房屋面积预测房价几个回归模型)

这两个图说明了:
(1)、图1说明了,模型不是越复杂越好,训练精度随复杂度逐渐变小;测试误差刚开始随着模型复杂度而变小,但当复杂度超过某点之后,测试误差不但不会变小,反而会越来越大;
(2)、图2是对图1的一个示例说明。

上面我们讲了正则化的主要目的就是提高模型的泛化能力,或降低模型的测试误差。在实际模型中,它是如何实现的呢?
我们以图2中最右边这个图来说,其模型是一个4次多项式,因它把一些噪音数据也包括进来了,所以导致模型很复杂,实际上房价与房屋面积应该是2次多项式函数,如图2中间这个图。

当然这里我们取10000只是用来代表一个"大值",现在,如果我们要最小化这个函数,那么为了最小化这个新的代价函数,我们要让 θ3 和 θ4 尽可能小。因为,如果你在原有代价函数的基础上加上 10000 乘以 θ3 这一项 ,那么这个新的代价函数将变得很大,所以,当我们最小化这个新的代价函数时, 我们将使 θ3 的值接近于 0,同样 θ4 的值也接近于 0,就像我们忽略了这两个值一样。如果我们做到这一点( θ3 和 θ4 接近 0 ),那么我们将得到一个近似的二次函数。如下图:

图3 利用正则化提升模型泛化能力

12.2 机器学习中常用的两种正则化策略

机器学习中几乎都可以看到损失函数后面会添加一个额外项,常用的额外项一般有两种,一般英文称作ℓ1-norm和ℓ2-norm,中文称作L1正则化和L2正则化,或者L1范数和L2范数。
L1正则化和L2正则化可以看做是损失函数的惩罚项。所谓『惩罚』是指对损失函数中的某些参数做一些限制。对于线性回归模型,使用L1正则化的模型建叫做Lasso回归,使用L2正则化的模型叫做Ridge回归(岭回归)。下式是Python中Lasso回归的损失函数,式中加号后面一项α||w||1即为L1正则化项。

下式是Python中Ridge回归的损失函数,式中加号后面一项α||w||22即为L2正则化项。

一般回归分析中回归w表示特征的系数,从上式可以看到正则化项是对系数做了处理(限制)。L1正则化和L2正则化的说明如下:
L1正则化是指权值向量w中各个元素的绝对值之和,通常表示为〖||w||〗_1
L2正则化是指权值向量w中各个元素的平方和然后再求平方根(可以看到Ridge回归的L2正则化项有平方符号),通常表示为〖||w||〗_2
一般都会在正则化项之前添加一个系数,Python中用α表示,一些文章也用λ表示。这个系数需要用户指定。

12.3.正则化的主要作用

L1正则化和L2正则化的作用:
L1正则化可以产生稀疏权值矩阵,即产生一个稀疏模型,可以用于特征选择
L2正则化可以防止模型过拟合(overfitting);一定程度上,L1也可以防止过拟合
1)L1正则化和特征选择
假设有如下带L1正则化的损失函数:

图4 L1正则化

同样可以画出他们在二维平面上的图形,如下:

图5 L2正则化

2)L2正则化和过拟合
拟合过程中通常都倾向于让权值尽可能小,最后构造一个所有参数都比较小的模型。因为一般认为参数值小的模型比较简单,能适应不同的数据集,也在一定程度上避免了过拟合现象。可以设想一下对于一个线性回归方程,若参数很大,那么只要数据偏移一点点,就会对结果造成很大的影响;但如果参数足够小,数据偏移得多一点也不会对结果造成什么影响,提高模型的鲁棒性。
那为什么L2正则化可以获得值很小的参数?
以线性回归中的梯度下降法为例。假设要求的参数为θ,h_θ (x)是我们的假设函数,那么线性回归的代价函数如下:

 

其中λ就是正则化参数。从上式可以看到,与未添加L2正则化的迭代公式相比,每一次迭代,θj都要先乘以一个小于1的因子(如1-αλ/m),从而使得θj不断减小,因此总得来看,θ是不断减小的。
L1正则化一定程度上也可以防止过拟合。L1可以是权重系数逼近于0或等于0,这样可以简化模型,提高模型的泛化能力,从而达到与L2正则化类似的效果。

12.4 逻辑回归中使用正则化实例分析

这里我们以逻辑回归实例来具体说明,在算法中如何利用正则化策略惩罚参数值的。

因此,减少正则化参数倒数C的值就相当于增强正则化的强度,以下我们通过绘制对四个权重系数进行L2正则化的图像予以展示。

1)导入数据

from sklearn import datasets
import numpy as np
import matplotlib.pyplot as plt

iris = datasets.load_iris()
X = iris.data
y = iris.target

2)把数据集划分为训练集和测试集,划分比例为7:3

from sklearn.cross_validation import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=0)

from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)

3)对逻辑回归加入正则化项,并对C的不同取值,对权重的影响图形化。

from sklearn.linear_model import LogisticRegression

weights, params = [], []
for c in range(-5, 5):
lr = LogisticRegression(C=10 ** c, random_state=0)
lr.fit(X_train_std, y_train)
weights.append(lr.coef_[1])
params.append(10**c)

weights = np.array(weights)

plt.plot(params, weights[:, 0], linestyle='-',
label='sepal width')
plt.plot(params, weights[:, 1], linestyle='-.',
label='sepal width')

plt.plot(params, weights[:, 2],linestyle=':',
label='petal length')
plt.plot(params, weights[:, 3], linestyle='--',
label='petal width')

plt.ylabel('weight coefficient')
plt.xlabel('C')
plt.legend(loc='upper left')
plt.xscale('log')

plt.show()

图6 利用正则化控制逻辑回归权重参数值

从上图我们不难看出,如果我们减小参数C的值,即增强正则化项的强度,可以使权重系统逐渐收缩。

12.5 Dropout正则化简介

Dropout是Srivastava等人在2014年的一篇论文中提出的一种针对神经网络模型的正则化方法 Dropout: A Simple Way to Prevent Neural Networks from Overfitting。
Dropout的做法是在训练过程中按一定比例(比例参数可设置)随机地忽略一些神经元。这些神经元被随机地“抛弃”了。也就是说它们在正向传播过程中对于下游神经元的贡献效果暂时消失了,反向传播时该神经元也不会有任何权重的更新。
随着神经网络模型不断地学习,神经元的权值会与整个网络的上下文相匹配。神经元的权重针对某些特征进行调优,具有一些特殊化。周围的神经元则会依赖于这种特殊化,如果过于特殊化,模型会因为对训练数据过拟合而变得脆弱不堪。神经元在训练过程中的这种依赖于上下文的现象被称为复杂的协同适应(complex co-adaptations)。
这么做的效果就是,网络模型对神经元特定的权重不那么敏感。这反过来又提升了模型的泛化能力,不容易对训练数据过拟合。
Dropout训练的集成包括所有从基本的基础网络除去非输出单元形成 子网络,如在图7所示。


图 7 基础网络Dropout为多个子网络

Dropout训练由所有子网络组成的集成,其中子网络通过从基本网络中删除非输出单元构 建。我们从具有两个可见单元和两个隐藏单元的基本网络开始。这四个单元有十六个可能的子集。 右图展示了从原始网络中丢弃不同的单元子集而形成的所有十六个子网络。在这个小例子中,所 得到的大部分网络没有输入单元或没有从输入连接到输出的路径。当层较宽时,丢弃所有从输入 到输出的可能路径的概率变小,所以这个问题对于层较宽的网络不是很重要。

较先进的神经网络基于一系列仿射变换和非线性变换,我 们可以将一些单元的输出乘零就能有效地删除一个单元。这个过程需要对模型一些 修改,如径向基函数网络,单元的状态和参考值之间存在一定区别。为了简单起见, 我们在这里提出乘零的简单Dropout算法,但是它被简单地修改后,可以与从网络中 移除单元的其他操作一起工作。
回想一下使用Bagging学习,我们定义 k 个不同的模型,从训练集有替换采样 构造 k 个不同的数据集,然后在训练集 i 上训练模型 i。Dropout的目标是在指数 级数量的神经网络上近似这个过程。具体来说,训练中使用Dropout,我们使用基 于minibatch的学习算法和小的步长,如梯度下降等。我们每次在minibatch加载一个 样本,然后随机抽样应用于网络中所有输入和隐藏单元的不同二值掩码。对于每个 单元,掩码是独立采样的。掩码值为 1 的采样概率(导致包含一个单元)是训练开 始前固定一个超参数。它不是模型当前参数值或输入样本的函数。通常一个输入单 元包括的概率为 0.8,一个隐藏单元包括的概率为 0.5。然后,我们运行之前一样的 前向传播、反向传播以及学习更新。图8说明了在Dropout下的前向传播。

图 8
在使用Dropout的前馈网络中前向传播的示例。(顶部) 在此示例中,我们使用具有两个输入 单元,具有两个隐藏单元的隐藏层以及一个输出单元的前馈网络。(底部) 为了执行具有Dropout的 前向传播,我们随机地对向量 μ 进行采样,其中网络中的每个输入或隐藏单元对应一项。μ 中的 每项都是二值的且独立于其他项采样。每项为 1 的概率是超参数,对于隐藏层通常为 0.5,对于输 入通常为 0.8。网络中的每个单元乘以相应的掩码,然后正常地继续通过网络的其余部分前向传 播。这相当于从图7中随机选择一个子网络并通过它前向传播。

12.6 使用Dropout的小技巧

1)通常丢弃率控制在20%~50%比较好,可以从20%开始尝试。如果比例太低则起不到效果,比例太高则会导致模型的欠学习。
2)在大的网络模型上应用。当dropout用在较大的网络模型时更有可能得到效果的提升,模型有更多的机会学习到多种独立的表征。
3)在输入层(可见层)和隐藏层都使用dropout。在每层都应用dropout被证明会取得好的效果。
4)增加学习率和冲量。把学习率扩大10~100倍,冲量值调高到0.9~0.99.
5)限制网络模型的权重。大的学习率往往导致大的权重值。对网络的权重值做最大范数正则化等方法被证明会提升效果。

12.7 小结

L1、L2是我们常见正则化方式,正则化在机器学习中运用非常广泛,而且功能很强大,经常用来提升模型的泛化能力,此外,正则化策略还包括dropout、数据集增强等,甚至提前终止、集成方法、参数绑定与共享等都与正则化有千丝万缕的关系。其中有些正则化策略这里不展开来说了,后续在深度学习中我们会介绍。

本章参考以下博客或书:
http://www.cnblogs.com/jianxinzhou/p/4083921.html
http://blog.csdn.net/jinping_shi/article/details/52433975
《Python机器学习》塞巴斯蒂安.拉施卡 著

第11章 神奇的SVM及实例分析

11.1 SVM简介

支持向量机(SupportVector Machines),在处理线性数据集、非线性数据集都有较好效果,在机器学习或者模式识别领域可是无人不知,无人不晓。八九十年代的时候,和神经网络一决雌雄,独领风骚几十年,甚至有人把神经网络八九年代的再度沉寂归功于它。
它的强大或神奇与它采用的相关技术不无关系,如最大类间隔、松弛变量、核函数等等(这些技术本章都会介绍),使用这些技术使其在众多机器学习方法中脱颖而出。虽然几十年过去了,但风采依旧,究其原因:与其高效、简洁、易用的特点分不开,其中一些处理思想与当今深度学习技术有很大关系,如其用核方法解决非线性数据集分类问题,很像带隐含层的神经网络,可以说两者有同工异曲之妙。
分类的方法有很多,如线性回归、逻辑回归、决策树、贝叶斯等等,分类的目的是学会一个分类函数或分类模型(或者叫做分类器),该模型能把数据集中的数据项映射到给定类别中的某一个,对新数据通过这个映射,预测其类别。
支持向量机进行分类,目的也是一样:得到一个分类器或分类模型,不过它的分类器一个超平面(如果数据集是二维,这个超平面就是直线,三维数据集,就是平面,以此类推),这个超平面把样本一分为二,当然,这种划分不是简单划分,需要使正例和反例之间的间隔最大。间隔最大,其泛化能力就最强。如何得到这样一个超平面?下级我们通过用一个二维空间例子来说明。

11.2 分类间隔最大的超平面

何为超平面?哪种超平面是我们需要?,我们首先看下图的几个超平面或直线。

如何获取最大化分类间隔?分类算法优化目标通常是最小化分类误差,但对SVM而言,我们的优化的目标是最大化分类间隔。所谓间隔是指两个分离的超平面间的距离,其中最靠近超平面的训练样本又称为支持向量(support vector)。以下我们通过一个二维空间的简单实例来进一步说明。
假设在一个二维空间,分布下例这些点(有些是圆点,有些是方块)。



所以我们的问题就变成:

这是个凸二次规划问题。什么叫凸?凸集是指这样一些点的集合,其中任取两个点连一条直线,这条线上的点仍然在这个集合内部,因此说“凸”。例如下图,对于凸函数(在数学表示上,满足约束条件是仿射函数,也就是线性的Ax+b的形式)来说,局部最优就是全局最优,但对非凸函数来说就不是了。二次表示目标函数是自变量的二次函数。


既然是凸二次规划问题,就可以通过一些现成的 QP (Quadratic Programming) 的优化工具来得到最优解。所以,我们的问题到此为止就算全部解决了。虽然这个问题确实是一个标准的 QP 问题,但是它也有它的特殊结构,通过 Lagrange Duality 变换到对偶变量 (dual variable) 的优化问题之后,可以找到一种更加有效的方法来进行求解,而且通常情况下这种方法比直接使用通用的 QP 优化包进行优化要高效得多。也就说,除了用解决QP问题的常规方法之外,还可以应用拉格朗日对偶性,通过求解对偶问题得到最优解,这就是线性可分条件下支持向量机的对偶算法,这样做的优点在于:一是对偶问题往往更容易求解;二者可以自然的引入核函数,进而推广到非线性分类问题。至于对偶问题及其优化这里就不展开来说了,有兴趣的读者可以参考有关资料。

11.3使用松弛向量解决非线性可分问题

实际数据集往往很难找到一条直线或一个超平面就可一分为二,或者这些数据集是线性可分的。如果出现非线性数据集,我们该如何处理呢?这里我们介绍一种所谓软间隔的分类方法,这种方法由Vladimir Vapnik在1995年引入,其基本思想是引入一个松弛系数ξ,放松线性约束的条件,以便在适当的罚项成本下,对错误分类的情况优化时也能收敛。
为了处理这种情况,我们允许数据点在一定程度上偏离超平面。也就是允许一些点跑到H1和H2之间,也就是他们到分类面的间隔会小于1。如下图:


其中C是离群点的权重,引入非负参数ξi后(称为松弛变量),就允许某些样本点的函数间隔小于1,即在最大间隔区间里面,或者函数间隔是负数,即样本点在对方的区域中。而放松限制条件后,我们需要重新调整目标函数,以对离群点进行处罚,目标函数后面加上的第二项就表示离群点越多,目标函数值越大,而我们要求的是尽可能小的目标函数值。通过变量C我们可以控制对错误分类的惩罚程度。C越大表明离群点对目标函数影响越大,也就是越不希望看到离群点。这时候,间隔也会很小。我们看到,目标函数控制了离群点的数目和程度,使大部分样本点仍然遵守限制条件,如下图所示:

至此,我们已经了解了线性SVM的基本概念,主要原理,接下来我们通过一个实例来演示一个SVM模型对鸢尾花数据集中的样本进行分类。

11.4 SVM分类实例

接下来我们以鸢尾花卉数据集(lris)样本,使用SVM对其进行分类,lris是一类多重变量分析的数据集。通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三个种类中的哪一类。
1)导入数据
使用sklearn中导入数据的库(datasets)进行数据的导入,为方便可视化起见,这里我们花瓣长度,花瓣宽度为两个特征,并由此构建一个特征矩阵X,同时将对应花的类标赋给向量Y,具体实现如下:

from sklearn import datasets
import numpy as np

iris = datasets.load_iris()
X = iris.data[:, [2, 3]]
y = iris.target

2)对数据进行标准化处理

from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
sc.fit(X)
X_std = sc.transform(X)

4)为更好的显示划分后的效果,这里我们先定义几个函数,用来图形化数据。

from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
import warnings

def versiontuple(v):
return tuple(map(int, (v.split("."))))

def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02):

# setup marker generator and color map
markers = ('s', 'x', 'o', '^', 'v')
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(y))])

# plot the decision surface
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
np.arange(x2_min, x2_max, resolution))
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
Z = Z.reshape(xx1.shape)
plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())

for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
alpha=0.8, c=cmap(idx),
marker=markers[idx], label=cl)

5)利用SVM对对数据集进行分类,这里使用了松弛变量来处理非线性数据集。

%matplotlib inline
from sklearn.svm import SVC
import matplotlib.font_manager as fm ###便于中文显示
myfont = fm.FontProperties(fname='/home/hadoop/anaconda3/lib/python3.6/site-packages/matplotlib/mpl-data/fonts/ttf/simhei.ttf')

svm = SVC(kernel='linear', C=1.0, random_state=0)
svm.fit(X_std, y)

plot_decision_regions(X_std, y,classifier=svm)
plt.xlabel("标准化处理后花瓣长度",fontproperties=myfont,size=12)
plt.ylabel("标准化处理后花瓣宽度",fontproperties=myfont,size=12)
plt.legend(loc='upper left')
plt.tight_layout()

plt.show()

11.5在 SVM中应用核函数

上节我们介绍了使用松弛变量处理非线性数据集(含有一些离群点)问题,效果还不错。但有些数据集其分布就是非线性可分的,如比较典型的“异或”问题,遇到这种数据集,我们该如何处理呢?显然使用松弛变量难以达到满意效果,不过没有关系,此时,我们可以使用SVM的一种强大而神奇的核函数法。通过核函数我们可以把线性不可分数据集转换为线性可分(在更高维度空间中)。
接下来我们简单介绍一下核函数或核方法。
我们先通过一个图及一个简单实例,先来大致了解一下SVM的核威力。我们先一个图,给大家一点感性认识:

图1是一个二维空间上的一些散点分布图,通过一个核函数ϕ:
ϕ(x1,x2)=(z1,z2,z3)=(x1,x2,〖x1〗^2+〖x2〗^2)
把一个在二维空间线性不可分的数据集映射到三维空间(图2)后,就可以线性可分了(其中超平面可以把数据分成上下两部分)。

这是核方法的一个直观认识,下面我们使用scikit-learn包的SVM类,具体实现一个核方法。
1)在二维空间,生成一个类似‘异或’的数据集

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

np.random.seed(0)
X_xor = np.random.randn(200, 2)
y_xor = np.logical_xor(X_xor[:, 0] > 0,
X_xor[:, 1] > 0)
y_xor = np.where(y_xor, 1, -1)

plt.scatter(X_xor[y_xor == 1, 0],
X_xor[y_xor == 1, 1],
c='b', marker='x',
label='1')
plt.scatter(X_xor[y_xor == -1, 0],
X_xor[y_xor == -1, 1],
c='r',
marker='s',
label='-1')

plt.xlim([-3, 3])
plt.ylim([-3, 3])
plt.legend(loc='best')
plt.tight_layout()
plt.show()


显然,对这个数据集是线性不可分的,即无法找到一条直线或超平面,把这个数据集划分为两类。
接下来我们采用SVM类中一个高斯核或径向基函数核(Radial Basis Function Kernel,RBF Kernel ),把数据映射到一个高维空间,然后利用SVM对其进行分类或划分。

svm = SVC(kernel='rbf', random_state=0, gamma=0.10, C=10.0)
svm.fit(X_xor, y_xor)
plot_decision_regions(X_xor, y_xor,
classifier=svm)

plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

从以上图可以看到,SVM比较完美的把不同类型的点划分成了两部分。

核函数我们可以把它理解为把低维数据集映射为高维数据集的映射或相似函数。接下来我们看一下核函数一般形式及特点、常用核函数。
设xi,xj∈X,X属于R(n)空间,非线性函数Φ实现输入空间X到特征空间F的映射,其中F属于R(m),n<<m。根据核函数技术有:
K(xi,xj) =<Φ(xi),Φ(xj) >
其中:<, >为内积,K(xi,xj)为核函数。从上式可以看出,核函数将m维高维空间的内积运算转化为n维低维输入空间的核函数计算,从而巧妙地解决了在高维特征空间中计算的“维数灾难”等问题,从而为在高维特征空间解决复杂的分类或回归问题奠定了理论基础。
特点编辑
核函数方法的广泛应用,与其特点是分不开的:
(1)核函数的引入避免了“维数灾难”,大大减小了计算量。而输入空间的维数n对核函数矩阵无影响,因此,核函数方法可以有效处理高维输入。
(2)无需知道非线性变换函数Φ的形式和参数.
(3)核函数的形式和参数的变化会隐式地改变从输入空间到特征空间的映射,进而对特征空间的性质产生影响,最终改变各种核函数方法的性能。
(4)核函数方法可以和不同的算法相结合,形成多种不同的基于核函数技术的方法,且这两部分的设计可以单独进行,并可以为不同的应用选择不同的核函数和算法。
常见分类编辑
核函数的确定并不困难,满足Mercer定理的函数都可以作为核函数。常用的核函数有:线性核函数,多项式核函数,径向基核函数,Sigmoid核函数等,这些核函数的表达式如下:

参考网站:http://blog.csdn.net/zouxy09/article/details/17291543
参考书:Python机器学习

第10章 K-means聚类算法及实例

10.1 K-means简介

机器学习中有两类的大问题,一个是分类,一个是聚类。
分类是根据一些给定的已知类别标识的样本,训练某种学习机器,使它能够对未知类别的样本进行分类。这属于监督学习。而聚类指事先并不知道任何样本的类别标识,希望通过某种算法来把一组未知类别的样本划分成若干类别,这在机器学习中被称作无监督学习。在本章,我们关注其中一个比较简单的聚类算法:k-means算法。

10.2 K-means 算法

k-means算法是一种很常见的聚类算法,它的基本思想是:
(1)适当选择k个类的初始中心
(2)在第i次迭代中,对任意一个样本,求其到K个中心的距离,将该样本归到距离 最短的中心所在的类
(3)利用均值等方法更新该类的中心值
(4)对于所有的K个聚类中心,如果利用(2)(3)的迭代法更新后,值保持不变, 则迭代结束,否则继续迭代。
最后结果是同一类簇中的对象相似度极高,不同类簇中的数据相似度极低。

10.3 K-means简单实例

假定我们有如下9个点:
A1(2, 10) A2(2, 5) A3(8, 4) A4(5, 8) A5(7, 5) A6(6, 4) A7(1, 2) A8(4, 9)
现希望分成3个聚类(即k=3)
初始化选择 A1(2, 10), A4(5, 8) ,A7(1, 2)为聚类中心点,假设两点距离定义为ρ(a, b) = |x2 – x1| + |y2 – y1| . (当然也可以定义为其它格式,如欧氏距离)
第一步:选择3个聚类中,分别为A1,A4,A7

这些点的分布图如下:

第二步:计算各点到3个类中心的距离,那个点里类中心最近,就把这个样本点
划归到这个类。选定3个类中心(即:A1,A4,A7),如下图:

对A1点,计算其到每个cluster 的距离
A1->class1 = |2-2|+|10-10}=0
A1->class2 = |2-5|+|10-8|=5
A1->class3 = |2-1|+|10-2|=9
因此A1 属于cluster1,如下图:

按照类似方法,算出各点到聚类中心的距离,然后按照最近原则,把样本点放在那个族中。如下表格:

根据距离最短原则,样本点的第一次样本划分,如下图:

第三步:求出各类中样本点的均值,并以此为类的中心。
cluster1只有1个点,因此A1为中心点
cluster2的中心点为 ( (8+5+7+6+4)/5,(4+8+5+4+9)/5 )=(6,6)。注意:这个点并非样本点。
cluster3的中心点为( (2+1)/2, (5+2)/2 )= (1.5, 3.5),
新族的具体请看下图中x点:

第四步:计算各样本点到各聚类中心的距离,重复以上第二、第三步,把样本划分到新聚类中,如下图:

持续迭代,直到前后两次迭代不发生变化为止,如下:

10.4 sklearn.K-means的实例

初始化聚类中心比较有讲究,上面这个实例我们是人工指定,A1、A4、A7为初始化的聚类中心;如果K值选择不当或聚类中心选择不当,则可能导致聚类效果不佳。因此,在实际项目中往往采用随机选择K个聚类中心,另外,K的选取也是很重要的,K的选取我们也有比较好的方法,如肘(elbow)方法、轮廓图(silhouette plot)方法等,这里就不展开来说,有兴趣的读者可以查阅相关资料。

%matplotlib inline
import numpy as np #科学计算包
import matplotlib.pyplot as plt #python画图包

from sklearn.cluster import KMeans #导入K-means算法包
from sklearn.datasets import make_blobs

import matplotlib.font_manager as fm ###便于中文显示
myfont = fm.FontProperties(fname='/home/hadoop/anaconda3/lib/python3.6/site-packages/matplotlib/mpl-data/fonts/ttf/simhei.ttf')

plt.figure(figsize=(12, 12))

'''''
make_blobs函数是为聚类产生数据集
产生一个数据集和相应的标签
n_samples:表示数据样本点个数,默认值100
n_features:表示数据的维度,默认值是2
centers:产生数据的聚类中心点,默认值3
init:采用随机还是k-means++等(可使聚类中心尽可能的远的一种方法)
cluster_std:数据集的标准差,浮点数或者浮点数序列,默认值1.0
center_box:中心确定之后的数据边界,默认值(-10.0, 10.0)
shuffle :洗乱,默认值是True
random_state:随机生成器的种子

'''

n_samples = 1500
random_state = 10
X, y = make_blobs(n_samples=n_samples, random_state=random_state)

# Incorrect number of clusters
y_pred = KMeans(n_clusters=2, random_state=random_state).fit_predict(X)

plt.subplot(221) #在2图里添加子图1
plt.scatter(X[:, 0], X[:, 1], c=y_pred) #scatter绘制散点
plt.title("不恰当的聚类数",fontproperties=myfont,size=12) #加标题

# Different variance
X_varied, y_varied = make_blobs(n_samples=n_samples,
cluster_std=[1.0, 2.5, 0.5],
random_state=random_state)
y_pred = KMeans(n_clusters=3, init='random',random_state=random_state).fit_predict(X_varied)

plt.subplot(222)#在2图里添加子图3
plt.scatter(X_varied[:, 0], X_varied[:, 1], c=y_pred)
plt.title("方差不同的散点",fontproperties=myfont,size=12)

plt.show()

10.5 Mini Batch K-Means算法

对K-means算法,如果原数据很多,其计算量非常大,对于大数据是否有更好的方法呢?在计算梯度时,我们用整个数据集计算,如果数据量大的话,我们可以采用随机梯度的方法。同理,我们在处理聚类算法时,也有类似的方法,这就是scikit-learn提供的Mini Batch K-Means算法,其具体方法如下:
Mini Batch K-Means算法是K-Means算法的变种,采用小批量的数据子集减小计算时间,同时仍试图优化目标函数,这里所谓的小批量是指每次训练算法时所随机抽取的数据子集,采用这些随机产生的子集进行训练算法,大大减小了计算时间,与其他算法相比,减少了k-均值的收敛时间,小批量k-均值产生的结果,一般只略差于标准算法。
该算法的迭代步骤有两步:
1:从数据集中随机抽取一些数据形成小批量,把他们分配给最近的质心
2:更新质心
与K均值算法相比,数据的更新是在每一个小的样本集上。对于每一个小批量,通过计算平均值得到更新质心,并把小批量里的数据分配给该质心,随着迭代次数的增加,这些质心的变化是逐渐减小的,直到质心稳定或者达到指定的迭代次数,停止计算
Mini Batch K-Means比K-Means有更快的 收敛速度,但同时也降低了聚类的效果,但是在实际项目中却表现得不明显,这是一张k-means和mini batch k-means的实际效果对比图。

import time

import numpy as np
import matplotlib.pyplot as plt

from sklearn.cluster import MiniBatchKMeans, KMeans
from sklearn.metrics.pairwise import pairwise_distances_argmin
from sklearn.datasets.samples_generator import make_blobs

##############################################################################
# Generate sample data
np.random.seed(0)

batch_size = 45
centers = [[1, 1], [-1, -1], [1, -1]] #初始化三个中心
n_clusters = len(centers) #聚类的数目为3
#产生3000组两维的数据,以上边三个点为中心,以(-10,10)为边界,数据集的标准差是0.7
X, labels_true = make_blobs(n_samples=3000, centers=centers, cluster_std=0.7)

##############################################################################
# Compute clustering with Means

k_means = KMeans(init='k-means++', n_clusters=3, n_init=10)
t0 = time.time() #当前时间
k_means.fit(X)
#使用K-Means 对 3000数据集训练算法的时间消耗
t_batch = time.time() - t0

##############################################################################
# Compute clustering with MiniBatchKMeans

mbk = MiniBatchKMeans(init='k-means++', n_clusters=3, batch_size=batch_size,
n_init=10, max_no_improvement=10, verbose=0)
t0 = time.time()
mbk.fit(X)
#使用MiniBatchKMeans 对 3000数据集训练算法的时间消耗
t_mini_batch = time.time() - t0

##############################################################################
# Plot result

#创建一个绘图对象, 并设置对象的宽度和高度, 如果不创建直接调用plot, Matplotlib会直接创建一个绘图对象
'''''
当绘图对象中有多个轴的时候,可以通过工具栏中的Configure Subplots按钮,
交互式地调节轴之间的间距和轴与边框之间的距离。
如果希望在程序中调节的话,可以调用subplots_adjust函数,
它有left, right, bottom, top, wspace, hspace等几个关键字参数,
这些参数的值都是0到1之间的小数,它们是以绘图区域的宽高为1进行正规化之后的坐标或者长度。
'''

fig = plt.figure(figsize=(8, 3))
fig.subplots_adjust(left=0.02, right=0.98, bottom=0.05, top=0.9)
colors = ['#4EACC5', '#FF9C34', '#4E9A06']

# We want to have the same colors for the same cluster from the
# MiniBatchKMeans and the KMeans algorithm. Let's pair the cluster centers per
# closest one.
k_means_cluster_centers = np.sort(k_means.cluster_centers_, axis=0)
mbk_means_cluster_centers = np.sort(mbk.cluster_centers_, axis=0)
k_means_labels = pairwise_distances_argmin(X, k_means_cluster_centers)
mbk_means_labels = pairwise_distances_argmin(X, mbk_means_cluster_centers)
order = pairwise_distances_argmin(k_means_cluster_centers,
mbk_means_cluster_centers)

# KMeans
ax = fig.add_subplot(1, 3, 1) #add_subplot 图像分给为 一行三列,第一块
for k, col in zip(range(n_clusters), colors):
my_members = k_means_labels == k
cluster_center = k_means_cluster_centers[k]
ax.plot(X[my_members, 0], X[my_members, 1], 'w',
markerfacecolor=col, marker='.')
ax.plot(cluster_center[0], cluster_center[1], 'o', markerfacecolor=col,
markeredgecolor='k', markersize=6)
ax.set_title('KMeans')
ax.set_xticks(())
ax.set_yticks(())
plt.text(-3.5, 1.8, 'train time: %.2fs\ninertia: %f' % (
t_batch, k_means.inertia_))

# MiniBatchKMeans
ax = fig.add_subplot(1, 3, 2)#add_subplot 图像分给为 一行三列,第二块
for k, col in zip(range(n_clusters), colors):
my_members = mbk_means_labels == order[k]
cluster_center = mbk_means_cluster_centers[order[k]]
ax.plot(X[my_members, 0], X[my_members, 1], 'w',
markerfacecolor=col, marker='.')
ax.plot(cluster_center[0], cluster_center[1], 'o', markerfacecolor=col,
markeredgecolor='k', markersize=6)
ax.set_title('MiniBatchKMeans')
ax.set_xticks(())
ax.set_yticks(())
plt.text(-3.5, 1.8, 'train time: %.2fs\ninertia: %f' %
(t_mini_batch, mbk.inertia_))

# Initialise the different array to all False
different = (mbk_means_labels == 4)
ax = fig.add_subplot(1, 3, 3)#add_subplot 图像分给为 一行三列,第三块

for k in range(n_clusters):
different += ((k_means_labels == k) != (mbk_means_labels == order[k]))

identic = np.logical_not(different)
ax.plot(X[identic, 0], X[identic, 1], 'w',
markerfacecolor='#bbbbbb', marker='.')
ax.plot(X[different, 0], X[different, 1], 'w',
markerfacecolor='m', marker='.')
ax.set_title('Difference')
ax.set_xticks(())
ax.set_yticks(())

plt.show()


参考网站:http://blog.csdn.net/gamer_gyt/article/details/51244850

第9章 自然语言处理---情感分析实例

在自然语言处理中,首先需要把文本或单词等转换为数值格式,为后续机器学习或深度学习使用,把文本或单词转换为数值,有几种模型,如词袋模型(bag of words或简称为BOW)、word2vec等。

9.1 词袋模型(BOW)示例

BOW模型是信息检索领域常用的文档表示方法。在信息检索中,BOW模型假定对于一个文档,忽略它的单词顺序和语法、句法等要素,将其仅仅看作是若干个词汇的集合,文档中每个单词的出现都是独立的,不依赖于其它单词是否出现。也就是说,文档中任意一个位置出现的任何单词,都不受该文档语意影响而独立选择的。例如有如下三个文档:
1、The sun is shining
2、The weather is sweet
3、The sun is shining and the the weather is sweet
基于这三个文本文档(为简便起见这里以一个句子代表一个文档),构造一个词典或词汇库。如果构建词典?首先,看出现哪些单词,然后,给每个单词编号。在这三个文档中,共出现7个单词(不区分大小写),分别是:the,is ,sun,shining,and,weather,sweet。
然后,我们把这7个单词给予编号,从0开始,从而得到一个单词:序号的字典:
{'and':0,'is':1,'shining':2,'sun':3,'sweet':4,'the':5,'weather':6}
现在根据这个字典,把以上三个文档转换为特征向量(在对应序列号中是否有对应单词及出现的频率):
第一句可转换为:
[0 1 1 1 0 1 0]
第二句可转换为:
[0 1 0 0 1 1 1]
第三句可转换为:
[1 2 1 1 1 2 1]
0表示字典中对应单词在文档中未出现,1表示对应单词在文档出现一次,2表示出现2次。出现在特征向量中值也称为原始词频(raw term frequency):tf(t,d),单词t在文档d出现的次数)
这个一个简单转换,如果有几个文档,而且有些单词在每个文档中出现的频度都较高,这种频繁出现的单词往往不含有用或特别的信息,在向量中如何降低这些单词的权重?这里我们可以采用逆文档频率(inverse document frequency,idf)技术来处理。
原始词频结合逆文档频率,称为词频-逆文档词频(term frequency - inverse document frequency,简称为tf-idf)。
tf-idf如何计算呢?我们通过以下公式就明白了:
tf-idf(t,d)=tf(t,d)*idf(t,d)
其中idf(t,d)=log□(n_d/(1+df(d,t)))
n_d 表示总文档数(这里总文档数为3),df(d,t)为文档d中的单词t涉及的文档数量。
取对数是为了保证文档中出现频率较低的单词被赋予较大的权重,分母中的加1是为了防止df(d,t)为零的情况。有些模型中也会在分子加上1,分子变为1+n_d,tf-ifd(t,d)= tf(t,d)*(idf(t,d)+1),Scikit-learn采用这中计算方法。
如我们看单词'the'在第一个句子或第一个文档(d1来表示)中的tf-idf(t,d)的值
tf-idf('the',d1)=tf('the',d1)*idf('the',d1)
=1*log3/(1+3)=1*log0.75=-0.125

这些计算都有现成的公式,以下我们以Scikit-learn中公式或库来计算。

import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
count=CountVectorizer()
docs=np.array(['The sun is  shining',
'The weather is sweet',
'The sun is shining and the the weather is  sweet'])
bag=count.fit_transform(docs)

print(count.vocabulary_) #vocabulary_表示字典

运行结果:
{'the': 5, 'sun': 3, 'is': 1, 'shining': 2, 'weather': 6, 'sweet': 4, 'and': 0}

print(bag.toarray())
打印结果为:
[[0 1 1 1 0 1 0]
 [0 1 0 0 1 1 1]
 [1 2 1 1 1 2 1]]

以下求文档的tf-idf

from sklearn.feature_extraction.text import TfidfTransformer
tfidf=TfidfTransformer()
np.set_printoptions(precision=2)
print(tfidf.fit_transform(count.fit_transform(docs)).toarray())
#打印结果为:
[[ 0.    0.43  0.56  0.56  0.    0.43  0.  ]
 [ 0.    0.43  0.    0.    0.56  0.43  0.56]
 [ 0.4   0.48  0.31  0.31  0.31  0.48  0.31]]

说明:sklearn计算tf-idf时,还进行了归一化处理,其中TfidfTransformer缺省使用L2范数。
我们按照sklearn的计算方式,即tf-idf(t,d)=tf(t,d)*(log(1+n_d)/(1+df(d,t))+1),不难验证以上结果,以第一语句为例。
第一个语句的v=tf-idf(t,d1)=[0,1,1.28,1.28,0,1,0]

tf-idf(t,d1)norm=||v||/(〖||v||〗_2)=v/sqrt(∑▒v_i^2 )=v/2.29
=[0,0.43,0.56,0.56,0,0.43,0]
这个与上面的计算结果一致。

9.2情感分析实例

情感分析,有时也称为观点挖掘,是自然语言处理(NLP)领域一个非常重要的一个分支,它主要分析评论、文章、报道等的情感倾向,掌握或了解人们这些情感倾向非常重要。这些倾向对我们处理后续很多事情都有指定或借鉴作用。
这里我们以人们对一个互联网电影的评论为数据集。该数据集包含50,000个关于电影的评论,正面评论高于6星,负面评论低于5星。
以下我们采用词袋模型(BOW),用Python语言处理包(NLTK)对数据进行处理,由于数据量比较大,我们使用随机梯度下载方法来优化,利用逻辑蒂斯回归分类器进行分类。具体步骤如下:

9.2.1 加载数据

下载数据:
http://ai.stanford.edu/~amaas/data/sentiment

tar -zxf aclImdb_v1.tar.gz

文件结构:
在aclImdb目录下有test和train等目录,在train和test目录下,各有二级子目录neg和pos目录。其中neg目录存放大量评级负面或消极txt文件,pos存放大量评级为正面或积极的评论txt文件

hadoop@master:~/data/nlp_im/aclImdb$ ll
total 1732
-rw-r--r-- 1 hadoop hadoop 903029 Jun 12  2011 imdbEr.txt
-rw-r--r-- 1 hadoop hadoop 845980 Apr 13  2011 imdb.vocab
-rw-r--r-- 1 hadoop hadoop   4037 Jun 26  2011 README
drwxr-xr-x 4 hadoop hadoop   4096 Aug 29 15:16 test/
drwxr-xr-x 5 hadoop hadoop   4096 Aug 29 15:16 train/
hadoop@master:~/data/nlp_im/aclImdb$ cd train/
hadoop@master:~/data/nlp_im/aclImdb/train$ ll
total 66580
-rw-r--r-- 1 hadoop hadoop 21021197 Apr 13  2011 labeledBow.feat
drwxr-xr-x 2 hadoop hadoop   352256 Aug 29 15:18 neg/
drwxr-xr-x 2 hadoop hadoop   352256 Aug 29 15:16 pos/
drwxr-xr-x 2 hadoop hadoop  1409024 Aug 29 15:16 unsup/
-rw-r--r-- 1 hadoop hadoop 41348699 Apr 13  2011 unsupBow.feat
-rw-r--r-- 1 hadoop hadoop   612500 Apr 12  2011 urls_neg.txt
-rw-r--r-- 1 hadoop hadoop   612500 Apr 12  2011 urls_pos.txt
-rw-r--r-- 1 hadoop hadoop  2450000 Apr 12  2011 urls_unsup.txt

把这些文件附加到df中,同时显示加载进度。

import pyprind
import pandas as pd
import os
pbar=pyprind.ProgBar(50000)
labels={'pos':1,'neg':0}
df=pd.DataFrame()
for s in ('test','train'):
    for l in ('pos','neg'):
        path='./aclImdb/%s/%s'% (s,l)
        for file in os.listdir(path):
            with open(os.path.join(path,file),'r') as infile:
                txt=infile.read()
            df=df.append([[txt,labels[l]]],ignore_index=True)
            pbar.update()
df.columns=['review','snetiment']

运行了大概2分多钟:
0% [##############################] 100% | ETA: 00:00:00
Total time elapsed: 00:02:42

重排标签顺序,并把数据集存储到cvs文件中

import numpy as np
np.random.seed(0)
df=df.reindex(np.random.permutation(df.index))
df.to_csv('./movie_data.csv',index=False)

查看或检查存储数据

df=pd.read_csv('./movie_data.csv')
df.head(4)

查询结果如下:

这里有个拼写错误,snetiment,应该是sentiment,如果要更改过来,只要修改df的列名即可:

df.columns=['review','sentiment']

9.2.2数据预处理

1)、首先使用自然语言处理工具NLTK,下载停用词,然后过来文件。

import nltk
nltk.download('stopwords')

2)、对文件进行预处理,过来停用词、删除多余符号等。

from nltk.corpus import stopwords
import re
stop=stopwords.words('english')
def tokenizer(text):
    text=re.sub('<[^>]*>','',text)
    emoticons=re.findall('(?::|;|=)(?:-)?(?:</span>|<span class=" />|D|P)',text.lower())
    text=re.sub('[\W]+',' ',text.lower())+' '.join(emoticons).replace('-','')
    tokenized=[w for w in text.split() if w not in stop]
    return tokenized

3)、定义一个生成器函数,从csv文件中读取文档

def stream_docs(path):
    with open(path,'r') as csv:
        next(csv)# skip header
        for line in csv:
            text,label=line[:-3],int(line[-2])
            yield text,label

4)、定义一个每次获取的小批量数据的函数

def get_minibatch(doc_stream,size):
    docs,y=[],[]
    try:
        for _ in range(size):
            text,label=next(doc_stream)
            docs.append(text)
            y.append(label)
    except StopIteration:
            return None,None
    return docs,y

5)、利用sklearn中的HashingVectorizer进行语句的特征化、向量化等。

from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.linear_model import SGDClassifier
vect=HashingVectorizer(decode_error='ignore',n_features=2**21,preprocessor=None,tokenizer=tokenizer)
clf=SGDClassifier(loss='log',random_state=1,n_iter=1)
doc_stream=stream_docs(path='./movie_data.csv')

9.2.3训练模型

训练模型

import pyprind
pbar=pyprind.ProgBar(45)
classes=np.array([0,1])
for _ in range(45):
    x_train,y_train=get_minibatch(doc_stream,size=1000)
    if not x_train:
        break
    x_train=vect.transform(x_train)
    clf.partial_fit(x_train,y_train,classes=classes)
    pbar.update()

9.2.4评估模型

x_test,y_test=get_minibatch(doc_stream,size=5000)
x_test=vect.transform(x_test)
print('accuracy: %.3f' % clf.score(x_test,y_test))

测试结果为:
accuracy: 0.879
效果还不错,准确率达到近88%

第8章 航空公司客户价值分析

传统的识别客户价值应用最广泛的模型主要通过3个指标(最近消费时间间隔(Recency)、消费频率(Frequency)和消费金额(Monetary))来进行客户细分,识别出价值高的客户,简称RFC模型。
在RFC模型中,消费金额表示在一段时间内,客户购买产品的总金额。但是不适用于航空公司的数据处理。因此我们用客户在一段时间内的累计飞行里程M和客户在一定时间内乘坐舱位的折扣系数C代表消费金额。再在模型中增加客户关系长度L,所以我们用LRFMC模型。

8.1 探索数据

探索数据的缺失情况、异常值等。

#-*- coding: utf-8 -*-
#对数据进行基本的探索
#返回缺失值个数以及最大最小值

import pandas as pd

#航空原始数据,第一行为标题
datafile= '/home/hadoop/data/air_customer/air_data.csv'
#数据探索结果表
resultfile = '/home/hadoop/data/air_customer/tmp/explore.xls'
data = pd.read_csv(datafile, encoding = 'utf-8')
explore = data.describe().T
#计算空值
explore['null'] = len(data)-explore['count']
##统计空值,最大,最小值等
explore = explore[['null', 'max', 'min']]
explore

数据部分结果

null max min
MEMBER_NO 0.0 62988.000000 1.00
FFP_TIER 0.0 6.000000 4.00
AGE 420.0 110.000000 6.00
FLIGHT_COUNT 0.0 213.000000 2.00
BP_SUM 0.0 505308.000000 0.00
EP_SUM_YR_1 0.0 0.000000 0.00
EP_SUM_YR_2 0.0 74460.000000 0.00
SUM_YR_1 551.0 239560.000000 0.00
SUM_YR_2 138.0 234188.000000 0.00
SEG_KM_SUM 0.0 580717.000000 368.00
WEIGHTED_SEG_KM 0.0 558440.140000 0.00
AVG_FLIGHT_COUNT 0.0 26.625000 0.25

#保存结果
explore.to_excel(resultfile)

8.2 预处理数据

根据上面的数据统计,丢弃所有不符合的数据,
1、票价为空的
2、票价为0,但是折扣不是0,而且飞行里程大于0,
这样的数据是错误数据,直接删除

#票价非空值才保留
data = data[data['SUM_YR_1'].notnull()]
data = data[data['SUM_YR_2'].notnull()]
# 去掉票价为0,但是折扣不是0,或飞行里程大于0
data = data.drop(data['SUM_YR_1'] ==0 & (data['SEG_KM_SUM'] != 0) | (data['avg_discount'] > 0))
##保存清理后数据
cleanedfile = '/home/hadoop/data/air_customer/tmp/data_cleaned.csv'
data.to_csv(cleanedfile, encoding = 'utf-8')

8.3 数据归约

# 属性规约:去掉不相管的属性,只留下与LRFMC模型相关的属性
# FFP_DATE 入会时间
# LOAD_TIME 观测窗口结束时间
# FLIGHT_COUNT 飞行频率
# avg_discount 平均折扣
# SEG_KM_SUM 观测窗口总飞行公里数
# LAST_TO_END 最后一次乘机时间至观察窗口末端时长

data = data[['FFP_DATE','LOAD_TIME', 'FLIGHT_COUNT', 'avg_discount', 'SEG_KM_SUM','LAST_TO_END']]
#保存数据
cleanedfile = '/home/hadoop/data/air_customer/tmp/data_cleaned.csv'
data.to_csv(cleanedfile, encoding = 'utf-8')

数据变化的LRFMC数据:
L = LOAD_TIME - FFP_DATE (观测窗口时间 - 入会时间)
R = LOAD_TIME - LAST_TO_END (观测窗口时间 - 最后一次乘机时间)
F = FLIGHT_COUNT
M = SEG_KM_SUM
C = avg_discount

from datetime import datetime
import time

def normal_time(date):
'''
格式化数据
'''

return datetime.strptime(date,"%Y/%m/%d")

def interval_time(dd):
'''
计算时间间隔,以月为单位
'''

return dd.days / 30

# 计算LRFMC数据
data_LRFMC= pd.read_csv(cleanedfile,encoding='utf-8')
# data_LRFMC.columns = ['L', 'R', 'F','M', 'C']
data_LRFMC['L'] = (data['LOAD_TIME'].apply(normal_time) - data['FFP_DATE'].apply(normal_time)).apply(interval_time)
data_LRFMC['R'] = data['LAST_TO_END']
data_LRFMC['F'] = data['FLIGHT_COUNT']
data_LRFMC['M'] = data['SEG_KM_SUM']
data_LRFMC['C'] = data['avg_discount']

# 显示数据的描述,最大值和最小值
data_LRFMC_describe = data_LRFMC.describe().T
data_LRFMC_describe = data_LRFMC_describe[['max','min']].T
LRFMCfile = '/home/hadoop/data/air_customer/tmp/LRFMC.csv'
#数据写入文件
data_LRFMC.to_csv('LRFMCfile')
data_LRFMC_describe[['L','R','F','M','C']]
L R F M C
max 114.0 731.0 213.0 375074.0 1.500000
min 12.0 1.0 2.0 751.0 0.136017

最大值和最小值间隔较大,需要对数据进行标准化。

data_LRFMC=data_LRFMC[['L','R','F','M','C']]

# 标准化、重命名、写入文件
data_normal = (data_LRFMC - data_LRFMC.mean()) / (data_LRFMC.std())
data_normal.columns = ['Z'+i for i in data_normal.columns]

data_normafile = '/home/hadoop/data/air_customer/tmp/data_norma.csv'
data_normal.to_csv('data_normafile')

8.4 训练模型

数据处理完毕,下面进行模型的构建,
1、使用聚类算法,将数据生成5类用户
2、针对聚类结果进行特征分析

from sklearn.cluster import KMeans
k = 5
kmodel = KMeans(k,n_jobs=2) #得到模型
data_normal1=data_normal.dropna()
kmodel.fit(data_normal1) #训练模型

# 查看聚类中心和对应的类别
print(kmodel.cluster_centers_)
print(kmodel.labels_)
[[ 0.17343002 -0.07166046 -0.11266706 -0.09427382 2.69339174]
[-0.69751929 -0.40379498 -0.1697544 -0.17211799 -0.21902225]
[-0.3148445 1.67877305 -0.57462591 -0.5402772 -0.11557315]
[ 0.47921629 -0.79631365 2.48086723 2.43227193 0.27007082]
[ 1.15091348 -0.36698843 -0.09473823 -0.10441368 -0.13723191]]

print(kmodel.labels_)
[3 3 3 ..., 1 1 4]

8.4 可视化数据结果

import matplotlib.pyplot as plt

clu = kmodel.cluster_centers_
x = [1,2,3,4,5]

colors = ['red','green','yellow','blue','black']
for i in range(5):
plt.plot(x,clu[i],label='clustre '+str(i),linewidth=6-i,color=colors[i],marker='o')

plt.xlabel('L R F M C')
plt.ylabel('values')
plt.show()

8.5 客户价值分析

注意kmeans每次运行的时候得到的类会有差别,簇号也会相应的改变,但是中间点基本不会改变:
cluster L R F M C color
客户群1 0.17343 -0.07166 -0.11267 -0.09427 0.693392 red
客户群2 -0.69752 -0.40379 -0.16975 -0.17212 -0.21902 gree
客户群3 -0.31484 1.678773 -0.57463 -0.54028 -0.11557 yellow
客户群4 0.479216 -0.79631 2.480867 2.432272 0.270071 blue
客户群5 1.150913 -0.36699 -0.09474 -0.10441 -0.13723 black

我们重点关注的是L,F,M,从图中可以看到:
1、客户群4[blue] 的F,M很高,L也不低,可以看做是重要保持的客户;
2、客户群3[yellow] 的R比较高,为重要发展客户
3、客户群1[red] 重要挽留客户,原因:C较高,但是F,M较低
4、客户群2[green] 一般客户
5、客户群5[black] 低价值客户

第7章集成学习

7.1集成学习概述

集成学习(ensemble learning)可以说是现在非常火爆的机器学习方法了。它本身不是一个单独的机器学习算法,而是通过构建并结合多个机器学习器来完成学习任务。也就是我们常说的“博采众长”。集成学习可以用于分类问题集成,回归问题集成,特征选取集成,异常点检测集成等等,可以说所有的机器学习领域都可以看到集成学习的身影,甚至在目前最火的深度学习中也常见其影子。
集成学习的主要思想:对于一个比较复杂的任务,综合许多人的意见来进行决策往往比一家独大好,正所谓集思广益。其过程如下:

7.2投票分类器(VotingClassifier)

投票分类器的原理是结合了多个不同的机器学习分类器,使用多数票或者平均预测概率(软票),预测类标签。这类分类器对一组相同表现的模型十分有用,同时可以平衡各自的弱点。投票分类又可进一步分为多数投票分类(Majority Class Labels)、加权平均概率(soft vote,软投票)。
7.2.1多数投票分类(MajorityVote Class)
多数投票分类的分类原则为预测标签不同时,按最多种类为最终分类;如果预测标签相同时,则按顺序,选择排在第1的标签为最终分类。举例如下:
 预测类型的标签为该组学习器中相同最多的种类:例如给出的分类如下
 分类器1 -> 标签1
 分类器2 -> 标签1
 分类器3 -> 标签2
投票分类器(voting=‘hard’)则该预测结果为‘标签1’。
 在各个都只有一个的情况下,则按照顺序来,如下:
 分类器1 -> 标签2
 分类器2 -> 标签1
最终分类结果为“标签2”

7.2.1.1Iris数据集概述
首先,我们取得数据,下面这个链接中有数据的详细介绍,并可以下载数据集。https://archive.ics.uci.edu/ml/datasets/Iris
从数据的说明上,我们可以看到Iris有4个特征,3个类别。但是,我们为了数据的可视化,我们只保留2个特征(sepal length和petal length)。数据可视化代码如下:

%matplotlib inline
import pandas as pd
import matplotlib.pylab as plt
import numpy as np

# 加载Iris数据集作为DataFrame对象
df = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data', header=None)
X = df.iloc[:, [0, 2]].values # 取出2个特征,并把它们用Numpy数组表示

plt.scatter(X[:50, 0], X[:50, 1],color='red', marker='o', label='setosa') # 前50个样本的散点图
plt.scatter(X[50:100, 0], X[50:100, 1],color='blue', marker='x', label='versicolor') # 中间50个样本的散点图
plt.scatter(X[100:, 0], X[100:, 1],color='green', marker='+', label='Virginica') # 后50个样本的散点图
plt.xlabel('petal length')
plt.ylabel('sepal length')
plt.legend(loc=2) # 把说明放在左上角,具体请参考官方文档
plt.show()

示例代码如下:

from sklearn import datasets
from sklearn import cross_validation
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier

iris = datasets.load_iris()
X, y = iris.data[:, 1:3], iris.target

clf1 = LogisticRegression(random_state=1)
clf2 = RandomForestClassifier(random_state=1)
clf3 = GaussianNB()

eclf = VotingClassifier(estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)], voting='hard', weights=[2,1,2])

for clf, label in zip([clf1, clf2, clf3, eclf], ['Logistic Regression', 'Random Forest', 'naive Bayes', 'Ensemble']):
scores = cross_validation.cross_val_score(clf, X, y, cv=5, scoring='accuracy')
print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

运行结果如下:
Accuracy: 0.90 (+/- 0.05) [Logistic Regression]
Accuracy: 0.93 (+/- 0.05) [Random Forest]
Accuracy: 0.91 (+/- 0.04) [naive Bayes]
Accuracy: 0.95 (+/- 0.05) [Ensemble]

7.2.2多数投票分类(MajorityVote Class)
相对于多数投票(hard voting),软投票返回预测概率值的总和最大的标签。可通过参数weights指定每个分类器的权重;若权重提供了,在计算时则会按照权重计算,然后取平均;标签则为概率最高的标签。
举例说明,假设有3个分类器,3个类,每个分类器的权重为:w1=1,w2=1,w3=1。如下表:

下面例子为线性SVM,决策树,K邻近分类器:

from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from itertools import product
from sklearn.ensemble import VotingClassifier

#Loading some example data
iris = datasets.load_iris()
X = iris.data[:, [0,2]]
y = iris.target

#Training classifiers
clf1 = DecisionTreeClassifier(max_depth=4)
clf2 = KNeighborsClassifier(n_neighbors=7)
clf3 = SVC(kernel='rbf', probability=True)
eclf = VotingClassifier(estimators=[('dt', clf1), ('knn', clf2), ('svc', clf3)], voting='soft', weights=[2,1,2])

clf1 = clf1.fit(X,y)
clf2 = clf2.fit(X,y)
clf3 = clf3.fit(X,y)
eclf = eclf.fit(X,y)

##这些分类器分类结果
x_min,x_max = X[:,0].min()-1,X[:,0].max()+1
y_min,y_max = X[:,1].min()-1,X[:,1].max()+1
xx,yy = np.meshgrid(np.arange(x_min,x_max,0.1),
np.arange(y_min,y_max,0.1))
f, axarr = plt.subplots(2, 2, sharex='col', sharey='row', figsize=(10, 8))
for idx, clf, tt in zip(product([0, 1], [0, 1]),
[clf1, clf2, clf3, eclf],
['Decision Tree (depth=4)', 'KNN (k=7)',
'Kernel SVM', 'Soft Voting']):

Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

axarr[idx[0], idx[1]].contourf(xx, yy, Z, alpha=0.4)
axarr[idx[0], idx[1]].scatter(X[:, 0], X[:, 1], c=y, alpha=0.8)
axarr[idx[0], idx[1]].set_title(tt)
plt.show()

7.3自适应分类器(Adaboost)

Adaboost是一种迭代算法,其核心思想是针对同一个训练集训练不同的分类器(弱分类器),然后把这些弱分类器集合起来,构成一个更强的最终分类器(强分类器)。其算法本身是通过改变数据分布来实现的,它根据每次训练集之中每个样本的分类是否正确,以及上次的总体分类的准确率,来确定每个样本的权值。将修改过权值的新数据集送给下层分类器进行训练,最后将每次训练得到的分类器最后融合起来,作为最后的决策分类器。使用adaboost分类器可以排除一些不必要的训练数据特征,并放在关键的训练数据上面。
下面的例子展示了AdaBoost算法拟合100个弱学习器

from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.ensemble import AdaBoostClassifier

iris = load_iris()
clf = AdaBoostClassifier(n_estimators=100)
scores = cross_val_score(clf, iris.data, iris.target)
scores.mean()

输出结果为:
0.95996732026143794

实现原理:
1、假设我们有如下样本图:

2、第一次分类

第2次分类

第3次分类

第4次综合以上分类