您的位置:首页 > 其它

《机器学习实战》第五章:Logistic回归(1)基本概念和简单实例

2017-04-25 22:51 483 查看
最近感觉时间越来越宝贵,越来越不够用。不过还是抽空看了点书,然后整理到博客来。

加快点节奏,废话少说。

Keep calm & carry on.

-------------------------------------------------------------------------------------

这次要介绍的内容是Logistic Regression(LR,逻辑斯蒂回归)

相比于前几章,难度稍微加大了一些。

需要一些高数和基本的线代的基本知识,4年前学的东西已经忘得差不多了,好在查查资料还看得懂。

-------------------------------------------------------------------------------------

首先介绍介绍一下“回归”的概念。

我觉得百度百科上的解释就挺好的。

回归,指研究一组随机变量(Y1 ,Y2 ,…,Yi)和另一组(X1,X2,…,Xk)变量之间关系的统计分析方法。通常(Y1 ,Y2 ,…,Yi)是因变量,(X1,X2,…,Xk)是自变量。

当因变量和自变量呈现线性关系时,我们可以利用称为线性回归方程的最小二乘函数,对自变量和因变量之间关系进行建模。基于这种线性模型的回归分析称为线性回归(Linear regression)。其目的是寻找合适的参数,用一条直线去拟合散列的数据点,就像下图这个样子。线性回归在之后的章节会讲到。



而这篇博客要介绍的Logistic回归,做的是这么一件事情:根据现有的数据,对分类的边界线建立回归公式,以此进行分类。

它和线性回归在直观上的一个区别就是,数据点不一定呈现行排列,也许是这里一坨,那里一坨,它们每一坨代表一个,里面的数据点具有同一个标签。就像下面这个样子:



我们要做的就是找出分类的边界线。这个边界线是用回归公式来表示的。我们在训练分类器的时候,所做的事情就是利用最优化算法,找出回归公式中的最佳回归系数。

这里的概念有点多,后文会一一介绍。还是先按惯例把算法的特点介绍一下。

-------------------------------------------------------------------------------------

Logistic回归(Logistic Regression)

(1)是一种基于最优化算法的分类方法。
(2)优点:计算代价不高,易于理解和实现。
 缺点:容易欠拟合,分类精度可能不高。
 适用数据类型: 数值型和标称型数据
-------------------------------------------------------------------------------------

基于Logistic回归和Sigmoid函数的分类

基于Logistic回归的分类模型是这样的,给定任意一组输入,然后通过某个函数得到输出,这个输出就是输入数据的分类。比如,在二分类情况下,这个函数就输出0或1

符合这个性质的函数有很多种,其中一种就是Sigmoid函数



这个函数长这样:



当z为0的时候,函数值为0.5;随着z的增大,函数值逼近于1;随着z的减小,函数值逼近于0.

所以,这个函数很适合做我们刚才提到的二分类的分类函数。假设输入数据的特征是(x0, x1, x2, ..., xn),我们在每个特征上乘以一个回归系数 (w0,
w1, w2, ... , wn),然后累加得到sigmoid函数的输入z:



那么,输出就是一个在0~1之间的值,我们把输出大于0.5的数据分到1类,把输出小于0.5的数据分到0类。这就是Logistic回归的分类过程。

我们要做的,就是确定这个分类器中的最佳回归系数,即(w0, w1, w2, ... , wn)

上面这个公式可以写成下面这样的形式。其中w是行向量,x是列向量。x是分类器的输入数据,w是回归系数。



哦对了,顺便说一句,sigmoid函数的导数挺有趣的:



可以自行验证一下。

-------------------------------------------------------------------------------------

梯度上升法

现在我们就去找最佳参数w。方法用的是最优化算法,这里首先介绍其中一种:梯度上升法

它的思想是:要找到某个函数的最大值,最好最快的方法就是沿着函数的梯度方向探寻。如果梯度记为(读作“Nabla”),那么函数f(x,y)的梯度由下式表示:



其中,函数f(x,y)必须要在带计算的(x,y)点上有定义,并且可微

梯度上升法其实类似于“爬山”的过程,我们想到达这座山的最高点,每次前进一小步,每到一个点的时候,都希望能找到最佳的移动方向(即上面所计算的梯度),并按照这个方向前进下一步。画个图就明白了:



