其他分享
首页 > 其他分享> > 《机器学习实战》学习笔记(三):Logistic回归

《机器学习实战》学习笔记(三):Logistic回归

作者:互联网

目录

一、Logistic回归函数

逻辑斯谛回归(logistic regression)是统计学习中的经典分类方法,属于对数线性模型,所以也被称为对数几率回归。这里要注意,虽然带有回归的字眼,但是该模型是一种分类算法,逻辑斯谛回归是一种线性分类器,针对的是线性可分问题。利用logistic回归进行分类的主要思想是:根据现有的数据对分类边界线建立回归公式,以此进行分类。这里的“回归”一词源于最佳拟合,表示要找到最佳拟合参数集,因此,logistic训练分类器时的做法就是寻找最佳拟合参数,使用的是最优化方法。
我们先说一个概念,事件的几率(odds),是指该事件发生的概率与该事件不发生的概率的比值。如果事件发生的概率是p,那么该事件的几率是p/(1-p)。取该事件发生几率的对数,定义为该事件的对数几率(log odds)或logit函数:
在这里插入图片描述
我对其取对数的理解大概是这样:事件发生的概率p的取值范围为[0,1],对于这样的输入,计算出来的几率只能是非负的(大家可以自己验证),而通过取对数,便可以将输出转换到整个实数范围内,下面是log函数的在二维坐标系中的图像,依照图像就会对标黄的那句话有一个形象的了解了。

那我们将输出转换到整个实数范围内的目的是什么呢?因为这样,我们就可以将对数几率记为输入特征值的线性表达式 :

在这里插入图片描述
其中,p(y =1|x)是条件概率分布,表示当输入为x时,实例被分为1类的概率,依据此概率我们能得到事件发生的对数几率。但是,我们的初衷是做分类器,简单点说就是通过输入特征来判定该实例属于哪一类别或者属于某一类别的概率。所以我们取logit函数的反函数,经如下推导:
在这里插入图片描述
公式1就是logistic函数。而Φ(x)是一个sigmoid函数,类似于阶跃函数的S型生长曲线:
在这里插入图片描述
上图给出了sigmoid函数在不同坐标尺度下的两条曲线图。当x为0时,sigmoid函数值为0.5。随着x的增大,对应的sigmoid函数的值将逼近于1;而随着x的减小,sigmoid函数的值将逼近于0。而第二幅图中我们能看到在横坐标的刻度足够大是,在x=0处sigmoid函数看起来很像阶跃函数。

那么对于公式1,我们可以这样解释:为了实现logistic回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,将这个总和带入sigmoid函数中。进而得到一个范围在0-1之间的数值。最后设定一个阈值,在大于阈值时判定为1,否则判定为0。以上便是逻辑斯谛回归算法是思想,公式就是分类器的函数形式。

二、确定最佳回归系数——极大似然估计+最优化

已经确定了logisti分类函数,有了分类函数,我们输入特征向量就可以得出实例属于某个类别的概率。但这里有个问题,权重w(回归系数)我们是不确定的。正如我们想的那样,我们需要求得最佳的回归系数,从而使得分类器尽可能的精确。

如何才能获得最佳的回归系数呢?这里就要用到最优化方法。在很多分类器中,都会将预测值与实际值的误差的平方和作为损失函数(代价函数),通过梯度下降算法求得函数的最小值来确定最佳系数。前面我们提到过某件事情发生的概率为p,在逻辑斯蒂回归中所定义的损失函数就是定义一个似然函数做概率的连乘,数值越大越好,也就是某个样本属于其真实标记样本的概率越大越好。如,一个样本的特征x所对应的标记为1,通过逻辑斯蒂回归模型之后,会给出该样本的标记为1和为-1的概率分别是多少,我们当然希望模型给出该样本属于1的概率越大越好。既然是求最大值,那我们用到的最优化算法就是梯度上升,其实也就是与梯度下降相反而已。

2.1 最大似然函数

我们需要先定义一个最大似然函数L,假定数据集中的每个样本都是独立的,其计算公式如下:
在这里插入图片描述
L(w)就是我们以上说的对于损失函数的最原始的定义,但我们还要进一步处理,那就是取对数,在进行极大似然估计的时候我们都知道要取对数,那为什么我们要取对数呢?

