机器学习——Logistic(逻辑)回归

目录

一、logistic回归简介

1.1Sigmoid函数

1.2梯度上升算法

二、logistic回归 代码实现

2.1加载数据

2.2Sigmoid函数代码

2.3回归梯度上升函数

 2.4画出决策边界

2.5随机梯度上升

2.6改进后的随机梯度上升

三、代码整合及小结 

3.1代码整合

3.2小结


一、logistic回归简介

  本篇首先阐述Logistic回归的定义,然后介绍一些最优化算法,其中包括基本的梯度上升法和一个改进的随机梯度上升法,这些最优化算法将用于分类器的训练。

  尽管逻辑回归的名称中包含“回归”,但它实际上是用于分类的,特别是二分类问题。逻辑回归通过计算输入特征的线性组合,使用Sigmoid函数将输出值压缩0和1之间,表示某个类别发生的概率。通过设定一个阈值(如0.5),可以将概率转换为类别输出。逻辑回归通常用于估计对象属于某一类别的概率,这是一个典型的分类任务。

1.1Sigmoid函数

   我们想要的函数应该是,能接受所有的输入然后预测出类别。例如,在两个类的情况下,上述函数输出0或1。或许你之前接触过具有这种性质的函数,该函数称为海维塞德阶跃函数,或者直接称为单位阶跃函数。然而,海维塞德阶跃函数的问题在于:该函数在跳跃点上从0瞬间跳跃到1,这个瞬间跳跃过程有时很难处理。幸好,另一个函数也有类似的性质R,且数学上更易处理,这就是Sigmoid函数。Sigmoid函数具体的计算公式如下:

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

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

        确定了分类器的函数形式之后,现在的问题变成了:最佳回归系数是多少?如何确定它们的大小?

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

        如果采用向量的写法,上述公式可以写成z=w^{T}x,它表示将这两个数值向量对应元素相乘然后全部加起来即得到z值。其中的向量x是分类器的输入数据,向量w也就是我们要找到的最佳参数(系数)从而使得分类器尽可能地精确。为了寻找该最佳参数,需要用到最优化理论的一些知识。

1.2梯度上升算法

        下面首先介绍梯度上升的最优化方法,我们将学习到如何使用该方法求得数据集的最佳参数。接下来,展示如何绘制梯度上升法产生的决策边界图,该图能将梯度上升法的分类效果可视化地呈现出来。最后我们将学习随机梯度上升算法,以及如何对其进行修改以获得更好的结果。

        你最经常听到的应该是梯度下降算法,它与这里的梯度上升算法是一样的,只是公式中的加法需要变成减法。

        梯度上升对应的公式可以写成:

                                                 

        梯度下降对应的公式可以写成:

                                                          

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

二、logistic回归 代码实现

图中有100个样本点,每个点包含两个数值型特征:X1和X2。

在此数据集上,我们将通过使用梯度上升法找到最佳回归系数,也就是拟合出Logistic回归模型的最佳参数。

2.1加载数据

#数据加载
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

2.2Sigmoid函数代码

#Sigmoid函数
def sigmoid(inX):
    return 1.0/(1+np.exp(-inX))

2.3回归梯度上升函数

  变量step是向目标移动的步长,Cycles是迭代次数。在for循环迭代完成后,将返回训练好的回归系数。需要强调的是,运算是矩阵运算。变量h不是一个数而是一个列向量,列向量的元素个数等于样本个数,这里是100。对应地,运算dataMatrix * weights代表的不止一次乘积计算,事实上该运算包含了300次的乘积。

def gradAscent(dataMathIn,classLabel):
    dataMatrix = np.mat(dataMathIn) #将列表转化为numpy的矩阵
    labelMat = np.mat(classLabel).transpose()   #将列表转化为矩阵,并求转置(transpose)
    m,n = np.shape(dataMatrix)
    step = 0.001
    Cycles = 500     #迭代次数
    weight = np.ones((n,1))
    for k in range(Cycles):
        h = sigmoid(dataMatrix*weight)#维度100*3*3*1,相当于给每一雏都做了sigmoid,这里是予预测值,Sigmoid(数据矩阵x权重),是0/1给聚类
        #dataMatrix是样本矩阵,每一行代表着一个数据
        error = labelMat - h    #(y-h)误差值,梯度上升方向,聚类和真实值的误差
        weight = weight + step*dataMatrix.transpose()*error #w=w+step*(y-h)*x梯度上升的迭代公式,维度3*100*100*1,权重更新的方向
    return weight

 2.4画出决策边界