假设我们从x0这个点开始,然后想接近中心的那个最高点。那么首先计算x0点的梯度,移动到X1点。然后计算X1点的梯度,移动到X2.....如此循环迭代,直到满足停止条件。

那么,梯度为什么总是代表最佳的移动方向呢?因为,它求的是函数关于各个变量的偏导数,所以它总是代表函数值增长最快的方向。

说到这里只定义了移动方向,而没定义每次移动的距离。我们把这个距离称为“步长”,记做 α 

用向量来表示的话,梯度上升法的迭代公式如下:



它其实就是在不断的更新我们要找的回归系数w。这个公式会不断迭代执行,直到达到了迭代次数,或算法达到了某个事先设定的允许误差范围。

现在问题是,这个公式是怎么来的?步长 × 函数f(w)的梯度 ,然后加到原来的w上就得到了新的w,是这么个道理没错。不过f(w)这个函数到底指的是什么呢?难道就是作为预测函数的sigmoid吗?


        


作者根本没讲啊![b]作者根本没讲啊!作者根本没讲啊![/b]

[b]入门教程也不用入成这个样子吧![/b]

好吧,我们直接去看代码吧,也许能有点启示

这个例子是这样的:有100个样本点,每个样本点有2个数值型特征:X1和X2,分类的种数为2种:0和1

数据集长这样:



上代码:

from numpy import *

def loadDataSet():
dataMat = []; labelMat = []  #训练数据集,标签List
fr = open('testSet.txt')
for line in fr.readlines():
lineArr = line.strip().split()
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])  #分别是X0,X1,X2
labelMat.append(int(lineArr[2]))  #标签
return dataMat, labelMat

def sigmoid(z):
return 1.0/(1+exp(-z))

def gradAscent(dataMatIn, classLabels):
dataMatrix = mat(dataMatIn)             #100 * 3的矩阵
labelMat = mat(classLabels).transpose() #100 * 1的列向量
m, n = shape(dataMatrix)  #行数(样本数:100)、列数(特征数:3)
alpha = 0.001  #目标移动的步长
maxCycles = 500  #迭代次数
weights = ones((n,1))  #初始回归系数。3*1的列向量,每个系数初始化为1.0。weights为numpy.matrix型

for k in range(maxCycles):              #循环500次
h = sigmoid(dataMatrix*weights)     #矩阵乘法:每个样本的特征值×系数,拔得出来的值作为sigmoid函数输入
error = (labelMat - h)              #计算每个样本的sigmoid输出与标签的差值
weights = weights + alpha * dataMatrix.transpose()* error  #仍然懵逼
return weights

loadDataSet用来从文本里面获取样本数据集和样本标签。要注意的是第8行,人为地给每个样本多加了一个特征X0,且设置值为1.0

gradAscent就是梯度上升法训练过程的函数。

样本数据集是DataMatIn;标签List经过转置(transpose)后变成了列向量;weights是列向量,包含了3个回归系数,初始化为1.0:       






然后看循环里面的内容:

h计算的是每个样本特征乘以系数并累加后,作为sigmoid函数的输入得到的输出。是个100*1的列向量

error是计算h中每个值与labelMat中对应值的误差:



然而![b]然而!然而!最关键的倒数第2行[b]代码仍然看不懂啊![/b][/b]

我们把这行代码和之前那条让人懵逼的公式对照来看一下:

weights = weights + alpha * dataMatrix.transpose()* error



这样看来...样本数据集的矩阵转个置,再乘上error,就是f(w)的梯度了。所以...f(w)到底长什么样子呢???

先不管那么多,我们先看一下按照这样的做法,更新w到底有没效果吧。我们关注的是预测出的分类与真实标签的误差,即error。error里面有100项,我们就取一项error[0]来观察一下。我们在倒数第2行的前面,加一句:

print '第 {} 次循环,error[0]= {}'.format(k + 1, error[0])
看下结果:

dataMat, labelMat = loadDataSet()
gradAscent(dataMat, labelMat)



  



    


从数量级可以看出,error从最初的一个负值,不断地接近0,也就是说,误差越来越小。

