机器学习:逻辑回归

 

引言

   

之前学习了逻辑回归,主要是从三方面学习的,一个是coursera上台大林轩田老师机器学习公开课的逻辑回归部分,一个是斯坦福Andrew Ng老师机器学习公开课的逻辑回归部分,另一个是《机器学习实战》逻辑回归部分

   

前两者主要是对逻辑回归理论的学习,后者主要是实践的学习,现在对其进行整理,也便于自己思考。

   

本文主要内容

   

本文主要分为以下内容:

   

首先大致介绍了逻辑回归的分类过程,包括常规分类过程中的计算权重与样本特征之间乘积得到分数,利用sigmod函数将分数转换到01之间的值,然后利用这个值进行分类

   

然后对损失函数进行了分析,包括如何利用极大似然得到最佳的权重向量值,以及梯度下降法时权重的更新公式

   

再然后对解决过拟合的一般方式进行了一定的说明,包括正规化,减少特征

   

最后是机器学习实战的代码和仿真实验的结果

   

逻辑回归的分类过程

   

跟一般的分类模型一样,分类的话需要找一个合适的分类函数,台大机器学习中称之为h函数(hypothesis函数)

   

台大机器学习课程中对h得到的过程是这样讲解的

   

权重w与样本特征值的乘积得到分数

   

样本x是有n维的,每一维代表样本的一个特征,每一特征在判断x属于哪一类时所占的权重不一样,所以首先需要对x的各个维度(即特征向量的每一维)加权值w(这里的w未知,我们逻辑回归的目的就是求出这个w权重向量,表示对特征向量每一维所占的权重)算出一个作为判断分类的一个分数s,s的计算方式如下

   

   

逻辑函数将分数转换为类别

   

然后有了分数s,就需要利用一个逻辑函数将s转换为01的值,0,1也就是分类的结果,0代表负类,1代表正类

   

逻辑回归中在这一步与其他不同的是它含增加了一个sigmod函数,将分数转换到01之间的一个值

   

   

得到这个01之间的值θ(s),再将其与一个阈值做比较,一般是0.5,大于阈值的为正类1,小于阈值的为负类0

   

最终得到分类模型与样本特征向量的关系公式如下

   

   

应用分类函数进行类别判断

   

得到分类模型(函数)之后,如果想知道某一样本的分类结果,常见的场景应用是将这个样本的特征向量作为x输入到这个函数,得到一个y值,将y值与阈值做比较,大于阈值的为正,小于阈值的为负。

   

对分类函数(模型)的选取需要对数据有一定的了解或分析,知道或者猜测预测函数的"大概"形式,比如是线性函数还是非线性函数。

   

损失函数

   

要怎样去衡量输出的类别与实际的类别之间的差值呢,这就需要构造一个损失函数,该函数表示预测的输出(h)与训练数据类别(y)之间的偏差,可以是二者之间的差(h-y)(机器学习实战中选取这种形式),也可以是其他的形式。

   

综合考虑所有训练数据的"损失",将Cost求和或者求平均,记为J(θ)函数,表示所有训练数据预测值与实际类别的偏差。

   

显然,J(θ)函数的值越小表示预测函数越准确(即h函数越准确),所以这一步需要做的是找到J(θ)函数的最小值。

   

找函数的最小值有不同的方法,Logistic Regression实现时有的是梯度下降法(机器学习实战中用了梯度下降法和随机梯度下降法)。

   

构造损失函数

   

其实前面的分类函数h(x)还有一个深层次的含义,它代表了样本x属于类别1的概率

   

   

Andrew Ng在课程中直接给出了Cost函数及J(θ)函数的公式,但是并没有给出具体的解释,只是说明了这个函数来衡量h函数预测的好坏是合理的。

   

   

台大林轩田老师倒是从原理上推导了整个过程,但个人感觉这部分只是为了得到衡量分类结果的一个指标值,在此只写出部分推导过程,包括极大似然估计和梯度下降法的推导,其余的有兴趣的可以去看台大机器学习相关部分,讲的很清楚。

   

极大似然推导

   

对于之前的关于分类函数h代表样本属于类别1的概率问题

   

   

将此式综合起来,即综合y=1y=0的情况

   

   

此式稍加观察可以发现和上面的式子代表的含义是一样的,y=1时,1-h(x)部分就没有了,y=0时,h(x)部门就没有了

   

取似然函数为

   

   

对数似然函数为

   

   

最大似然估计就是求使l(θ)取最大值时的θ,其实这里可以使用梯度上升法求解(机器学习实战书中即是用的梯度上升法),求得的θ就是要求的最佳参数。但是,在Andrew Ng的课程中将J(θ)取为下式,即:

   

   

因为乘了一个负的系数-1/m,所以取J(θ)最小值时的θ为要求的最佳参数,求最大值就变为了求最小值,梯度上升法就变为了梯度下降法

   

θ更新过程可以写成:

   

   

过拟合问题

   

对于线性回归或逻辑回归的损失函数构成的模型,可能会有些权重很大,有些权重很小,导致过拟合(就是过分拟合了训练数据),使得模型的复杂度提高,泛化能力较差(对未知数据的预测能力)。

   

