机器学习实战5--Logistic回归

前面的分类都是基于标签是离散值进行的,这里回归是针对标签是连续值进行的。
假设现在有一些数据点,我们用一条直线对这些点进行拟合,这个拟合过程就是回归,该线就是最佳拟合直线。主要思想:根据现有数据对分类边界线建立回归公式,以此进行分类。回归就是最佳拟合,找到最佳拟合参数集,训练分类器的做法就是寻找最佳拟合参数,使用的是最优化算法。
找到分类回归系数就可以了。
1:基于logistic回归和Sigmoid函数的分类
基于Logistic回归的函数应能接受所有的输入然后预测出类别。sigmoid函数类似於单位阶跃函数,
这里写图片描述
当x=0时,函数值为0.5,x大于0,逐渐逼近于1;x小于0,逐渐逼近于0。
为了实现sigmoid函回归分类器,在每个特征上都乘以一个回归系数,然后所有的结果值相加,将这个总和代入sigmoid函数中,进而得到一个范围在0-1之间的数值,任何大于0.5的数据被归入1类,小于0.5的被归入0类。Logistic回归也可以被看作是一种概率估计。
这里写图片描述
g(z)的曲线是
这里写图片描述

此时就可以对标签y进行分类了,
这里写图片描述
其中θTx=0 即θ0+θ1*x1+θ2*x2=0 称为决策边界即boundarydecision。

2:线性回归的cost function是依据最小二乘法是最小化观察值和估计值之间的差平方和。
这里写图片描述
但是对于Logistic回归,我们的cost function不能最小化观察值和估计值之间的差平方和,J(θ)为非凸函数,这样就会存在很多局部极值点,无法通过梯度迭代得到最终的参数,重新定义一种cost function:
这里写图片描述
这个函数的意思是当y=1时,估计值为1;当y=0时,估计值为0;这样就是预测准确了,但是若预测错误,就估计值会无穷大。
将上面的函数写在一起:
这里写图片描述
这样总体损失函数就是:
这里写图片描述

3:sigmoid函数的输入记为z,由下面公式得出:
z=w0x0+w1x1+w2x2+w3x3+…+wnxn,
写成向量的形式:z=wTx;向量x是分类器的输入数据,向量w是要找到的最佳参数,从而使得分类器尽可能精确。
梯度最优化方法:
要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。f(x,y)的梯度由下式表示:
这里写图片描述
求和后得到:
这里写图片描述
对于随机化的梯度迭代每次只使用一个样本进行参数更新,就为:
这里写图片描述
下面的代码就是基于这个公式的。
我们知道hθ(x)≥0.5<后面简用h>,此时y=1, 小于0.5,y=0. 那么我们就用h作为y=1发生的概率,那么当y=0时,h<0.5,此时不能用h作为y=0的概率,<因为最大似然的思想使已有的数据发生的概率最大化,小于0.5太小了>,我们可以用1-h作为y=0的概率,这样就可以作为y=0的概率了,,然后只需要最大化联合概率密度函数就可以了。
这样联合概率密度函数就可以写成:
这里写图片描述
再转换成对数似然函数,就和上面给出的似然函数一致了。
这里写图片描述

4:梯度上升算法沿梯度方向移动,梯度算子总是指向函数值增长最快的方向。梯度上升优化算法的实现:

#-*- coding:utf-8 -*-
from numpy import *
#计算代价不高,易于理解和实现,容易欠拟合,分类精度不高,数据类型为数值型和标称型数据
def loadDataSet():
    dataMat = []; labelMat = []
    fr = open('testSet.txt')#打开文本
    for line in fr.readlines():#逐行读取,并保存成矩阵,x0为1
        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)#转换成numpy矩阵数据类型
    labelMat = mat(classLabels).transpose()#把标签转换成行向量
    m,n = shape(dataMatrix)#m是行,n是列
    alpha = 0.001#向目标移动的步长
    maxCycles = 500#迭代次数
    weights = ones((n,1))#初始化矩阵向量,n行一列的单位矩阵
    for k in range(maxCycles):#每一次迭代
        h = sigmoid(dataMatrix*weights)#把z=wx带入函数,求出一个列向量,代表计算出的标签
        error = (labelMat - h)#与实际的标签进行比较,得误差
        weights = weights + alpha * dataMatrix.transpose() * error#回归系数为原来的回归系数加上步长乘以数据集行列变换后乘以误差列向量,得w1,w2,w3三个回归系数
    return weights

这里写图片描述
给出一组数据,就可以得到回归系数。实现Logistic回归。
5:确定了回归系数,得到不同类别数据之间的分隔线。画出该分隔线。

def plotBestFit(weights):#画出回归线
    import matplotlib.pyplot as plt#
    dataMat,labelMat = loadDataSet()#导入数据集
    dataArr = array(dataMat)#转换成array阵列
    n = shape(dataArr)[0]#得到行数
    xcord1 = []; ycord1 = []#设置数据点的x,y座标
    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)#x的取值范围和步长间隔
    y = (-weights[0]-weights[1]*x)/weights[2]#针对x和回归系数的y的取值
    ax.plot(x,y)
    plt.xlabel('X1');plt.ylabel('X2');
    plt.show()

