目录
一、Logistic基本概念
1.1Logistic回归
1.2Logistic分布
1.3基于Logistic回归和Sigmoid函数的分类
二、基于最优化方法的最佳回归系数确定
2.1极大似然估计
2.2 梯度上升法
2.3梯度下降法
三、利用Logistic模型进行分类测试
3.1数据准备
3.2编写代码查看数据集的分布情况
3.3训练算法:使用梯度上升找到最佳参数
3.4分析数据:画出决策边界
3.5训练算法:随机梯度上升
3.6改进的随机梯度上升算法
四、从疝气病症预测病马的死亡率
4.1准备数据:处理数据中的缺失值
4.2测试算法:用Logistic回归进行分类
五、总结
5.1Logistic回归的一般过程
5.2优缺点
Logistic回归(logistic regression)是统计学习中的经典分类方法,属于对数线性模型,所以也被称为对数几率回归。虽然名字有回归,但是实际上却是一种经典的分类方法,其主要思想是:根据现有的数据对分类边界线(Decision Boundary)建立回归公式,以此进行分类。
Logistic 分布是一种连续型的概率分布,其分布函数和密度函数分别为:
其中,μ 表示位置参数,γ 为形状参数。我们可以看下逻辑斯蒂分布在不同的、的情况下,其概率密度函数的图形:
逻辑斯蒂分布在不同的、的情况下,其概率分布函数的图形:
Logistic 分布是由其位置和尺度参数定义的连续分布。Logistic 分布的形状与正态分布的形状相似,但是 Logistic 分布的尾部更长,所以我们可以使用 Logistic 分布来建模比正态分布具有更长尾部和更高波峰的数据分布。在深度学习中常用到的 Sigmoid 函数就是 Logistic 的分布函数在 的特殊形式。
我们想要的函数应该是,能接受所有的输入然后预测出类别。例如,在两个类的情况下,上述函数输出0或1。例如海维塞德阶跃函数,或直接称为单位阶跃函数,图像如下:
该函数的问题在于不连续、不可微,该函数在跳跃点上从0瞬间跳跃到1,有时很难处理,而Sigmoid函数也有这样的性质,且数学上更易处理。Sigmoid函数具体的计算公式如下:
下图给出了Sigmoid函数在不同坐标尺度下的两条曲线图。当x为0时,Sigmoid函数值为0.5。 随着x的增大,对应的Sigmoid值将逼近于1;而随着x的减小,Sigmoid值将逼近于0。如果横坐标 刻度足够大,Sigmoid函数看起来很像一个阶跃函数。
因此,为了实现Logistic回归分类,我们可以在每个特征上都乘以一个回归系数,然后把 所有的结果值相加,将这个总和代入Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分入1类,小于0.5即被归入0类。所以,Logistic回归也可以被看成是一种概率估计。
Sigmoid函数的输入记为z,
其中向量x是分类器的输入数据,向量w就是我们要找到的最佳系数,b为常数。代入Sigmoid函数中得到:
该预测函数的值表示y=1的概率,所以有y=1和y=0的概率分别为
通过,我们获得z值,再通过Sigmoid函数把z值映射到0~1之间,获得数值之后就可进行分类。比如定义,大于0.5的分类为1,反之,即分类为0。所以我们要解决的问题就是获得最佳回归系数,即求解w和b得值。
假设:
可以得到似然函数为:
对似然函数取对数可得:
基本思想:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为▽,则函数f(x,y)的梯度由下式表示:
这个梯度意味着要沿x的方向移动:
如图,梯度上升算法到达每个点后都会重新估计移动的方向。从P0开始,计算完该点的梯度,函数就根据梯度移动到下一点P1。在P1点,梯度再次被重新计算,并沿新的梯度方向移动到P2。如此循环迭代,直到满足停止条件。迭代的过程中,梯度算子总是保证我们能选取到最佳的移动方向。
用向量来表示的话,梯度算法的迭代公式如下:
,其中表示步长。
按梯度上升的反方向迭代公式即可
梯度上升算法用来求函数的最大值,而梯度下降算法用来求函数的最小值
有100个样本点,每个数据有两个特征维度,将第一列看做x1的值,第二列看做x2的值,第三列作为分类的标签
# -*- coding:UTF-8 -*-
import matplotlib.pyplot as plt
import numpy as np
"""
函数说明:加载数据
Returns:
dataMat - 数据列表
labelMat - 标签列表
"""
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])) #添加标签
fr.close() #关闭文件
return dataMat, labelMat #返回
"""
函数说明:绘制数据集
"""
def plotDataSet():
dataMat, labelMat = loadDataSet() #加载数据集
dataArr = np.array(dataMat) #转换成numpy的array数组
n = np.shape(dataMat)[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]) #1为正样本
else:
xcord2.append(dataArr[i,1]); ycord2.append(dataArr[i,2]) #0为负样本
fig = plt.figure()
ax = fig.add_subplot(111) #添加subplot
ax.scatter(xcord1, ycord1, s = 20, c = 'black', marker = 's',alpha=.5)#绘制正样本
ax.scatter(xcord2, ycord2, s = 20, c = 'red',alpha=.5) #绘制负样本
plt.title('DataSet') #绘制title
plt.xlabel('x1'); plt.ylabel('x2') #绘制label
plt.show() #显示
if __name__ == '__main__':
plotDataSet()
如图所示:假设Sigmoid函数的输入记为z,那么,即可将数据分割开。其中,x0为全是1的向量,x1为数据集的第一列数据,x2为数据集的第二列数据。因此,我们需要求出这个方程未知的参数w0,w1,w2,也就是我们需要求的回归系数(最优参数)。
"""
函数说明:sigmoid函数
Parameters:
inX - 数据
Returns:
sigmoid函数
"""
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
"""
函数说明:梯度上升算法
Parameters:
dataMatIn - 数据集
classLabels - 数据标签
Returns:
weights.getA() - 求得的权重数组(最优参数)
"""
def gradAscent(dataMatIn, classLabels):
dataMatrix = np.mat(dataMatIn) #转换成numpy的mat
labelMat = np.mat(classLabels).transpose() #转换成numpy的mat,并进行转置
m, n = np.shape(dataMatrix) #返回dataMatrix的大小。m为行数,n为列数。
alpha = 0.001 #移动步长,也就是学习速率,控制更新的幅度。
maxCycles = 500 #最大迭代次数
weights = np.ones((n,1))
for k in range(maxCycles):
h = sigmoid(dataMatrix * weights) #梯度上升矢量化公式
error = labelMat - h
weights = weights + alpha * dataMatrix.transpose() * error
return weights.getA() #将矩阵转换为数组,返回权重数组
现在,我们已经求出了未知的参数w0=4.12414349,w1=0.48007329,w2=-0.6168482,接下去通过求解出的参数,我们就可以确定不同类别数据之间的分隔线,画出决策边界。
# 绘制数据集和Logistic回归最佳拟合直线
def plotBestFit(weights):
dataMat, labelMat = loadDataSet() # 加载数据集,标签
dataArr = np.array(dataMat) # 转换成umPy的数组
n = np.shape(dataArr)[0] # 获取数据总数
xcord1 = []; ycord1 = [] # 存放正样本
xcord2 = []; ycord2 = [] # 存放负样本
for i in range(n): # 依据数据集的标签来对数据进行分类
if int(labelMat[i]) == 1: # 数据的标签为1,表示为正样本
xcord1.append(dataArr[i, 1]); ycord1.append(dataArr[i, 2])
else: # 否则,若数据的标签不为1,表示为负样本
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 = np.arange(-3.0, 3.0, 0.1) # x区间
y = (-weights[0] - weights[1] * x) / weights[2] # 最佳拟合直线
ax.plot(x, y)
plt.title('BestFit') # 标题
plt.xlabel('X1'); plt.ylabel('X2') # x,y轴的标签
plt.show()
if __name__ == '__main__':
weights = gradAscent(dataMat, labelMat)
plotBestFit(weights)
从得出的结果图,我们可以看出,这个分类结果很不错了,从图上看出错的点不多。
# 随机梯度上升算法
def stocGradAscent0(dataMatrix, classLabels): # dataMatIn数据集、classLabels数据标签
m, n = np.shape(dataMatrix) # 获取数据集矩阵的大小,m为行数,n为列数
alpha = 0.01 # 目标移动的步长
weights = np.ones(n) # 所以初始化为1
for i in range(m): # 重复矩阵运算
h = sigmoid(sum(dataMatrix[i] * weights)) # 矩阵相乘,计算sigmoid函数
error = classLabels[i] - h # 计算误差
weights = weights + alpha * error * dataMatrix[i] # 矩阵相乘,更新权重
return weights
# 运行测试代码
dataMat, labelMat = loadDataSet()
weigths = stocGradAscent0(np.array(dataMat), labelMat)
plotBestFit(weigths)
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))
可以看到,随机梯度上升算法与梯度上升算法在代码上很相似,但也有一些区别:第一,后 者的变量h和误差error都是向量,而前者则全是数值;第二,前者没有矩阵的转换过程,所有 变量的数据类型都是NumPy数组。
随机梯度上升算法在上述数据集上的执行结果,最佳拟合直线并非最佳分类线,可以看出拟合曲线出现了很大的偏差。
# 改进的随机梯度上升算法
def stocGradAscent1(dataMatrix, classLabels, numIter=150): # dataMatIn数据集、classLabels数据标签、numIter迭代次数
m, n = shape(dataMatrix) # 获取数据集矩阵的大小,m为行数,n为列数
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 # apha目标移动的步长,每次迭代调整
randIndex = int(random.uniform(0, len(dataIndex))) # 随机选取更新样本
h = sigmoid(sum(dataMatrix[randIndex] * weights)) # 矩阵相乘,计算sigmoid函数
error = classLabels[randIndex] - h # 计算误差
weights = weights + alpha * error * dataMatrix[randIndex] # 矩阵相乘,更新权重
del (dataIndex[randIndex]) # 删除已使用过的样本
return weights
# 测试
dataMat, labelMat = loadDataSet()
weigths = stocGradAscent1(array(dataMat), labelMat)
plotBestFit(weigths)
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))
改进的第一个方面在alpha = 4 / (1.0 + j + i) + 0.0001,alpha在每次迭代的时候都会调整,另外,虽然alpha会随着迭代次数不断减小,但永远不会减小到0。必须这样做的原因是为了保证在多次迭代之后新数据仍然具有一定的影响。 如果要处理的问题是动态变化的,那么可以适当加大上述常数项,来确保新的值获得更大的回归系数。另一点值得注意的是,在降低alpha的函数中,alpha每次减少1/(j+i) ,其中j是迭代次数, i是样本点的下标。
第二个改进的地方在randIndex = int(random.uniform(0, len(dataIndex)))处,这里通过随机选取样本来更新回归系数,每一次都是迭代未用过的样本点。这种方法将减少周期性的波动,计算量减少了,而且从上述的运行结果可以看出,回归效果挺好。
使用 Logistic 回归来预测患有疝病的马的存活问题。疝病是描述马胃肠痛的术语。然而,这种病不一定源自马的胃肠问题,其他问题也可能引发马疝病。这个数据集中包含了医院检测马疝病的一些指标,有的指标比较主观,有的指标难以测量,例如马的疼痛级别。
原始数据集
上述数据还存在一个问题,数据集中有30%的值是缺失的。数据中的缺失值是一个非常棘手的问题,下面给出了一些可选的做法:
数据预处理:
处理好的数据
# -*- coding:UTF-8 -*-
import numpy as np
import random
def gradAscent(dataMatIn, classLabels):
dataMatrix = np.mat(dataMatIn) #转换成numpy的mat
labelMat = np.mat(classLabels).transpose() #转换成numpy的mat,并进行转置
m, n = np.shape(dataMatrix) #返回dataMatrix的大小。m为行数,n为列数。
alpha = 0.01 #移动步长,也就是学习速率,控制更新的幅度。
maxCycles = 500 #最大迭代次数
weights = np.ones((n,1))
for k in range(maxCycles):
h = sigmoid(dataMatrix * weights) #梯度上升矢量化公式
error = labelMat - h
weights = weights + alpha * dataMatrix.transpose() * error
return weights.getA() #将矩阵转换为数组,并返回
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
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(len(currLine)-1):
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)
trainingLabels.append(float(currLine[-1]))
trainWeights = gradAscent(np.array(trainingSet), trainingLabels) #使用改进的随即上升梯度训练
errorCount = 0; numTestVec = 0.0
for line in frTest.readlines():
numTestVec += 1.0
currLine = line.strip().split('\t')
lineArr =[]
for i in range(len(currLine)-1):
lineArr.append(float(currLine[i]))
if int(classifyVector(np.array(lineArr), trainWeights[:,0]))!= int(currLine[-1]):
errorCount += 1
errorRate = (float(errorCount)/numTestVec) * 100 #错误率计算
print("测试集错误率为: %.2f%%" % errorRate)
def classifyVector(inX, weights):
prob = sigmoid(sum(inX*weights))
if prob > 0.5: return 1.0
else: return 0.0
if __name__ == '__main__':
colicTest()
可以看到由于数据集中存在 30%的值是缺失的,因此测试集的错误率会比较高,这是很难避免的。