逻辑回归从原理到算法的实现

逻辑回归原理介绍:

线性回归能对连续值进行预测,而现实生活中常见的是分类问题。既然能够用线性回归预测出连续值结果,那根据结果设定一阈值是不是就可以解决这个问题了呢?事实上对于很标准的情况下确实是可以的。但很多实际的情况下,我们需要学习的分类数据并没有那么精准,因此引出逻辑回归。即将线性回归预测的值通过sigmoid函数映射到0和1之间。

 定义代价函数,其实是一种衡量预测的结果与实际结果差距的函数,比如线性回归的代价函数是所有预测结果与实际结果的平方差和,当然我们也可以和线性回归类比得到代价函数,但这里的预测结果是通过S型函数映射的结果,用平方差和会出现非凸函数的问题,简单的说就是函数会有多个局部极值点。我们希望代价函数是一个凸函数,实际上我们也找到了一个适合逻辑回归的代价函数:

对于类别为1的代价函数为,图像为下图左边蓝色线,当预测结果为0时,代价函数值非常大,预测结果越接近于1即越接近真实类别1,代价函数越小;

对于类别为0的代价函数为,图像为下图右边橙色线,当预测结果为1时,代价函数值非常大,预测结果越接近于0即越接近真实类别0,代价函数越小。

 综上,逻辑回归的代价函数可以写为如下形式:

 2.理解梯度上升法:求-x^2+4x的最大值

这个函数的极值怎么求?显然这个函数开口向下,存在极大值,它的函数图像如下:

0?wx_fmt=png

求极值,先求函数的导数: 

令导数为0,可求出x=2即取得函数f(x)的极大值。极大值等于f(2)=4

        但是真实环境中的函数不会像上面这么简单,就算求出了函数的导数,也很难精确计算出函数的极值。此时我们就可以用迭代的方法来做。就像爬坡一样,一点一点逼近极值。这种寻找最佳拟合参数的方法,就是最优化算法。爬坡这个动作用数学公式表达即为:

0?wx_fmt=png

       其中,α为步长,也就是学习速率,控制更新的幅度。效果如下图所示:

0?wx_fmt=png

可以设置最大迭代次数或更新前后两次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

猜你喜欢

转载自blog.csdn.net/qq_24946843/article/details/84253919