自学内容网 自学内容网

机器学习第五章笔记——logistic回归(逻辑回归)

目录

引言

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

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

2.1 梯度上升法 

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

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

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

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

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


引言

        这个章节中将首次接触到最优化算法,假设目前存在一些数据点,用一条之间对这些点进行拟合,这个拟合过程就被称为回归。利用Logistic回归进行分类的主要思想就是:根据现有数据对分类边界线建立回归公式,以此进行分类。回归一词源于最佳拟合,即要找到最佳拟合参数集。

        训练分类器时的做法其实就是在寻找最佳拟合参数,使用的就是最优化算法。二值型输出分类器的数学原理。

        Logistic回归的一般过程

        1、收集数据:采用任意方法收集数据

        2、准备数据:由于需要计算距离,所以就要求数据类型为数值型,结构化数据格式则最佳

        3、分析数据:采用任意方法对数据进行分析

        4、训练算法:大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数

        5、测试算法:一旦完成训练步骤,分类就会很快

        6、使用算法:第一步:输入一些数据,并将其转换成对应的结构化数值,接着基于训练好的回归系数就可以对这些数值进行简单的回归计算,判定它们属于哪个类别;这之后在输出的类别上做一些其他的分析工作。

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

        优点:计算代价不高,易于理解与实现

        缺点:容易欠拟合,分类精度可能不高

        适用的数据类型:数值型和标称型数据 

需要的函数应该是能够接受所有的输入并预测出类别,假设,已知有两个类,上述函数输出0或1。在数学中,一般将这类函数称为海维赛德阶跃函数亦或者更为熟悉的单位阶跃函数。不过阶跃函数的问题在于函数在跳跃点上从0瞬间跳跃到1,瞬间跳跃的过程往往很难进行处理。Sigmoid函数与阶跃函数有着相同的性质,且在数学上更容易处理,计算公式为:

图1  两种坐标尺度下的sigmoid函数图

        图1给出了Sigmoid函数在不同尺度坐标下的两条曲线图。x为0时,Sigmoid函数值就为0.5,随着x的增大,对应的Sigmoid值将越来越逼近1,反之就会逼近0,当横坐标的刻度足够大时,Sigmoid函数看起来就很像一个阶跃函数了。

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

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

        Sigmoid函数的输入记为z,由下面的公式可以得出:

z = w_{0}x_{0} + w_{1}x_{1} + w_{2}x_{2} + .... + w_{n}x_{n}

采用向量的写法,上述公式可以写成z = w^{T}x,它表示将两个数值向量的对应元素相乘然后全部相加就可以得到z值,向量x是分类器的输入数据,向量w是我们想要找到的最佳参数。

2.1 梯度上升法 

        梯度上升法基于的思想是:找到某函数的最大值,最好的方法是沿着该函数的梯度方向去探寻,以函数f(x,y)的梯度为例,可以将其写成:

        该梯度表示要沿着x方向移动\frac{\partial f(x,y)}{\partial x},再沿y方向移动\frac{\partial f(x,y)}{\partial y},函数f(x,y)必须在待计算的点上有定义并且可微。

图2  梯度上升算法到达每一个点后都将重新估计移动的方向

        可以从图2中看到梯度上升算法沿梯度方向移动了一步,其总是指向函数值增长最快的方向。这里所说的是移动方向,而非移动量的大小。这个量值称为是步长,记作\alpha。如果用向量来表示,梯度算法的迭代公式为:

w: = w+\alpha \bigtriangledown _{w}f(w)

        这个公式将会被一直执行,直到达到某个停止条件(迭代次数达到指定值或算法达到某个可以允许的误差)为止。 

        除梯度上升算法外,我们平时最常听到的是梯度下降算法,与梯度上升算法类似,只是要将公式中的加法改成减法。

                w := w - \alpha \bigtriangledown _{w}f(w)

        梯度上升算法用来求函数的最大值,而梯度下降算法用来求函数的最小值。

图3 简单数据集

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

        图3中有100个样本点,每一个点包含有两个数值型特征:X1和X2,通过梯度上升法求得最佳回归系数:

        梯度上升优化算法:

def loadDataSet():
    dataMat = []  # 创建数据列表
    labelMat = []  # 创建标签列表
    fr = open('D:\\learning\\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]))  # 将lineArr中第三个数据设为标签
    fr.close()  # 关闭文本
    return dataMat, labelMat


def sigmoid(inX):
    return 1.0 / (1 + np.exp(-inX))


def gradAscent(dataMatrix, classLabels, numIter=150):
    dataMatrix = np.mat(dataMatrix)
    labelMat = np.mat(classLabels).transpose()
    m, n = np.shape(dataMatrix)  # m,n分别为dataMatrix的长和宽
    alpha = 0.001  # 设置向目标移动的步长
    maxCycles = 500  # 迭代次数
    weights = np.ones((n, 1))  # 创建零矩阵
    for k in range(maxCycles):  # 迭代开始
        h = sigmoid(dataMatrix * weights)  
        error = (labelMat - 1)
        weights = weights + alpha * dataMatrix.transpose() * error
    return weights

观察实际执行效果:

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

梯度上升算法的实际工作是在函数gradAscent()中完成的,函数有两个参数,dataMathIn的一个二维数,每列分别代表着一个不同的特征。每行则代表着每个训练样本。采用100个样本的简单数据集,包含两个特征X1,X2以及第0维特征X0。

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

        上面的学习中解出了一组回归系数,确定了不同类别数据之间的分隔线。下面就如何在不同类之间画出分隔线进行学习。

def plotBestFit(weights):
    dataMat, labelMat = loadDataSet()  # 获取数据和标签
    dataArr = np.array(dataMat)  #
    n = np.shape(dataMat)[0]  # n为dataMat的行数
    xcord1 = []; ycord1 = []
    xcord2 = []; ycord2 = []
    for i in range(n):
        if int(labelMat[i]) == 1:
            xcord1.append(dataArr[i, 1]);  ycord1.append(dataArr[i, 2])  # 分别向创建的x,y数组中添加dataArr中的第一、二列数据
        else:
            xcord2.append(dataArr[i,1]); ycord2.append(dataArr[i,2])
    fig = plt.figure()  # 创建图表
    ax = fig.add_subplot(111)
    ax.scatter(xcord1, ycord1, s = 20, c = 'red', marker = 's',alpha=.5)  # 设置图表中曲线的格式
    ax.scatter(xcord2, ycord2, s = 20, c = 'green',alpha=.5)
    x = np.arange(-3.0, 3.0, 0.1)   # 设置x的范围与间隔
    y = (-weights[0] - weights[1] * x) / weights[2]  # 计算x对应y的值
    ax.plot(x, y)  # 绘图
    plt.xlabel('X1'); plt.ylabel('X2')
    plt.show()

测试绘图,输入:

import logRegres
from numpy import *
dataArr, labelMat = logRegres.loadDataSet()
weights = logRegres.gradAscent(dataArr, labelMat)
logRegres.plotBestFit(weights.getA())

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

        梯度上升算法在每次更新回归系数时都需要遍历整个数据集,这个方法处理100个左右的数据集时尚可,当处理数10亿样本以及成千上万个特征时,这个方法的计算复杂度就很高了。梯度上升算法是一次仅用一个样本店来更新回归系数。由于可以在新样本到来时对分类器进行增量式更新,故随机梯度上升算法是一个在线学习算法。一次处理所有的数据就被称为”批处理“。

        随机梯度上升算法的实现代码:

def stocDradAscent0(dataMatrix, classLabels):
    m, n = np.shape[dataMatrix]
    alpha = 0.01
    weights = np.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

随机梯度上升算法与梯度上升算法在代码上很相似,但后者的变量h和误差error都是向量,而前者则全是数值。前者没有矩阵的转换过程所有变量的数据类型都是Numpy数组。

测试算法:

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

执行结果: 

图4  随机梯度算法执行结果

从图中可以看出,最终的结果并不是那么完美,分类器错分了三分之一的样本数据。

图5  运行随机梯度上升算法,在数据集的一次遍历中回归系数与迭代次数之间的关系

上图展示了随机梯度上升算法在200词迭代过程中回归系数的变化情况。其中X2只用了50次迭代就达到了稳定值,但系数1和0则需要更多次迭代。同时从图中还能看到在大的波动停止后,还是会有一些小的周期性波动。产生这种现象的原因是存在一些不能正确分类的样本点。通过修改随机梯度上升算法解决上述问题:

def stocDradAscent1(dataMatrix, classLabels, numIter=150):
    m, n = np.shape(dataMatrix)
    weights = np.ones(n)
    for j in range(numIter):
        for i in range(m):
            alpha = 4 / (1.0 + j +i) + 0.01
            randIndex = int(np.random.uniform(0, len(dataMatrix)))
            h = sigmoid(sum(dataMatrix[randIndex] * weights))
            error = classLabels[randIndex] - h
            weights = weights + alpha * error * dataMatrix[randIndex]
    return weights

        在改进该算法后,alpha在每次迭代的时候都会调整,这会缓解图5中的数据波动或高频波动。同时设置中alpha会随着迭代次数的增加而不断减小,但永远不会减小到0。这样做是为了保证在多次迭代后新数据仍然能具有一定的影响。当处理的问题是动态变化的时候,可以适当加大上述的常数项,保证新的值获得更大的回归系数。同时,通过随机选取样本来更新回归系数,减少了周期性的波动。

图6  随机选择样本和alpha动态减少机制的随机梯度上升算法生成的系数收敛图

测试:

dataArr, labelMat = logRegres.loadDataSet()
weights = logRegres.stocDradAscent1(array(dataArr), labelMat)

logRegres.plotBestFit(weights)

得出结果: 

可以很清楚的看出,在改进算法后分类器错误分类的情况得到了巨大改进。这里使用的默认迭代次数是150次。

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

        利用Logistic回归来预测患有疝气病的马的存活问题,步骤如下:

        1、收集数据:给定数据文件

        2、准备数据:利用python解析文本文件并填充缺失值

        3、分析数据:可视化并观察数据

        4、训练算法:使用优化算法,找到最佳的系数

        5、测试算法,为了量化回归结果,需要观察错误率,根据错误率决定是否回退到训练阶段,通过改变迭代的次数和步长等参数来得到更好的回归系数。

        6、使用算法,实现一个简单的命令行程序来收集马的症状并输出预测结果。

        在算法中除了部分指标主观和难以测量外,数据集中还有30%的值是缺失的。

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

        处理数据中的缺失值有以下一些方法:

        1、使用可用特征的均值来填补缺失值

        2、使用特殊值来填补缺失值,如-1

        3、忽略有缺失值的样本

        4、使用相似样本的均值添补缺失值

        5、使用另外的机器学习算法预测缺失值

        对数据进行预处理,以便可以顺利使用分类算法。预处理阶段需要做的两件事:1、所有缺失值必须用一个实数值来替换,使用的Numpy数据类型不允许包含缺失值,利用实数0替换所有的缺失值,这样做是在更新时不会影响系数的值,将回归系数的公式进行更新:

weights = weights + alpha * error * dataMatrix[randIndex]

当dataMatrix的某特征对应值为0,则特征系数就不做更新。同时Sigmoid(0) = 0.5,对于结果的预测不具有任何的倾向性,所有不会对误差项造成任何影响。

       当测试数据集中存在一条数据的类别标签已经缺失,简单做法是将该条数据丢弃。在Logistic回归进行分类时这种做法是合理的,但采用类似KNN的做法可能就不行了

3.2 测试算法 

        具体实验代码为:

def classifyVector(inX, weights):
    prob = sigmoid(sum(inX * weights))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0


def colicTest():
    frTrain = open('D:\\learning\\horseColicTraining.txt')
    frTest = open('D:\\learning\\horseColicTest.txt')
    trainingSet = []; trainingLabels = []
    for line in frTrain.readlines():
        currline = line.strip().split('\t')  # strip()去除首尾空格符,以'\t'为分隔符
        lineArr = []
        for i in range(21):
            lineArr.append(float(currline[i]))
        trainingSet.append(lineArr)  # 训练数据
        trainingLabels.append(float(currline[21]))  # 训练数据标签
    trainWeights = stocDradAscent1(np.array(trainingSet), trainingLabels, 500)  # 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]):  # 利用回归系数和特征向量计算对应的Sigmoid值,与标签进行比较,若不同错误数据加1
            errorCount += 1
    errorrate = (float(errorCount)/numtestvec)
    print('the error rate of this test is :%f' %errorrate)
    return errorrate


def multiTest():
    numTest = 15; errorSum = 0.0
    for k in range(numTest):
        errorSum += colicTest()
    print('after %d iterations the average error rate is: %f' %(numTest, errorSum/float(numTest)))

classifyVector函数用于计算Sigmoid值,值大于0.5返回1,小于0.5返回0。colicTest()函数用于打开测试集和训练集并对其数据进行格式化处理。数据导入后,将数据的最后一列作为标签,使用stocGrandAscent1()来计算回归系数向量。使用500次迭代,尽可能的让回归系数完全收敛。

最终结果为: 

从结果上看15次迭代后的平均错误率在32%,结果并不差,因为还存在30%的数据缺失。调整colicTest()中的迭代次数和stocGrandAscent1()中的步长可以进一步的降低平均错误率。


原文地址:https://blog.csdn.net/jgq1466693/article/details/126871005

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!