第五章 Logistic回归

  • 5.1基于Logistic回归和Sigmoid函数的分类
  • 5.2基于最优化方法的最佳回归系数确定
  • 5.2.1梯度上升法
  • 5.2.2训练算法:使用梯度上升找到最佳参数
  • 5.2.3分析数据:画出决策边界
  • 5.2.3训练算法:随机梯度上升
  • 5.3示例:从疝气病病症预测病马的死亡率
  • 5.3.1准备数据:处理数据中的缺失值
  • 5.3.2测试算法:用Logistic回归进行分类
  • 5.4小结


5.1基于Logistic回归和Sigmoid函数的分类

Logistic回归:

优点:计算代价不高,易于理解和实现。
缺点:容易欠拟合,分类精度可能不高。
适应数据类型:数值型和标称型数据。

    Sigmoid函数的计算公式如下:

sigmoid函数有点 sigmoid函数的优缺点_回归

sigmoid函数有点 sigmoid函数的优缺点_逻辑回归_02


    上图为Sigmoid函数在不同坐标尺度下的两条曲线图。可以看出,如果横坐标刻度足够大,Sigmoid函数看起来很像一个阶跃函数。

    因此,为了实现Logistic回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,将这个总和带入Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分入1类。小于0.5则被归入0类。所以,Logistic回归也可以被看成是一种概率估计。

5.2基于最优化方法的最佳回归系数确定

sigmoid函数有点 sigmoid函数的优缺点_sigmoid函数有点_03,由下面公式得出:
sigmoid函数有点 sigmoid函数的优缺点_sigmoid函数有点_04
    如果采用向量的写法,上述公式可以写成sigmoid函数有点 sigmoid函数的优缺点_逻辑回归_05其中的向量x是分类器的输入数据,向量w也就是我们要找到的最佳参数(系数),从而使得分类器尽可能的精确。

5.2.1梯度上升法

sigmoid函数有点 sigmoid函数的优缺点_逻辑回归_06 ,则函数sigmoid函数有点 sigmoid函数的优缺点_机器学习_07的梯度由下式表示:

sigmoid函数有点 sigmoid函数的优缺点_回归_08

    这个梯度意味着要沿x的方向移动sigmoid函数有点 sigmoid函数的优缺点_机器学习_09,沿y的方向移动sigmoid函数有点 sigmoid函数的优缺点_数据_10。一个具体的函数例子如下图所示:

sigmoid函数有点 sigmoid函数的优缺点_回归_11


    上图中的梯度上升算法沿梯度方向移动了一步。可以看到,梯度算子总是指向函数值增长最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记住 sigmoid函数有点 sigmoid函数的优缺点_数据_12用向量来表示的话,梯度算法的迭代公式如下:

sigmoid函数有点 sigmoid函数的优缺点_数据_13

5.2.2训练算法:使用梯度上升找到最佳参数

    梯度上升法的伪代码如下:
    每个回归系数初始化为1
    重复R次:
        计算整个数据集的梯度
        使用alpha*gradient更新回归系数的向量
        返回回归系数

创建一个名为logRegres.py的文件,添加下列代码:

def loadDataSet():  # 得到了数据矩阵和标签矩阵
    dataMat = []
    labelMat = []
    fr = open(r'testSet.txt')  # 这里相对路径一直找不到文件,改用绝对路径
    for line in fr.readlines():
        lineArr = line.strip().split()  # strip()去除首尾空格,split拆分字符串,默认为空格拆分
        dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])  # 这里的1.0是x0
        labelMat.append(int(lineArr[2]))
    return dataMat, labelMat


def sigmoid(inX):  # 实现的是sigmoid函数
    return 1.0 / (1 + exp(-inX))


def gradAscent(dataMatIn, classLabels):  # 实现的是梯度上升算法
    dataMatrix = mat(dataMatIn)  # 把list数据类型变成矩阵
    labelMat = mat(classLabels).transpose()  # transpose是矩阵转置
    m, n = shape(dataMatrix)  # 行、列
    alpha = 0.001
    maxCycles = 500  # 迭代次数
    weights = ones((n, 1))
    for k in range(maxCycles):
        h = sigmoid(dataMatrix * weights)  # 维度100*3 * 3*1,相当于给每一维都做了sigmoid,这里是预测值,Sigmoid(数据矩阵x权重),是0/1给聚类
        # dataMatrix是样本矩阵,每一行代表着一个数据
        error = (labelMat - h)  # 梯度上升方向,聚类和真实值的误差
        weights = weights + alpha * dataMatrix.transpose() * error  # 维度3*100 * 100*1,权重更新的方向
    return weights

测试代码:

import logRegres

dataArr,labelMat = logRegres.loadDataSet()
print(logRegres.gradAscent(dataArr,labelMat))

结果:

sigmoid函数有点 sigmoid函数的优缺点_数据_14

5.2.3分析数据:画出决策边界

    解出的回归系数确定了不同类别数据之间的分割线。接下来要画出分割线,从而使得优化的过程便于理解。
在logRegres.py中添加代码:

def plotBestFit(wei):
    import matplotlib.pyplot as plt
    weights = wei#getA()是将numpy矩阵转化成数组形式,这里删去了
    dataMat, labelMat = loadDataSet()
    dataArr = array(dataMat)#转成array类型
    n = shape(dataArr)[0]#shape是维度,[0]代表维度的第一个值
    xcord1 = [];ycord1 = []
    xcord2 = [];ycord2 = []
    for i in range(n):#根据标签0/1把数据分到两个矩阵数组中
        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')
    #plt.scatter(x, y, c="b", label="scatter figure") x: x轴上的数值 y: y轴上的数值 c:散点图中的标记的颜色label:标记图形内容的标签文本
    x = arange(-3.0,3.0,0.1)#生成一维数组,从-3到3步长0.1
    y = (-weights[0]-weights[1]*x)/weights[2]#weights的三个分量分别是三个参数,数值意义还没搞懂
    # 0 = w0 + w1x1 + w2y  -> y = (-w0 -w1x1)/w2   z = 0时,sigmoid函数取中间值,也就是0.5
    #分界线是0=w0x0 + w1x1 + w2x2,这里要画关于x1和x2的分界线所以这一步是求在x1不断变化时x2的值
    ax.plot(x,y)
    plt.xlabel('X1');plt.ylabel('X2');
    plt.show()

测试代码:

import logRegres

dataArr,labelMat = logRegres.loadDataSet()
weights = logRegres.gradAscent(dataArr,labelMat)
logRegres.plotBestFit(weights.getA())

结果:

sigmoid函数有点 sigmoid函数的优缺点_机器学习_15


分析:

    从上图可以看出分类结果很好,只错分了二到五个点。但是,尽管例子简单且数据集很小,这个方法确需要大量的计算(300次乘法)。因此,需要对算法稍作改进。

5.2.3训练算法:随机梯度上升

    梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理100个左右的数据集时尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。
    随机梯度上升算法可以写成如下的伪代码:
    所有回归系数初始化为1
    对数据集中每个样本
        计算该样本的梯度
        使用alpha*gradient更新回归系数值
    返回回归系数值
在logRegres.py中添加下列代码:

def stocGradAscent0(dataMatrix,classLabels):#随机梯度上升算法,来一个更新一次权重系数
    m,n = shape(dataMatrix)
    alpha = 0.01
    weights =ones(n)
    for i in range(m):
        h = sigmoid(sum(dataMatrix[i]*weights))
        error = classLabels[i] - h
        weights = weights + alpha * error * dataMatrix[i]
    return weights

测试代码:

import logRegres
from numpy import *

dataArr,labelMat = logRegres.loadDataSet()
weights = logRegres.stocGradAscent0(array(dataArr),labelMat)
logRegres.plotBestFit(weights)

结果:

sigmoid函数有点 sigmoid函数的优缺点_逻辑回归_16


分析:

    这个分类器效果并不完美,但是这只是一次算法,下面将其迭代150次。

在logRegres.py中添加下列代码:

import numpy as np
def stocGraAscent0(dataMatrix,classLabels,numIter=150):#改进的随机梯度上升算法
    m,n = np.shape(dataMatrix)
    weights = np.ones(n)
    for j in range(numIter):
        dataIndex = list(range(m))
        for i in range(m):
            alpha = 4/(1.0+j+i)+0.01
            randIndex = int(np.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

测试代码:

import logRegres
from numpy import *

dataArr,labelMat = logRegres.loadDataSet()
weights = logRegres.stocGraAscent0(array(dataArr),labelMat)
logRegres.plotBestFit(weights)

结果:

sigmoid函数有点 sigmoid函数的优缺点_回归_17


分析:

    代码改进的地方有两处。第一处是alpha在每次迭代的时候都会调整,这会缓解数据波动或者高配波动。另一处是通过随机选取样本来更新回归系数,这样会减少周期性波动。

    下图为梯度上升算法和改进的梯度上升算法中回归系数与迭代次数的关系。可以看出,改进后的随机梯度上升算法随着迭代次数的增加回归系数与迭代次数关系更加接近阶跃函数曲线。

sigmoid函数有点 sigmoid函数的优缺点_数据_18

5.3示例:从疝气病病症预测病马的死亡率

5.3.1准备数据:处理数据中的缺失值

    处理缺失值有以下做法:
    1、使用可用特征的均值来填补缺失值
    2、使用特殊值来填补缺失值,比如-1
    3、忽略有缺失值的样本
    4、使用相似样本均值填补缺失值
    5、使用另外机器学习算法预测缺失值
    本数据集预处理:
    1、所有的特征缺失值必须用一个实数值来替换,因为numpy数据类型不允许包含缺失值,这里选择0,不会影响系数
    2、如果一条数据的类别标签缺失,那么丢弃该数据。

5.3.2测试算法:用Logistic回归进行分类

在logRegres.py中添加下列代码:
分类函数,输入特征向量和回归系数,输出类别1或0。

def classifyVector(inX,weights):
    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) #n行22列
        trainingLabels.append(float(currLine[21]))
    trainWeights = stocGraAscent1(np.array(trainingSet),trainingLabels,500)
    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(np.array(lineArr),trainWeights))!=int(currLine[21]):
            errorCount+=1
    errorRate = (float(errorCount)/numTestVec)
    print(f'the error rate is {errorRate}')
    return errorRate
#多次测试,求错误率
def multiTest():
    numTest = 10
    errorSum= 0.0
    for k in range(numTest):
        errorSum += colicTest()
    print(f'after {numTest} iterations the average error rate is {errorSum/float(numTest)}')

测试代码:

import logRegres

logRegres.multiTest()

结果:

sigmoid函数有点 sigmoid函数的优缺点_逻辑回归_19

5.4小结

    随机梯度上升算法与梯度上升算法的效果相当,但占用更少的计算资源。并且随机梯度上升是一个在线算法。它可以在新数据到来时就完成参数更新,而不需要重新读取整个数据集来进行批处理运算。