#画出决策边界
def plotBestFit(weight):
    import matplotlib.pyplot as plt
    dataMat,labelMat = loadDataSet()
    dataArr = np.array(dataMat)
    n = np.shape(dataArr)[0] #[0]求dataArr的行数,1,是求列数
    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)   #添加subplot
    ax.scatter(xcord1,ycord1,s = 30,c='red',marker = 's')   #绘制正样本
    ax.scatter(xcord2,ycord2,s = 30, c = 'green')   #绘制负样本
    w = np.array(weight)
    x = np.arange(-3.0,3.0,0.1) #X轴的取值范围
    y = (-w[0] - w[1] * x) / w[2] #设sigmoid =0,因为sigmoid = 0是两种类别的分界线  sigmoid = w0x0+w1x1+w2x2 = 0
    ax.plot(x,y)
    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.show()
dataMat,labelMat = loadDataSet()
weight = gradAscent(dataMat,labelMat)
plotBestFit(weight)

画出来的图像为

分类结果目测还不错,只错分了四五个点。

但是,尽管例子简单且数据集很小,这个方法却需要大量的计算(300次乘法)。

2.5随机梯度上升

  梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理100个左右的数据集时尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。由于可以在新样本到来时对分类器进行增量式更新,因而随机梯度上升算法是一个在线学习算法。与“在线学习”相对应,一次处理所有数据被称作是“批处理”。

#随机梯度上升
def stoGradAscent0(dataMatrix,classLabels):
    dataMatrix=np.array(dataMatrix)
    m,n = np.shape(dataMatrix)
    step = 0.01
    weight = np.ones(n)
    for i in range(m):
        h = sigmoid(sum(dataMatrix[i]*weight))
        error = classLabels[i] - h
        weight = weight +step*error*dataMatrix[i]
    return weight
dataMat,labelMat = loadDataSet()
#weight=gradAscent(dataMat,labelMat)
weight=stoGradAscent0(dataMat,labelMat)
#weight=stoGradAscent1(dataMat,labelMat)
plotBestFit(weight)

画出来的图像为:

该图与之前的回归梯度上升函数绘制出来的图有一些相似之处。

可以看到,拟合出来的直线效果一般,这里的分类器错分了三分之一的样本。

2.6改进后的随机梯度上升

  第一处改进,一方面,step在每次迭代的时候都会调整,这会缓解图上的数据波第一处改进在①处。一方面,step在每次迭代的时候都会调整,这会缓解图上的数据波动或者高频波动。另外,虽然step会随着迭代次数不断减小,但永远不会减小到0,这是因为还存在一个常数项。必须这样做的原因是为了保证在多次迭代之后新数据仍然具有一定的影响。如果要处理的问题是动态变化的,那么可以适当加大上述常数项,来确保新的值获得更大的回归系数。另一点值得注意的是,在降低step的函数中,step每次减少1/(j+i),其中j是迭代次数,i是样本点的下标。这样当j<<max(i)时,step就不是严格下降的。避免参数的严格下降也常见于模拟退火算法等其他优化算法中。

   第二个改进的地方,这里通过随机选取样本来更新回归系数。这种方法将减少周期性的波动,这种方法每次随机从列表中选出一个值,然后从列表中删掉该值(再进行下一次迭代)。

#随机梯度算法的改进
def stoGradAscent1(dataMatrix,classLabels,numIter = 150):
    dataMatrix=np.array(dataMatrix)
    m,n = np.shape(dataMatrix)
    weight = np.ones(n)
    for j in range(numIter):
        dataIndex = list(range(m))
        for i in range(m):
            step = 4/(1.0+j+i)+0.01 #降低step的大小,每次减小1/(j+i)。
            randIndex = int(random.uniform(0,len(dataIndex))) #随机选取样本,可以实现随机选取更新
            h = sigmoid(sum(dataMatrix[randIndex]*weight))
            error = classLabels[randIndex] - h
            weight = weight + step*error*dataMatrix[randIndex]
            del(dataIndex[randIndex])
    return weight
