简述:假设现在有一些数据点,我们用一条直线对这些点进行拟合(该线就称为最佳拟合直线),这个拟合过程就称作回归。当数据是线性可分的时候,我们可以利用最小二乘法来进行拟合,来进行分类。
当数据是线性不可分的时候,最小二乘法就不可以使用了(或者分类的效果很差很差),当数据是线性不可分的时候我们就可以利用Logistic回归进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,以此进行分类。
基于Sigmoid函数的Logistic回归:对于两个类的情况下,我们想要一个函数无论我们输入什么值,输出都是0或1(0,1就代表这两个类)。对于具有这种性质的函数,我们称为海维赛德阶跃函数,或者直接称为单位阶跃函数。然而海维赛德阶跃函数的问题在于:该函数在跳跃的上从0瞬间跳跃到1,这个跳跃的过程有时很难处理。幸好,另一个函数也具有类似的性质,且在数学上更容易处理,这就是Sigmoid函数。
Sigmoid的函数是 ,Sigmoid的函数图像是
其中z =,
,
为向量
其中=0 称为决策边界即boundarydecision。
现在我们的目的就是寻找最优的回归系数了,为了寻找最佳参数,我们需要用到最优化理论的一些知识。在下面我们首先介绍梯度上升这一最优化的方法。
梯度上升法基于的思想是:要找到某函数的最大值,最好的方法就是沿着该函数的梯度放心探寻。梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。至于梯度的计算大家可以参考百度,或者回顾下高数。。。。
梯度上升算法的迭代公式为:,该公式将一直被迭代执行,直至达到某个条件为止,比如迭代次数达到某个指定值或者算法达到某个可以允许的误差范围。
在这里提一下,梯度下降算法和梯度上升法是基本一样的,只不过迭代公式变为了减号:。梯度上升算法用来求函数的最大值,梯度下降算法用来求函数的最小值。
下面是梯度上升优化算法的代码实现:
from numpy import *
import matplotlib.pyplot as plt
'''
Logistic 回归梯度上升优化算法
'''
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 sigmoid(inX):
return 1.0/(1+exp(-inX))
def gradAscent(dataMatIn, classLabels):
dataMatrix = mat(dataMatIn) # 转换为numpy数组
labelMat = mat(classLabels).transpose() # 转换为numpy数组
m, n = shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weights = ones((n, 1))
for k in range(maxCycles):
h = sigmoid(dataMatrix*weights) # 计算分类
error = (labelMat - h) # 计算真实类别与预测类别的差值
weights = weights + alpha * dataMatrix.transpose() * error # 更新回归系数
return weights
在上面为什么倒数第二行是这样写的,下面就是一个解释,这个来源于coursera上吴恩达的机器学习课程
:
下面是画出决策边界的代码实现:
def plotBestFit(weights):
'''
画出数据集和Logistic回归最佳拟合直线的函数
:param weights: 回归系数矩阵
:return:
'''
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()
我们知道梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理100个作用的数据集尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升,由于可以在新样本到来时对分类器进行增量式更新,因此随机梯度上升算法是一个在线学习宣发。一次处理所有数据被称作“批处理”。下面是随机梯度上升算法的实现代码:
def stocGradAscent0(dataMatrix, classLabels):
'''
随机梯度上升法:一次只用一个样本点来更新回归系数
:param dataMatrix: 样本矩阵
:param classLabels: 样本标签向量
:return:返回训练完的回归系数矩阵
'''
m,n = shape(dataMatrix)
alpha = 0.01
weights = ones(n) # 初始化为全部元素为1的向量
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):
'''
改进的随机梯度上升法
:param dataMatrix: 样本矩阵
:param classLabels: 样本标签向量
:param numIter: 迭代的次数
:return:返回训练完的回归系数矩阵
'''
m,n = shape(dataMatrix)
weights = ones(n) # 初始化为全部元素为1的向量
for j in range(numIter):
dataIndex = list(range(m))
for i in range(m):
alpha = 4/(1.0+j+i)+0.0001 # alpha每次迭代时需要调整,随着迭代次数不断减小,但永远不会减小到0
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
使用改进的随机梯度提示算法,得到的决策边界如下图所以:
现在决策边界要比之前好的多了,虽然和使用梯度提示算法得到的结果差不多,但是要知道随机梯度提升算法收敛的更快,计算的时间更短。上面的函数较之前的函数改进了三个方面:1.是alpha会随着迭代次数不断减小,但永远不会减小到0。2.是通过随机选取样本来更新回归系数,减少周期性的波动。3.增加了一个迭代次数作为参数,可手动设置迭代次数。
下面是使用Logistic回归估计从疝气病症预测病马的死亡率案例。
(1)收集数据
(2)准备数据:用Python解析文本文件并填充缺失值
(3)分析数据:可视化并观察数据
(4)训练算法:使用优化算法,找到最佳的系数
(5)测试算法:为了量化回归的效果,需要观察错误率。根据错误率决定是否回退到训练阶段,通过改变迭代的次数和步长等参数来得到更好的回归系数。
(6)使用算法
对于处理数据中的缺失值,有以下方法:
(1)使用可用特征的均值来填补缺失值
(2)使用特殊值来填补缺失值,如-1
(3)忽略有缺失值的样本
(4)使用相似的样本的均值填补缺失值
(5)使用另外的机器学习算法预测缺失值
但是,对于类别标签丢失的数据,我们只能将其丢掉。在这里我们可用0来替换所有缺失值,因为这样对于Logistic回归而言,在更新时不会影响系数的值。
下面是案例代码:
def classifyVector(inX, weights):
'''
Logistic回归分类函数
:param inX: 需要预测数据的向量
:param weights: 回归系数
:return: prob值大于0.5则预测类别为1,反之为0
'''
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, 1000)
errorCount = 0
numTestVec = 0.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)))
总结
Logistic回归的目的是寻找一个非线性函数Sigmoid的最佳拟合参数,求解过程可以由最优化算法来完成。
在最优化算法中,最常用的就是梯度上升算法,而梯度上升算法又可以简化为随机梯度上升算法。
随机梯度上升算法和梯度上升算法的效果相当,但占用更少的计算资源。此外,随机梯度是一种在线算法,可以在数据到来时就完成参数的更新,而不需要重新读取整个数据集来进行批处理运算。
这篇笔记内容来自《机器学习实战》这本书。
总结的不恰当,或者哪里有问题的地方,请留言指出,,谢谢大家阅读。