逻辑回归原理介绍:
线性回归能对连续值进行预测,而现实生活中常见的是分类问题。既然能够用线性回归预测出连续值结果,那根据结果设定一阈值是不是就可以解决这个问题了呢?事实上对于很标准的情况下确实是可以的。但很多实际的情况下,我们需要学习的分类数据并没有那么精准,因此引出逻辑回归。即将线性回归预测的值通过sigmoid函数映射到0和1之间。
定义代价函数,其实是一种衡量预测的结果与实际结果差距的函数,比如线性回归的代价函数是所有预测结果与实际结果的平方差和,当然我们也可以和线性回归类比得到代价函数,但这里的预测结果是通过S型函数映射的结果,用平方差和会出现非凸函数的问题,简单的说就是函数会有多个局部极值点。我们希望代价函数是一个凸函数,实际上我们也找到了一个适合逻辑回归的代价函数:
对于类别为1的代价函数为,图像为下图左边蓝色线,当预测结果为0时,代价函数值非常大,预测结果越接近于1即越接近真实类别1,代价函数越小;
对于类别为0的代价函数为,图像为下图右边橙色线,当预测结果为1时,代价函数值非常大,预测结果越接近于0即越接近真实类别0,代价函数越小。
综上,逻辑回归的代价函数可以写为如下形式:
2.理解梯度上升法:求-x^2+4x的最大值
这个函数的极值怎么求?显然这个函数开口向下,存在极大值,它的函数图像如下:
求极值,先求函数的导数:
令导数为0,可求出x=2即取得函数f(x)的极大值。极大值等于f(2)=4
但是真实环境中的函数不会像上面这么简单,就算求出了函数的导数,也很难精确计算出函数的极值。此时我们就可以用迭代的方法来做。就像爬坡一样,一点一点逼近极值。这种寻找最佳拟合参数的方法,就是最优化算法。爬坡这个动作用数学公式表达即为:
其中,α为步长,也就是学习速率,控制更新的幅度。效果如下图所示:
可以设置最大迭代次数或更新前后两次x值 的变化小于某一阈值来作为终止条件。我们可以编写Python3代码,来实现这一过程:
可看到结果已经很接近2了。
def gradient(x):
return -2*x+4
def gradient_Ascent():
x_old = 0
x_new = 1
alpha = 0.001
permission = 0.000001
while(x_new-x_old>permission):#也可以设置最大迭代次数作为终止条件
x_old = x_new
x_new = x_old + alpha*gradient(x_old)
return x_new
gradient_Ascent()
>>1.9995013500160062
3.逻辑回归中用梯度下降法求回归系数
逻辑回归代价函数对某一个回归系数的梯度推导:
对于所有的回归系数,写成一个矩阵形式如下:
其中 为步长(,为误差。
import numpy as np
from numpy import mat
def loadDataSet():
dataSet = [];classLabels = []
fr = open('testSet.txt')
for line in fr.readlines():
lineArr = line.strip().split()
dataSet.append([1,float(lineArr[0]),float(lineArr[1])])
classLabels.append(int(lineArr[2]))
return dataSet,classLabels
#S型函数,任何数都可以通过该函数映射到0和1之间,而且h'(z) = h(z)(1-h(z))
def sigmoid(Z):
return 1.0/(1+np.exp(-Z))
#逻辑回归梯度下降算法
def gradient_dscent(dataSet,classLabels):
dataMat = mat(dataSet)
labelMat = mat(classLabels).T
m,n = dataMat.shape
alpha = 0.001
Iteration = 500
weight = mat(np.ones((n,1)))
for i in range(Iteration):
h = sigmoid(dataMat*weight)
error = h - labelMat
weight = weight -alpha*dataMat.T*error
return weight
dataSet,classLabels = loadDataSet()
weight = gradient_dscent(dataSet,classLabels)
weight
>>matrix([[ 4.12414349],
[ 0.48007329],
[-0.6168482 ]])
4.通过求解的回归系数画出决策边界
#画出决策边界
import matplotlib.pyplot as plt
def plotBestFit(w):
dataSet,classLabels = loadDataSet()
dataArr = np.array(dataSet)
n = dataArr.shape[0]
xcord1 = []; ycord1 = []
xcord2 = []; ycord2 = []
for i in range(n):
if(classLabels[i]==1):
xcord1.append(dataArr[i,1]);ycord1.append(dataArr[i,2])
else:
xcord2.append(dataArr[i,1]);ycord2.append(dataArr[i,2])
plt.subplot(1,1,1)
plt.scatter(xcord1,ycord1,s = 30,c = 'red',marker = 's')
plt.scatter(xcord2,ycord2,s = 30,c = 'green',marker = 'o')
x = np.arange(-3.0,3.0,0.1)
y = (-w[0]-w[1]*x)/w[2]
plt.plot(x,y)
plt.xlabel('X1');plt.ylabel('X2')
plotBestFit(np.array((weight)))
这个分类效果相当不错,从图上看知错分了两到四个点。但是, 尽管例子简答且数据集很小,这个方法却需要大量的计算(每迭代一次需要300次乘法)。下面对该算法稍作改进,因此引进随机梯度下降算法。
5.随机梯度下降算法
上面的批量梯度随机下降算法在每次更新时都需要遍历整个数据集,该方法在处理100个左右的数据集尚可,但如果有数十亿样本和成千上万个特征,那么该方法的计算复杂度就太高了。一种改进的方法是一次用一个样本点来更新回归系数,该方法称为随机梯度下降算法。
def stocGradDesc(dataSet,classLabels):
a = np.array(dataSet)
b = np.array(classLabels)
m,n = a.shape
alpha = 0.01
weight2 = np.ones((1,3))
for j in range(200):#在整个数据集上运行200次
for i in range(m):
h = sigmoid(np.sum(a[i]*weight2))#注意是按列求和
error = h - b[i]
weight2 = weight2 - alpha*a[i]*error
return weight2
weight2 = stocGradDesc(dataSet,classLabels)
print(weight2)
plotBestFit(np.array((weight2.reshape(-1,1))))
>>[[ 7.11282257 0.7158515 -1.0490159 ]]
注意这里的h和error都是数值,而上面的都是矩阵。
用单个数据集来更新回归系数,而且只迭代了200次,也能达到不错的效果,如下图所示:
6.随机梯度下降算法回归系数随迭代次数的变化情况
def stocGradDesc(dataSet,classLabels):
a = np.array(dataSet)
b = np.array(classLabels)
m,n = a.shape
alpha = 0.01
weight2 = np.ones((1,3))
weight2Arr = np.ones((1,3))
for j in range(200):#在整个数据集上运行200次
for i in range(m):
h = sigmoid(np.sum(a[i]*weight2))#注意是按列求和
error = h - b[i]
weight2 = weight2 - alpha*a[i]*error
weight2Arr = np.concatenate((weight2Arr,weight2),axis = 0)
return weight2Arr
#随机梯度下降算法回归系数随迭代次数的变化情况
weight2Arr = stocGradDesc(dataSet,classLabels)
a = weight2Arr[1:,:]
fig,axs = plt.subplots(1,3,figsize=(60,10))
plt.subplot(1,3,1);plt.plot(np.arange(200*100),a[:,0]);plt.ylabel('w0')
plt.subplot(1,3,2);plt.plot(np.arange(200*100),a[:,1]);plt.ylabel('w1');plt.ylabel('w1')
plt.subplot(1,3,3);plt.plot(np.arange(200*100),a[:,2]);plt.ylabel('w0');plt.ylabel('w0')
结论:可看到回归系数更新的时候,在大的波动停止后,还有一些小的周期性波动。不难理解,产生这种现象的原因是存在一些不能正常分类的样本点(数据集并非线性可分),在每次迭代时会引发系数的剧烈改变。我们期望算法能避免来回波动,从而更快的收敛到某个值。这里迭代了200次都还没趋于稳定。
7.改进的随机梯度下降算法¶
alpha随着迭代次数不断减小
通过随机选取样本来更新回归系数
def stocGradDesc2(dataSet,classLabels):
a = np.array(dataSet)
b = np.array(classLabels)
m,n = a.shape
weight3 = np.ones((1,3))
for j in range(20):
dataIndex = list(range(m))
for i in range(m):
alpha = 4/(1.0+j+i)+0.01#alpha随着迭代次数不断减小
randIndex = int(np.random.uniform(0,len(dataIndex)))#通过随机选取样本来更新回归系数
h = sigmoid(np.sum(a[randIndex]*weight3))
error = h - b[randIndex]
weight3 = weight3 - alpha*a[randIndex]*error
del(dataIndex[randIndex])
return weight3
weight3 = stocGradDesc2(dataSet,classLabels)
print(weight3)
plotBestFit((weight3.reshape(-1,1)))
>>[[12.17787388 0.69543104 -1.81984809]]
每次随机抽取样本来更新回归系数,而且步长随着迭代次数的增加而逐渐减小,改进的随机梯度下降算法只迭代20次,就能达到不错的效果,如下图所示:
8、改进的随机梯度下降算法回归系数随迭代次数的变化情况
def stocGradDesc2(dataSet,classLabels):
a = np.array(dataSet)
b = np.array(classLabels)
m,n = a.shape
weight3 = np.ones((1,3))
weight3Arr = np.ones((1,3))
for j in range(20):
dataIndex = list(range(m))
for i in range(m):
alpha = 4/(1.0+j+i)+0.01#alpha随着迭代次数不断减小
randIndex = int(np.random.uniform(0,len(dataIndex)))#通过随机选取样本来更新回归系数
h = sigmoid(np.sum(a[randIndex]*weight3))
error = h - b[randIndex]
weight3 = weight3 - alpha*a[randIndex]*error
weight3Arr = np.concatenate((weight3Arr,weight3),axis = 0)
del(dataIndex[randIndex])
return weight3Arr
#改进随机梯度下降算法回归系数随迭代次数的变化情况
weight3Arr = stocGradDesc2(dataSet,classLabels)
a = weight3Arr[1:,:]
fig,axs = plt.subplots(1,3,figsize=(60,10))
plt.subplot(1,3,1);plt.plot(np.arange(20*100),a[:,0]);plt.ylabel('w0')
plt.subplot(1,3,2);plt.plot(np.arange(20*100),a[:,1]);plt.ylabel('w1');
plt.subplot(1,3,3);plt.plot(np.arange(20*100),a[:,2]);plt.ylabel('w0');
结论:可看到改图与前面的图有两点不一样。第一点是该图没有像前面图那样出现周期性的波动,这归功于随机梯度下降里的样本随机选取机制;第二点是,该图只迭代了20次就逐渐趋于稳定,而前面的图迭代了200次还没趋于稳定,所以在收敛速度上随机梯度下降也要更快。
参考文献:《机器学习实战》 第5章内容
https://blog.csdn.net/SzM21C11U68n04vdcLmJ/article/details/78221784