《机器学习实战笔记--第一部分 分类算法:logistic回归1》

    利用logistic回归进行分类的主要思想是:根据现有的数据对分类边界建立回归公式,以此进行分类。训练分类器时的做法就是寻找最佳拟合参数,使用的是最优化算法。接下来介绍这个二值输出分类器的数学原理。

    

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

    1. 基于logistic回归和sigmoid函数的分类

    我们需要一个函数,接受所有的输入然后预测所有的输出类别。在两个类别的情况下,函数会输出1和0 。

    阶跃函数具有这种性质,但是该函数在从0瞬间跳跃到1的这个瞬间过程很难处理。我们换用另外一个在数学上更容易处理,也具有类似性质的函数,就是sigmoid函数。计算公式如下所示:

                                                                        

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

        

        为了实现logistic回归分类器,我们可以在每个特征上都乘一个回归系数,把所有的结果相加,如果大于0.5,数据将被分入1类,小于0.5将被分入0类。确定分类器后,现在问题就是怎么确定最佳回归系数,如何确定它们的大小?

    

2. 基于最优化方法确定最佳回归系数

  2.1梯度上升法

      思想:要找到某个函数的最大值,最好的方法是沿着该函数的梯度方向探寻。函数的梯度又下式表示:

    

                                                            

    梯度意味要沿着x的方向移动:,沿y的方向移动:。其中f(x,y)必须要在待计算点上有定义并且可微。

    一个具体的函数例子如下:

                       

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

                                                                                    

    该过程一直被迭代,直到到达某个停止条件为止,比如迭代次数到达某个指定值或则某个可以允许的误差。

            

    

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

    梯度上升的伪代码如下:

                

    下面是梯度上升算法的具体实现代码。

from numpy import *

def loadDataSet():
    dataMat = []; labelMat = []
    fr = open('testSet.txt')
    for line in fr.readlines():
        #print(line)
        lineArr = line.strip().split()
        #print(lineArr)
        # 每行前两个值是X1和X2,第三个值是标签,为了方便计算将X0预设为1
        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))

# 梯度计算
# dataMatIn是2维Numpy数组,每列对应不同特征,每行是一个训练样本
# 现在里面有X0,X1,X2的100*3的矩阵
def gradAscent(dataMatIn, classLabels):
    dataMatrix = mat(dataMatIn)             #转化为Numpy矩阵
    labelMat = mat(classLabels).transpose() #由行向量转化为列向量,矩阵的转置
    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)     #矩阵相乘,h是一个列向量 1*100
        error = (labelMat - h)              #vector subtraction
        weights = weights + alpha * dataMatrix.transpose()* error #按差值方向调整回归系数
        # 这里dataMatrix.transpose()* error就是当前对应的梯度
    return weights

    输出的结果为:

    

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

    上面已经求解了一组回归系数,确定了不同的数据之间的分界线,现在画出分界线,让优化过程更易理解。

    画出数据集及最佳拟合直线:

    

#将分类给画出来,画出最佳的拟合直线  
def plotBestFit(weights):  
    """ 
    :param weights: 最佳回归系数数组 
    :return: 
    """  
    dataMat, labelMat = loadDataSet()       #加载数据集,返回数据集合分类  
    dataArr = array(dataMat)        #将数据集数组化  
    dataShape = shape(dataArr)      #data的形状  
    print("data的形状:", dataShape)  
    n = dataShape[0]        #形状的第一个是行数(即数据集的个数),第二个是列数(即数据集的特征)  
    xcord1 = []     #分类为1的点  
    ycord1 = []  
    xcord0 = []     #分类为0的点  
    ycord0 = []  
    for i in range(n):  
        if int(labelMat[i]) == 1:       #如果分类为1,添加到1分类的点集,否者返回到0分类的点集  
            xcord1.append(dataArr[i, 1])        #这个dataArr有3列,其中第0列为1,第1,2列为x,y  
            ycord1.append(dataArr[i, 2])  
        else:  
            xcord0.append(dataArr[i, 1])  
            ycord0.append(dataArr[i, 2])  
    fig = plt.figure()      #figure()函数创建一个新的图  
    ax = fig.add_subplot(111)       #add_subplot()函数在一张figure里面生成多张子图参数111,表示1行1列第1个位置  
    ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')       #散点图  
    ax.scatter(xcord0, ycord0, s=30, c='green')  
    # 直线 x 的范围  
    x = arange(-3.0, 3.0, 0.1)      # (start, end, step)可以调step执行起来,看看图  
    # 画出直线,weights[0]*1.0+weights[1]*x+weights[2]*y=0  
    # 之前计算时对原始数据做了拓展,将两维拓展为三维,第一维全部设置为1.0,实际他是一个 y=ax+b, b常量  
    y = (-weights[0]-weights[1]*x)/weights[2]  
    ax.plot(x, y)       #画出直线  
    plt.xlabel('x1')        #X轴标记为x1  
    plt.ylabel('x0')        #Y轴标记为x0  
    plt.show()  
  