问题的主因

   

过拟合问题往往源自过多的特征。

   

解决方法

   

1)减少特征数量(减少特征会失去一些信息,即使特征选的很好)

   

可用人工选择要保留的特征;

特征选择算法;

   

2)正则化(特征较多时比较有效)

   

保留所有特征,但减少θ的大小

   

正规化解决过拟合实例

   

首先多说一句,正则项可以取不同的形式

   

在回归问题中取平方损失,就是参数的L2范数,也可以取L1范数。取平方损失时,模型的损失函数变为:

   

其中lambda是正则项系数:

   

  • 如果它的值很大,说明对模型的复杂度惩罚大,对拟合数据的损失惩罚小,这样它就不会过分拟合数据,在训练数据上的偏差较大,在未知数据上的方差较小,但是可能出现欠拟合的现象;
  • 如果它的值很小,说明比较注重对训练数据的拟合,在训练数据上的偏差会小,但是可能会导致过拟合。

   

   

接下来我们看斯坦福机器学习公开课中的一个房价问题用正规化解决过拟合的例子

   

左图是适当拟合,右图是过拟合。

   

   

分析得到,过拟合是由于其中x三次方四次方项的存在引起的,造成模型过于复杂,直观上看也就是曲线过于弯曲

   

如果我们想解决这个例子中的过拟合问题,最好能将x的三次方四次方项的影响消除,也就是让θ3和θ4尽可能的等于0

   

假设我们想这样做的话,一个简单的办法就是给原有的Cost函数的相应项加上两个略大惩罚项系数,例如:

   

   

这样在最小化Cost函数的时候

   

   

正则化后的梯度下降算法θ的更新变为:

   

   

逻辑回归实战

   

这部分主要是以机器学习实战中的例子为主导,自己也编写了整个算法的过程

   

首先是加载数据部分

   

def loadDataSet():

dataMat = []; labelMat = []

fr = open('testSet.txt')

for line in fr.readlines():

lineArr = line.strip().split()

dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])

labelMat.append(int(lineArr[2]))

return dataMat,labelMat

   

定义了两个数组用于存放数据和类别

   

加载后即基于数据和真实类别进行梯度下降法求取最佳的权重向量

   

def gradAscent(dataMatIn, classLabels):

dataMatrix = mat(dataMatIn) #convert to NumPy matrix

labelMat = mat(classLabels).transpose() #convert to NumPy matrix

m,n = shape(dataMatrix)

alpha = 0.001

maxCycles = 500

weights = ones((n,1))

for k in range(maxCycles): #heavy on matrix operations

h = sigmoid(dataMatrix*weights) #matrix mult

error = (labelMat - h) #vector subtraction

weights = weights + alpha * dataMatrix.transpose()* error #matrix mult

return weights

   

首先将数据数组转换为矩阵,方便之后的计算,alpha是步长,最大循环500次,权重向量初始化为全1向量

   

将数据域权重向量相乘,输入sigmod函数,得到分类结果,此处的h是一个向量

   

真实类别向量减去预测类别向量h得到分类的错误度,这也是一个向量

   

然后基于这个错误更新权重

   

这里的停止条件设为迭代500

   

最后会得到一个权重向量,而最佳分类线方程为0=wx,图形化如下

   

   

改进

   

由于上面的方法每次迭代都对所有的样本进行计算,计算量过大,所以一个改进的方法是每次迭代只对一个样本进行计算,更新权重也基于这个样本的损失函数进行更新,所以稍加改动,代码如下

   

def stocGradAscent0(dataMatrix, classLabels):

m,n = shape(dataMatrix)

alpha = 0.01

weights = ones(n) #initialize to all ones

for i in range(m):

h = sigmoid(sum(dataMatrix[i]*weights))

error = classLabels[i] - h

weights = weights + alpha * error * dataMatrix[i]

return weights

   

但是结果并不太好,这是由于每次用一个样本进行权重的更新,由于有一些样本是很难正确区分的样本,那这些样本会在每次迭代的过程中引发系数也就是权重的剧烈波动,如图所示

   

一个改进的方式是步长也随着迭代的次数进行改变,这样会缓解权重的波动

   

   

进一步改进,代码如下

   

def stocGradAscent1(dataMatrix, classLabels, numIter=150):

m,n = shape(dataMatrix)

weights = ones(n) #initialize to all ones

for j in range(numIter):

dataIndex = range(m)

for i in range(m):

alpha = 4/(1.0+j+i)+0.0001 #apha decreases with iteration, does not

randIndex = int(random.uniform(0,len(dataIndex)))#go to 0 because of the constant

h = sigmoid(sum(dataMatrix[randIndex]*weights))

error = classLabels[randIndex] - h

weights = weights + alpha * error * dataMatrix[randIndex]

del(dataIndex[randIndex])

return weights

   

另外选择样本时不是第i次迭代时选择第i个样本,而是每次迭代都随机选择样本,这样可以减少周期性波动

   

结果也很理想,与最开始用全部数据进行权重更新时的结果差不多,但计算量明显少于前面的方法

   

   

posted @ 2015-04-27 09:45  keedor  阅读(1753)  评论(0编辑  收藏  举报