您的位置:首页 > 其它

读书笔记:机器学习实战【第5章:Logistic回归】

2017-09-08 17:06 513 查看
利用Logistic回归进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,以此进行分类。

Logistic回归的一般过程:

收集数据

准备数据:要求数据类型为数值型,结构化则最佳。

分析数据:采用任意方法对数据进行分析。

训练算法:大部分时间将用于训练,目的是找到最佳的分类回归系数。

测试算法:一旦训练步骤完成,分类就会很快。

使用算法:首先我们需要一些输入数据,并将其转换成对应的结构化数值,接着,基于训练好的回归系数对这些数值进行简单地回归计算,判定它们属于哪个类别。在这之上,我们就可以在输出的类别上做一些其他分析工作。

5.1 基于Logistic回归和Sidmoid函数的分类

Logistic回归

优点:计算代价不高,易于理解与实现

缺点:容易欠拟合,分类精度可能不高。

适用数据类型:数值型和标称型数据

我们想要的函数是这样的:接受所有的输入并预测出类别,例如,在两个类的情况下,输出0或1。

Sigmoid函数具有适合的性质,具体的计算公式如下:

σ(z)=11+e −z



这是Sigmoid函数在不同坐标尺度下的两张曲线图,当x为0时,Sigmoid函数值为0.5,随着x的增大,Sigmoid的值会逼近于1,反之逼近于0,如果横坐标刻度足够大,看上去就像是一个阶跃函数。

因此,为了实现Logistic回归分类器,我们可以再每个特征上乘以一个回归系数,然后把所有的结果值相加,把这个总和代入Sigmoid函数,进而得到一个0~1之间的数值,最后,结果大于0.5的数据被归入1类,小于0.5的被归入0类,所以,Logistic回归也可以看成一种概率估计。

确定了分类器的函数形式之后,接下来要问的问题就是:最佳回归系数是多少?如何确定它们的大小?

5.2 基于最优化方法的最佳回归系数确定

Sigmoid函数的输入记为z,而它是由下面公式得出的:

z=w 0 x 0 +w 1 x 1 +w 2 x 2 +...+w n x n

如果采用向量的写法,上述公式则可以写成z=w T x 。它表示将这两个数值向量的对应元素相乘,然后全部加起来得到z值。其中的向量x是分类器的输入数据,向量w是我们要找到的最佳参数(回归系数),从而使得分类尽可能精确。而为了寻找该最佳参数,需要用到最优化理论的一些知识。

5.2.1 梯度上升法

我们介绍的第一个最优化算法叫做梯度上升法,梯度上升法基于的思想是:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻,如果梯度记为∇ ,则函数f(x,y)的梯度由下式表示:

∇f(x,y)=⎛ ⎝ ⎜ ⎜ ⎜ ∂f(x,y)∂x ∂f(x,y)∂y ⎞ ⎠ ⎟ ⎟ ⎟

这个梯度意味着,要沿x的方向移动∂f(x,y)∂x ,沿y的方向移动∂f(x,y)∂y 。其中,函数f(x,y)必须要在待计算的点上有定义并且可微。下图是一个具体的函数例子:



上图中的梯度上升算法,可以看到沿梯度方向移动的过程中,梯度算子总是指向函数值增长最快的方向,这里所说的是移动方向,而没有提到移动量的大小。该量值称为步长,记作α。用向量来表示的话,梯度上升算法的迭代公式如下:

w:=w+α∇ w f(w)

该公式将一直被迭代执行,直至达到某个停止条件:比如迭代次数达到指定值,或者算法达到某个可以允许的误差范围。

梯度下降算法:

更经常听到的算法是梯度下降算法,它与这里的梯度上升算法是一样的,只是公式中的加法需要变成减法,因此,对应的公式可以写成:

w:=w−α∇ w f(w)

梯度上升算法用来求函数的最大值,而梯度下降算法用来求函数的最小值。

5.2.2 训练算法:使用梯度上升找到最佳参数