首先,在似然函数值非常小的时候,可能出现数值溢出的情况(简单点说就是数值在极小的时候因为无限趋近于0而默认其等于0,具体的可以点解数值移溢出的链接看看这篇博客中的讲解),使用对数函数降低了这种情况发生的可能性。其次,我们可以将各因子的连乘转换为和的形式,利用微积分中的方法,通过加法转换技巧可以更容易地对函数求导。

2.2 取似然函数的对数

在这里插入图片描述
这里似然函数是取最大值的,我们可以直接将其确定为损失函数然后使用梯度上升算法求最优的回归系数。但是既然是损失函数,应该还是取最小值好一点,且我们最耳熟能详的方法也是梯度下降,很多书中讲到这里也都是用的梯度下降算法,所以我将以上公式取反来使用梯度下降算法来求最小值(说到底都一样,无非就是取反,一个加一个减)。
在这里插入图片描述
接下来我们就要使用梯度下降求最小值了。首先,计算对数似然函数对j个权重的偏导:
在这里插入图片描述
先计算一下sigmoid函数的偏导:
在这里插入图片描述

我们的目标是求得使损失函数最小化的权重w,所以按梯度下降的方向不断的更新权重:
在这里插入图片描述
所以综上,我们将梯度下降的更新规则定义为:
在这里插入图片描述

三、梯度上升算法

3.1 算法原理

梯度上升法基于的思想是:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为∇ \nabla∇,则函数f(x,y)的梯度由下式表示:
在这里插入图片描述
其中,函数f(x,y)必须要在待计算的点上有定义并且可微。一个具体的函数例子见下图:
在这里插入图片描述
梯度上升算法到达每个点后都会重新估计移动的方向。从P0开始,计算完该点的梯度,函数就根据梯度移动到下一点P1。在P1点,梯度再次被重新计算,并沿新的梯度方向移动到P2.如此循环迭代,知道满足通知条件。迭代的过程中,梯度算子总是保证我们能选取到最佳的移动方向

上图中的梯度上升算法沿梯度方向移动了一步。可以看到,梯度算子总是指向函数值增长最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记作α \alphaα。用向量来表示的话,梯度上升算法的迭代公式如下:
在这里插入图片描述该公式将一直迭代执行,直至达到某个停止条件为止,b比如迭代次数达到某个值或者算法达到某个可以允许的误差范围。
注:如果是梯度下降法,那就是按梯度上升的反方向迭代公式即可,对应的公式如下:
在这里插入图片描述

3.1 源码实现

// An highlighted block
from numpy import *


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]))
    return dataMat, labelMat


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


def gradAscent(dataMatIn, classLabels):
    dataMatrix = mat(dataMatIn)  # convert to NumPy matrix
    labelMat = mat(classLabels).transpose()  # convert to NumPy matrix
    m, n = shape(dataMatrix)
    alpha = 0.001
    maxCycles = 500
    weights = ones((n, 1))
    for k in range(maxCycles):  # heavy on matrix operations
        h = sigmoid(dataMatrix * weights)  # matrix mult
        error = (labelMat - h)  # vector subtraction
        weights = weights + alpha * dataMatrix.transpose() * error  # matrix mult
    return weights


def plotBestFit(weights):
    import matplotlib.pyplot as plt
    dataMat, labelMat = loadDataSet()
    dataArr = array(dataMat)
    n = shape(dataArr)[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])
        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')
    x = arange(-3.0, 3.0, 0.1)
    y = (-weights[0] - weights[1] * x) / weights[2]  #最佳拟合直线
    ax.plot(x, y)
    plt.xlabel('X1');
    plt.ylabel('X2');
    plt.show()


if __name__ =='__main__':
    dataArr,labelMat=loadDataSet()
    weights=gradAscent(dataArr,labelMat)
    plotBestFit(weights.getA())

运行结果:
在这里插入图片描述
这里设置了sigmoid函数中的输入为0,就如我们在最开始的图中所看到的,x=0是两个类的分界线,分类效果尚可。

四、算法实例——从疝气病症状预测病马的死亡率

4.1 实战背景