dataMat,labelMat = loadDataSet()
#weight=gradAscent(dataMat,labelMat)
#weight=stoGradAscent0(dataMat,labelMat)
weight=stoGradAscent1(dataMat,labelMat)
plotBestFit(weight)

运行后得到的图像为:

可见,经改进后的随机梯度上升的优化效果与回归梯度上升的效果相近。

三、代码整合及小结 

3.1代码整合

import numpy as np
import array
import random
 
#数据加载
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
 
 
#Sigmoid函数
def sigmoid(inX):
    return 1.0/(1+np.exp(-inX))
 
 
def gradAscent(dataMathIn,classLabel):
    dataMatrix = np.mat(dataMathIn) #将列表转化为numpy的矩阵
    labelMat = np.mat(classLabel).transpose()   #将列表转化为矩阵,并求转置(transpose)
    m,n = np.shape(dataMatrix)
    step = 0.001
    Cycles = 500     #迭代次数
    weight = np.ones((n,1))
    for k in range(Cycles):
        h = sigmoid(dataMatrix*weight)#维度100*3*3*1,相当于给每一雏都做了sigmoid,这里是予预测值,Sigmoid(数据矩阵x权重),是0/1给聚类
        #dataMatrix是样本矩阵,每一行代表着一个数据
        error = labelMat - h    #(y-h)误差值,梯度上升方向,聚类和真实值的误差
        weight = weight + step*dataMatrix.transpose()*error #w=w+step*(y-h)*x梯度上升的迭代公式,维度3*100*100*1,权重更新的方向
    return weight

 
#画出决策边界
def plotBestFit(weight):
    import matplotlib.pyplot as plt
    dataMat,labelMat = loadDataSet()
    dataArr = np.array(dataMat)
    n = np.shape(dataArr)[0] #[0]求dataArr的行数,1,是求列数
    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)   #添加subplot
    ax.scatter(xcord1,ycord1,s = 30,c='red',marker = 's')   #绘制正样本
    ax.scatter(xcord2,ycord2,s = 30, c = 'green')   #绘制负样本
    w = np.array(weight)
    x = np.arange(-3.0,3.0,0.1) #X轴的取值范围
    y = (-w[0] - w[1] * x) / w[2] #设sigmoid =0,因为sigmoid = 0是两种类别的分界线  sigmoid = w0x0+w1x1+w2x2 = 0
    ax.plot(x,y)
    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.show()

#随机梯度上升
def stoGradAscent0(dataMatrix,classLabels):
    dataMatrix=np.array(dataMatrix)
    m,n = np.shape(dataMatrix)
    step = 0.01
    weight = np.ones(n)
    for i in range(m):
        h = sigmoid(sum(dataMatrix[i]*weight))
        error = classLabels[i] - h
        weight = weight +step*error*dataMatrix[i]
    return weight
 
 
#随机梯度算法的改进
def stoGradAscent1(dataMatrix,classLabels,numIter = 150):
    dataMatrix=np.array(dataMatrix)
    m,n = np.shape(dataMatrix)
    weight = np.ones(n)
    for j in range(numIter):
        dataIndex = list(range(m))
        for i in range(m):
            step = 4/(1.0+j+i)+0.01 #降低step的大小,每次减小1/(j+i)。
            randIndex = int(random.uniform(0,len(dataIndex))) #随机选取样本,可以实现随机选取更新
            h = sigmoid(sum(dataMatrix[randIndex]*weight))
            error = classLabels[randIndex] - h
            weight = weight + step*error*dataMatrix[randIndex]
            del(dataIndex[randIndex])
    return weight
 
 
 
 
dataMat,labelMat = loadDataSet()
#weight=gradAscent(dataMat,labelMat)
#weight=stoGradAscent0(dataMat,labelMat)
weight=stoGradAscent1(dataMat,labelMat)
plotBestFit(weight)

 

3.2小结

  Logistic回归的目的是寻找一个非线性函数Sigmoid的最佳拟合参数,求解过程可以由最优化算法来完成。

  而在最优化算法中,最常用的就是梯度上升算法,梯度上升算法又可以简化为随机梯度上升算法。

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

  • 34
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值