所以,倒数第二行的代码是没问题的。但问题就是它和之前那条式子的含义到底是什么,这点我很想弄清楚。

然后,找啊找啊,就找到了这篇东西!里面有详细的推导过程!

-------------------------------------------------------------------------------------
梯度上升法 · 公式推导
很好。我们来解决心中的疑问,关于更新w的那条公式和那行代码,到底是怎样来的。

我们首先得了解代价函数(cost function)

代价函数,按照这位大神的说法,是“用于找到最优解的目的函数”

而按我的理解,在回归问题中,代价函数的作用,是评价算法所做的预测实际类别(预测)之间的误差。

对于梯度上升法,它的一个较为合适的代价函数如下:



其中,hθ(x)就是算法所做出的预测,即,将训练样本的数据特征x与回归系数w(此处是用θ来表示)进行向量乘法,并作为sigmoid函数得到的输出。






y就是训练样本所对应的真正类别(标签)。

我们直观地可以看下这个代价函数的作用:当预测准确(即y=1且预测值h接近于1,或当y=0且h接近于0)时,cost接近于0,即误差接近于0;而当预测错误(即y=1而预测值h接近于0,或当y=0而h接近于1)时,cost就为趋于无穷大,即误差趋于无穷大。

我们可以将上面那个分段函数合并成一条式子:



以上是对于一条训练样本而言,评价出的预测误差。那么对于有m条训练样本的一个数据集而言,代价函数就变成这个样子,就是把所有样本的预测误差累加起来。我们用J(θ)来表示:



回到梯度上升法。我们希望这个代价函数的函数值向0趋近,所以求梯度,即J(θ)回归系数θ的三项各自求偏导:



没啥复杂的。第2行后面那个小尾巴,是根据链式法则,hθ(xi)对θj求导。

第4行最后那儿:



当 j=0 时,就是对θ0求偏导,得到

 .
其他两项也一样。

推导的结果里面没带上求和符号,所以结果就是:



至此,真相已经浮出水面了!






我们把上面这个梯度公式和那行代码对照来看一下:

weights = weights + alpha * dataMatrix.transpose()* error



和梯度公式对照下,发现可以完全对上啊!

再看这条公式:



所以,f(w)指的其实是代价函数!

-------------------------------------------------------------------------------------

实例结果展示

我们来看下经过500次迭代更新后,最终的w是多少。

dataMat, labelMat = loadDataSet()
print gradAscent(dataMat, labelMat)




作为这篇又臭又长的博客的结尾,我们把所有数据点和分类界线在图里面给画出来。

def plotBestFit(dataMat, labelMat, weights):
import matplotlib.pyplot as plt
dataArr = array(dataMat)  #将Numpy的matrix类型转为array类型
n = shape(dataArr)[0]  #样本数:100
xcord1 = []; ycord1 = []  #标签为1的数据点的x坐标、y坐标
xcord2 = []; ycord2 = []  #标签为0的数据点的x坐标、y坐标
for i in range(n):
if int(labelMat[i])== 1:
xcord1.append(dataArr[i,1]); ycord1.append(dataArr[i,2])
else:
xcord2.append(dataArr[i,1]); ycord2.append(dataArr[i,2])
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')  #s参数是数据点的粗细,marker是标记('s'是正方形,默认圆形)
ax.scatter(xcord2, ycord2, s=30, c='green')
x = arange(-3.0, 3.0, 0.1)  #直线的x坐标范围
y = (-weights[0]-weights[1]*x)/weights[2] #直线的方程
ax.plot(x, y)
plt.xlabel('X1'); plt.ylabel('X2');
plt.show()
值得注意的是倒数第4行。我们有了最佳回归系数之后,就可以列出最佳拟合直线的方程。根据sigmoid函数的性质,横坐标为0处是两个分类(0和1)的分界线,所以令sigmoid函数的输入为0,就得到了分界线的方程:



其中x0是1,所以 x2=(-w0-w1x1) / w2

在图里面,令x1为横坐标,令x2为纵坐标,就能在图里把这条直线显示出来了。

dataMat, labelMat = loadDataSet()
weights=gradAscent(dataMat, labelMat)
plotBestFit(dataMat, labelMat, weights.getA())  #getA()函数将Numpy.matrix型转为ndarray型


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  机器学习