上图是本例使用的数据集,其中有100个样本点,每个点包含两个数值型特征:X1和X2。在此数据集上,我们将通过梯度上升法找到最佳回归系数,也就是拟合出Logistic回归模型的最佳参数。

为此,我们需要找到最优化的目标函数,原始的Sigmoid函数是这样的:

σ=11+exp(−w T x)

可以理解为:

p(y=1|w,x)=11+exp(−w T x) p(y=0|w,x)=exp(−w T x)1+exp(−w T x)

因此,对于每个样本的分类结果yi,可以列似然函数如下:

p(y=y i |w,x i )=y i ×11+exp(−w T x i ) +(1−y i )×exp(−w T x i )1+exp(−w T x i )

采用极大似然法估计数据集的分类,列出对数似然函数作为costFunction:

l(w,b)=∑ i=1 m lnp(y=y i |w,x i )=∑ i=1 m y i ×ln(11+exp(−w T x i ) )+(1−y i )×ln(exp(−w T x i )1+exp(−w T x i ) )=∑ i=1 m y i ×ln(1exp(−w T x i ) )+ln(exp(−w T x i )1+exp(−w T x i ) )=∑ i=1 m y i w T x i −ln[1+exp(w T x i )]

至此,获得目标函数l(w,b),而为了使用梯度上升法来计算它的极大值,需要计算它的梯度:

∇ w l(w)=∑ i=1 m y i x i −exp(w T x i )1+exp(w T x i ) ×x i =∑ i=1 m x i ×[y i −p(y i =1|w,x i )]

梯度上升法的伪代码如下:

每个回归系数初始化为1
重复R次:
计算整个数据集的梯度
使用`alpha×gradient`更新回归系数的向量
返回回归系数


下面的代码是梯度上升算法的具体实现:

def loadDataSet():  #读入数据
dataMat = [];labelMat = []
fr = open('testSet.txt')
for line in fr.readlines():
lineArr = line.strip().split()  #strip:删除字符串列,为空时默认删除空白符号如\t等;split:分割字符串后返回列表
dataMat.append([1.0,float(lineArr[0]),float(lineArr[1])]) #注意1是截距
labelMat.append(int(lineArr[2]))
return dataMat,labelMat

def sigmoid(inX): #把输入转化为Sigmoid函数
return 1.0/(1+exp(-inX))

def gradAscent(dataMatIn,classLabels):
#(以下两行)转换为NumPy矩阵数据类型:
dataMatrix = mat(dataMatIn)
labelMat = mat(classLabels).transpose() #注意转置
m,n = shape(dataMatrix) ##m行n列矩阵
alpha = 0.001 #步长
maxCycles = 500
weights = ones((n,1)) ##初始化回归系数为n*1,n是特征个数。
for k in range(maxCycles):
h = sigmoid(dataMatrix*weights) #函数输出结果,获得分类标签y=1的概率
error = (labelMat -h) #h是p(y=1),labelMat是原始样本分类标签y,error其实就是yi-p(y=1)
weights = weights + alpha*dataMatrix.transpose()*error #而dataMatrix是原始x,则这里其实就是x*[y-p(y=1)],获得的是梯度向量,乘以步数alpha就是梯度上升。
return weights


接下来,尝试运行代码:

In [40]: dataArr,labelMat = loadDataSet()

In [41]: gradAscent(dataArr,labelMat)
Out[41]:
matrix([[ 4.12414349],
[ 0.48007329],
[-0.6168482 ]])


这样,就获得了一组回归系数,即模型建立完成。

5.2.3 画出决策边界:

画出决策边界的代码如下:

def plotBestFit(weights):
import matplotlib.pyplot as plt
dataMat,labelMat=loadDataSet()
dataArr = array(dataMat)
n = shape(dataArr)[0]
xcord1 = []; ycord1 = []
xcord2 = []; ycord2 = []
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')
ax.scatter(xcord2, ycord2, s=30, c='green')
x = arange(-3.0, 3.0, 0.1)
y = (-weights[0]-weights[1]*x)/weights[2]
ax.plot(x, y)
plt.xlabel('X1'); plt.ylabel('X2');
plt.show()