这里写图片描述
这里写图片描述

这个方法错误率较小,但计算量却很大,需要300次乘法。
6:随机梯度上升:梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法计算量太大了。改进为随机梯度上升法:
一次仅用一个样本点来更新回归系数,在新样本到来时对分类器进行增量式更新,实现一个在线学习算法,不是一次处理所有数据的批处理。

#随机梯度上升算法,区别是这个的h和error是数值,而之前的是向量,这个的数据类型是numpy数组,没有矩阵的转换过程,可以在新数据到来时就完成参数更新,不需要读取整个数据集进行批处理,占用更少的资源,是在线算法
def stocGradAscent0(dataMatrix,classLabels):
    m,n = shape(dataMatrix)#得到数据集的行列数量
    alpha = 0.01#步长
    weights = ones(n)#单位行向量,行数=数据集列数
    for j in range(200):#200次迭代
        for i in range(m):#对每一行
            h = sigmoid(sum(dataMatrix[i]*weights))#求出这一行向量乘以回归系数,得到一个数值,带入函数中
            error = classLabels[i] - h#分别与每行的标签相比较,求误差
            weights = weights + alpha*error*dataMatrix[i]#回归系数等于原来的加上步长乘以误差乘以一行向量。
    return weights

这里写图片描述
这里写图片描述
一个判断优化算法的优劣的可靠方法是看它是否收敛,也就是说参数是否达到了稳定值,是否还会不断变化。

7:随机上升算法在参数大的波动停止后,还有一些小的周期性波动。产生这种现象的原因存在一些不能正确分类的样本点(数据集并非线性可分),在每次迭代时会引发系数的剧烈改变。我们期望算法能避免来回波动,从而收敛于某个值,另外收敛速度也要加快。

def stocGradAscent1(dataMatrix,classLabels,numIter=150):#第三个参数是迭代次数,默认是150次,用了两种机制,步长动态减少和样本随机选择,将会收敛很快,没有高频波动。
    m,n = shape(dataMatrix)
    weights = ones(n)
    for j in range(numIter):    
        dataIndex = range(m)
        for i in range(m):
            alpha = 4/(1.0+j+i)+0.01#这个步长在每次迭代的时候都会调整减小,但因为有常数项,则不会减小到0,保证在多次迭代后新数据仍有影响,学习下模拟退火算法
            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

模拟退火算法避免参数的严格下降。
随机采集样本来更新回归参数,减少周期性波动,每次随机从列表中选出一个值,然后从列表中删除该值(再进行下一次迭代)。
这里写图片描述
这里写图片描述

8:实例:从疝气病症预测病马的死亡率。
在给定的数据中,用python解析文本文件并填充缺失值,因为有些数据是缺失的,比如一个向量里没有全部的数据,某个传感器损坏导致一个特征无效等等:
使用可用特征的均值来填补缺失值;
使用特殊值来填补缺失值如-1;
忽略有缺失的样本;
使用相似样本的均值填补缺失值;
使用另外的机器学习算法预测缺失值。
回归系数的更新公式:
weights = weights + error * dataMatrix[randIndex],
如果dataMatrix的某特征值为0,那么该特征的系数将不做更新。
测试算法:把测试集上每个特征向量乘以最优化方法得来的回归系数,再将该乘积结果求和,最后输入到sigmoid函数中即可。如果对应的Sigmoid值大于0.5就预测类别标签为1,否则为0。

def classifyVector(inX,weights):#分类函数,给出20个特征和一个标签,包括回归系数,将会得到分类值
    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)
        trainingLabels.append(float(currLine[21]))
    trainWeights = stocGradAscent1(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(array(lineArr),trainWeights)) != int(currLine[21]):
            errorCount += 1
    errorRate = (float(errorCount)/numTestVec)
    print "the error rate of this test is: %f" %errorRate
    return errorRate
#分类时不需做太多工作,把测试集的特征向量乘以最优化方法得来的回归系数,求和,输入到函数中大于0.5就把标签设为1
def multiTest():#调用10次colicTest并求结果的平均值
    numTests = 10;errorSum = 0.0
    for k in range(numTests):
        errorSum += colicTest()
    print "after %d iterrations the average error rate is:%f" %(numTests,errorSum/float(numTests))

这里写图片描述

9:总结:Logistic回归的目的是寻找一个非线性函数Sigmoid的最佳拟合参数,求解过程可以由最优化算法来完成。在最优化算法中,最常用的就是梯度上升算法,可以简化为随机梯度上升算法,性能相当,但占用更少的计算资源。此外随机梯度上升是一个在线算法,它可以在新数据到来时就完成参数更新,而不需要重新读取整个数据集来进行批处理运算。
优点:计算代价不高,易于理解和实现。
缺点:容易欠拟合,分类精度可能不高。
适用数据类型:数值型和标称型数据。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章