plotBestFit(weights.getA())  #getA()将weights矩阵转换为数组,getA()函数与mat()函数的功能相反  
""" 
如果是矩阵的话会报这样的错: 
"have shapes {} and {}".format(x.shape, y.shape)) 
ValueError: x and y must have same first dimension, but have shapes (60,) and (1, 60) 
为啥要用数组呢?因为 x = arange(-3.0, 3.0, 0.1),len(x) = [3-(-3)]/0.1 = 60 
而weights是矩阵的话,y = (-weights[0]-weights[1]*x)/weights[2],len(y) = 1,有60个x,y只有一个,你这样都画不了线 
而weights是数据的话,len(y) = 60 
"""  

    输出的结果:

    

        从图上看,分类效果相当不错。只分错了4个点。尽管数据集很小,但是却需要计算300次乘法。下面我们将对算法进行改进,从而使他们可以再真实数据集上使用。

    

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

    梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理小数量的数据集时还可以,但是在大数据特征计算时改方法的复杂度就太高了。一种改进方法就是一次仅利用一个样本点来更新回归系数,该方法称为随机梯度上升算法。由于可以再新样本到来时对分类器进行增量式更新,因而称该算法是一个在 线学习算法。与在线学习相对应的就是一次性处理所有数据的 批处理 。

      

    以下是随机梯度算法的实现代码:

def stocGradAscent0(dataMatrix, classLabels):
    m,n = shape(dataMatrix)
    alpha = 0.01
    weights = ones(n)   #initialize to all ones
    for i in range(m):
        # h,error全是数值,没有矩阵转换过程。
        h = sigmoid(sum(dataMatrix[i]*weights))
        error = classLabels[i] - h
        weights = weights + alpha * error * dataMatrix[i]
    return weights

    画图:


    拟合的不如上图的那样完美,这里大概错分了三分之一的样本。但是直接这样比较是不公平的,梯度上升法是在整个数据集上迭代了500次才得到的结果。我们判断一个算法优劣是看他是否收敛,还是会不断的变化?

    下面我们修改随机梯度方法,使其在数据集上运行200次。最终绘制的三个回归系数变化如图:


    上图展示了随机梯度上升算法在200次迭代过程中回归系数的变化情况。其中X2,也就是系数2,只经过了50次迭代就达到了稳定值,但是系数1和0则需要多次迭代。另外值得注意的是,在大的波动停止后还有一些小的周期性波动。产生这种情况的原因是存在一些不能正确分类的样本点,在每次迭代后回引发系数的剧烈改变,我们希望算法能够避免来回波动,尽快的收敛到某个具体的值。另外收敛速度也需要加快。

    针对上图的问题,可以通过修改程序来解决问题,具体代码如下:

    

def stocGradAscent1(dataMatrix, classLabels, numIter=150):
    m,n = shape(dataMatrix)
    weights = ones(n)   #initialize to all ones
    for j in range(numIter):
        dataIndex = list(range(m))
        for i in range(m):
            alpha = 4/(1.0+j+i)+0.0001  # alpha在每次迭代时都会调整  
            randIndex = int(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

    

        比较上面两个图,第一点图5-7中的系数没有像图5-6那样出现周期性波动,这要归功于stoGradAscent1()中的样本随机选择机制,第二点是,图5-7比图5-6的横坐标要短很多,这是由于更新过的函数可以收敛的更快,我们仅对数据集进行了20次遍历,而之前进行了500次。

    观察这次的分类结果:

    

    可以看到,分隔效果差不多但是计算的量更少。

    默认迭代次数是150,可以自行修改。

    

猜你喜欢

转载自blog.csdn.net/qq_41635352/article/details/80625370