本次实战内容,将使用Logistic回归来预测患疝气病的马的存活问题。这里的数据包含了500个样本和28个特征。这种病不一定源自马的肠胃问题,其他问题也可能引发马疝病。该数据集中包含了医院检测马疝病的一些指标,有的指标比较主观,有的指标难以测量,例如马的疼痛级别。另外需要说明的是,除了部分指标主观和难以测量外,该数据还存在一个问题,数据集中有30%的值是缺失的。下面将首先介绍如何处理数据集中的数据缺失问题,然后再利用Logistic回归和随机梯度上升算法来预测病马的生死。

4.2 准备数据

预处理数据做两件事:

原始的数据集经过处理,保存为两个文件:horseColicTest.txt和horseColicTraining.txt
在这里插入图片描述
在这里插入图片描述
有了这些数据集,我们只需要一个Logistic分类器,就可以利用该分类器来预测病马的生死问题了。

4.3 构建Logistic回归分类器

之前小节已经分析过原理,使用Logistic回归方法进行分类并不需要做很多工作,所需做的只是把测试集上每个特征向量乘以最优化方法得来的回归系数,再将乘积结果求和,最后输入到Sigmoid函数中即可。如果对应的Sigmoid值大于0.5就预测类别标签为1,否则为0。

# -*- coding:UTF-8 -*-
import numpy as np
import random

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

def stocGradAscent1(dataMatrix, classLabels, numIter=150):
    m,n = np.shape(dataMatrix)                                                #返回dataMatrix的大小。m为行数,n为列数。
    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                                            #降低alpha的大小,每次减小1/(j+i)。
            randIndex = int(random.uniform(0,len(dataIndex)))                #随机选取样本
            h = sigmoid(sum(dataMatrix[randIndex]*weights))                    #选择随机选取的一个样本,计算h
            error = classLabels[randIndex] - h                                 #计算误差
            weights = weights + alpha * error * dataMatrix[randIndex]       #更新回归系数
            del(dataIndex[randIndex])                                         #删除已经使用的样本
    return weights                                                             #返回

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 = stocGradAscent1(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(len(currLine)-1):
            lineArr.append(float(currLine[i]))
        if int(classifyVector(np.array(lineArr), trainWeights))!= 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()

运行结果:
在这里插入图片描述
错误率稳定且较低,所以可以得到如下结论:

对应的,在Sklearn中,我们就可以根据数据情况选择优化算法,比如数据较小的时候,我们使用liblinear,数据较大时,我们使用sag和saga。

五、拓展——使用Sklearn构建Logistic回归分类器

5.1 LogisticRegressioin

sklearn.linear_model模块提供了很多模型供我们使用,比如Logistic回归、Lasso回归、贝叶斯脊回归等,可见需要学习的东西还有很多很多。此次,我们使用LogisticRegressioin
在这里插入图片描述

5.2 源码实战

# -*- coding:UTF-8 -*-
from sklearn.linear_model import LogisticRegression

"""
函数说明:使用Sklearn构建Logistic回归分类器
"""
def colicSklearn():
    frTrain = open('horseColicTraining.txt')                                        #打开训练集
    frTest = open('horseColicTest.txt')                                                #打开测试集
    trainingSet = []; trainingLabels = []
    testSet = []; testLabels = []
    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]))
    for line in frTest.readlines():
        currLine = line.strip().split('\t')
        lineArr =[]
        for i in range(len(currLine)-1):
            lineArr.append(float(currLine[i]))
        testSet.append(lineArr)
        testLabels.append(float(currLine[-1]))
    classifier = LogisticRegression(solver='liblinear',max_iter=10).fit(trainingSet, trainingLabels)
    test_accurcy = classifier.score(testSet, testLabels) * 100
    print('正确率:%f%%' % test_accurcy)

if __name__ == '__main__':
    colicSklearn()

运行结果:
在这里插入图片描述
可以看到,正确率又高一些了。更改solver参数,比如设置为sag,使用随机平均梯度下降算法会发现产生了警告:
在这里插入图片描述
而产生警告是因为算法还没有收敛,更改max_iter为5000,再运行代码:
在这里插入图片描述
可以看到,对于这样的500个样本的数据集,sag算法需要迭代上千次才收敛,而liblinear只需要不到10次。据此可以得出一个结论:我们需要根据数据集情况,选择最优化算法。

六、实验总结

6.1 Logistic回归的优缺点

优点:

缺点:

6.2 一些思考

标签:分类器,函数,梯度,回归,笔记,学习,算法,weights,Logistic
来源: https://blog.csdn.net/Lim11t/article/details/121455809