输出结果如图:



可以看到,分类结果相当不错。

5.2.4 训练算法:随机梯度上升

梯度上升算法每次更新回归系数时,都要遍历整个数据集。该方法在处理100个左右的数据集的时候尚可,但是,如果有数十亿的样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。由于可以再新样本到来时,对分类器进行增量式更新,因而随机梯度上升算法是一个在线学习算法,而与在线相对应的,一次处理所有数据的做法则被称为“批处理”。

随机梯度上升算法,可以写成如下的伪代码:

所有回归系数初始化为1
对数据集中的每个样本:
计算该样本的梯度
使用alpha×gradient更新回归系数值
返回回归系数值


实现随机梯度上升算法的代码如下:

def stocGradAscent0(dataMatrix,classLabels):
m,n = shape(dataMatrix)
alpha = 0.01
weights = ones(n)
for i in range(m):
h = sigmoid(sum(dataMatrix[i]*weights)) #y=1的概率
error = classLabels[i] - h #yi-p(y=1)
weights = weights + alpha*error*dataMatrix[i]
return weights


接下来,尝试以随机梯度上升算法建立模型:

In [60]: weights = stocGradAscent0(array(dataArr),labelMat)

In [61]: weights
Out[61]: array([ 1.01702007,  0.85914348, -0.36579921])

In [62]: plotBestFit(weights)




这是随机梯度上升算法获得的边界,可以看到与前面的相比,这里分类器错分了三分之一左右的样本。

但是,直接将随机梯度上升算法的结果与梯度上升法进行比较是不公平的:梯度上升法进行了500次迭代,而随机梯度上升算法只进行了100次。

一个判断优化算法优劣的可靠方法是看他是否瘦脸,也就是说参数是否达到了稳定值,是否还会不断变化。对此,我们在随机梯度上升算法上进行一些修改,使其在整个数据集上运行200次,但是,结果依然并不理想。



上图所展示了的,是随机梯度上升算法在200次迭代过程中回归系数的变化情况。其中的系数2,也就是X2只经过了50次迭代就达到了稳定值,但系数1和0则需要更多次的迭代。另外值得注意的是,在大的波动停止后,还会出现一些小的周期性波动。产生这种现象的原因是存在一些不能正确分类的样本点(毕竟,数据集并非完全线性可分,必然会存在不能分类的点),在每次迭代时会引发系数的剧烈改变。

我们期望算法能避免来回波动,从而收敛到某个值,另外,收敛速度也需要加快。

为此,我们对随机梯度上升算法进行改进:

def stocGradAscent1(dataMatrix,classLabels,numIter=150):
m,n = shape(dataMatrix)
weights = ones(n)
for j in range(numIter):
dataIndex = range(m)
for i in range(m):
#1.alpha每次迭代时需要调整
alpha = 4/(1.0+j+i)+0.01

#2.随机选取样本来更新系数
randIndex = int(random.uniform(0,len(dataIndex))) #此次被选中的样本
h = sigmoid(sum(dataMatrix[randIndex]*weights))
error = classLabels[randIndex]-h
weights = weights + alpha * error * dataMatrix[randIndex] #梯度上升更新系数
del(dataIndex[randIndex]) #被选中的样本从列表里删除,不会再选
return weights


与前述随机梯度上升法相比,一方面,步长alpha在每次迭代的时候都会调整,随着步数的增加,步长周期性下降,这会缓解数据波动或高频波动的形成。另外,虽然alpha会随着迭代次数不断减小,但是永远不会减到0,这是因为alpha中存在一个常数项,必须这样做的原因是为了保证多次迭代之后新数据仍然具有一定的影响。另外,这里也避免了参数的严格下降。

另一个改进的地方是,通过随机选取样本来更新回归系数,这种方法可以减少周期性的波动。

此外,还增加了一个迭代次数作为第三个参数,它的默认值是150。

新的回归系数变化情况如下图:



可以看到,系数以更快的速度收敛了。

接下来看看在同一数据集上的分类效果:

In [67]: weights = stocGradAscent1(array(dataArr),labelMat)

In [68]: weights
Out[68]: array([ 14.09265181,   0.95684552,  -1.98060954])

In [69]: plotBestFit(weights)


决策边界:



可以看到,这样的决策边界与梯度上升法的效果接近,但是计算量少了很多。

5.3 示例:从疝气病症预测病马的死亡率

本节将使用Logistic回归来预测患有疝气病的马的存活问题,这里的数据包含368个样本和28个特征。

需要说明的是,除了部分指标主观和难以测量,该数据集还存在一个问题,数据集中有30%的数据是缺失的。下面首先介绍如何处理数据集中的数据缺失问题,然后再利用Logistic回归和随机梯度上升算法预测病马的生死。

5.3.1 处理数据中的缺失值

面对缺失值,有一些可取的做法:

使用可用特征的均值填补缺失值

使用特殊值填补缺失值,如-1

忽略有缺失值的样本

使用相似样本的均值填补缺失值

使用另外的机器学习算法预测缺失值

现在,我们对下一节要用的数据集进行预处理,使其可以顺利第使用分类算法,在预处理阶段要做两件事:第一,所有的缺失值要用一个实数值来替换,这里选择0的原因在于,我们需要一个在更新时不会影响系数的值,而如果某个特征对应的值是0,它将不会对权重weights的更新造成影响。

另外,由于Sigmoid(0)=0.5,也就是说其对结果的预测不具任何倾向性,因此把缺失值定为0的做法,也不会对误差项造成任何影响。此外,该数据集中的特征取值一般不是0,也满足特殊值的要求。

另一件要做的事情是,如果在测试数据集中发现了一条数据的类别标签丢失,那就必须将其丢弃,这是无法插补的。

5.3.2 测试算法:用Logistic回归进行分类

首先,创建分类函数:

def classifyVector(inX,weights):
prob = sigmoid(sum(inX*weights))
if prob > 0.5: return 1.0
else: return 0.0


对于输入值的变量,将其用模型算出概率,再转化为类别。

接下来,是对数据进行分类的函数:

def colicTest():
frTrain = open('horseColicTraining.txt')
frTest = open('horseColicTest.txt')
trainingSet = [];trainingLabels = []

for line in frTrain.readlines():
currLine = line.strip().split('\t')
lineArr = []
for i in range(21):
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)
trainingLabels.append(float(currLine[21]))

trainWeights = stocGradAscent1(array(trainingSet),trainingLabels,500)

errorCount = 0;numTestVec = 0
for line in frTest.readlines():
numTestVec +=1.0
currLine = line.strip().split('\t')
lineArr = []
for i in range(21):
lineArr.append(float(currLine[i]))
if int(classifyVector(array(lineArr),trainWeights)) != int(currLine[21]):
errorCount +=1
errorRate = (float(errorCount)/numTestVec)

print "the error rate of this test is :%f" % errorRate
return errorRate

def multiTest():
numTests = 10;
errorSum = 0.0
for k in range(numTests):
errorSum += colicTest()
print 'after %d iterations the average error rate is:%f' %(numTests,errorSum/float(numTests))     #调用10次colicTest,取平均值。


接下来,就可以检验实际的运行效果了:

In [132]: multiTest()
the error rate of this test is :0.358209
the error rate of this test is :0.388060
the error rate of this test is :0.373134
the error rate of this test is :0.328358
the error rate of this test is :0.417910
the error rate of this test is :0.462687
the error rate of this test is :0.492537
the error rate of this test is :0.417910
the error rate of this test is :0.373134
the error rate of this test is :0.298507
after 10 iterations the average error rate is:0.391045


从上面的结果可以看到,10次迭代的平均错误率是39%左右,需要说明的是,如果考虑到原始数据有30%的缺失的话,那么这个结果并不差。

当然,调整步长和迭代次数可以降低这个错误率。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息