机器学习实战--Logistic回归与实例:从疝病症预测病马的死亡率

声明

        本文参考了《机器学习实战》书中代码,结合该书讲解,并加之自己的理解和阐述

机器学习实战系列博文

Logistic回归

        利用Logistic回归进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,以此进行分类。这里的“回归”一词源于最佳拟合,表示要找到最佳拟合参数集。我们想要的函数应该是,能接受所有的输入然后预测出类别。例如,在两个类的情况下,上述函数输出0或1,阶跃函数满足这个要求,只是阶跃函数的跳跃瞬间又是很难处理,幸好,另一个函数也有类似的性质,且数学上更易处理,这就是Sigmoid函数。
                                                                

        当x为0时, Sigmoid函数值为0.5。随着x的增大,对应的Sigmoid值将逼近于1;而随着x的减小, Sigmoid值将逼近于0。如果横坐标刻度足够大,Sigmoid函数看起来很像一个阶跃函数:
                                       

        Sigmoid函数的输入记为Z,其中Z=W’X,W’是W的转置,其中的向量X是分类器的输入数据,向量W也就是我们要找到的最佳参数(系数),从而使得分类器尽可能地精确。寻找最优参数常用的方法是梯度上升法/梯度下降法,他们两种方法本质一样,只是一个求最大值,一个求最小值。

Logistic算法实现

        下边以一个小例子来说明Logistic回归是怎么实现的,以及效果如何

导入数据

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(dataMatIn,classLabels):
    dataMatrix = np.mat(dataMatIn)
    labelMat = np.mat(classLabels).transpose()
    m,n = np.shape(dataMatrix)
    alpha = 0.001
    maxCycles = 500
    weights = np.ones((n,1))
    for k in range(maxCycles):
        h = sigmoid(dataMatrix*weights)
        error = (labelMat-h)
        weights = weights+alpha*dataMatrix.transpose()*error
    return weights

        函数返回经过训练之后参数,下边就要展示一下训练的效果,由于我们这次使用的训练集是二维的,所以可以在一张图片中画出,下边是划分情况:

def plotBestFit(weights):
    dataMat,labelMat = loadDataSet()
    dataArr = np.array(dataMat)
    n = dataArr.shape[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 = np.arange(-3.0,3.0,0.1)
    y = (-weights[0]-weights[1]*x)/weights[2]
    ax.plot(x,y.transpose())
    plt.xlabel('X1');plt.ylabel('X2')
    plt.show()

划分效果 

这个是迭代了500次之后的结果,划分还是很分明的,效果较好,我们还可以将alpha进行调整,不停的改变步长,代码如下:

def stocGradAscent(dataMatrix, classLabels, numIter=150):
    m,n = np.shape(dataMatrix)
    weights = np.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    #apha decreases with iteration, does not
            randIndex = int(random.uniform(0,len(dataIndex)))#go to 0 because of the constant
            h = sigmoid(sum(dataMatrix[randIndex]*weights))
            error = classLabels[randIndex] - h
            weights = weights + alpha * error * dataMatrix[randIndex]
            del(dataIndex[randIndex])
    return weights

效果为:

这个只用了150次迭代也取得了不错的划分结果

实例:从疝病症预测病马的死亡率

背景

        这里将使用Logistic回归来预测患有疝病的马的存活问题。这里的数据包含368个样本和28个特征,疝病是描述马胃肠痛的术语。然而,这种病不一定源自马的胃肠问题,其他问题也可能引发马疝病。该数据集中包含了医院检测马疝病的一些指标,有的指标比较主观,有的指标难以测量,例如马的疼痛级别,另外需要说明的是,除了部分指标主观和难以测量外,该数据还存在一个问题,数据集中有30%的值是缺失的,所以介绍如何处理数据集中的数据缺失问题:

        假设有100个样本和20个特征,这些数据都是机器收集回来的。若机器上的某个传感器损坏导致一个特征无效时该怎么办?此时是否要扔掉整个数据?这种情况下,另外19个特征怎么办?它们是否还可用?答案是肯定的。因为有时候数据相当昂贵,扔掉和重新获取都是不可取的,所以必须采用一些方法来解决这个问题,下面给出了一些可选的做法:

  • 使用可用特征的均值来填补缺失值;
  • 使用特殊值来填补缺失值,如-1;
  • 忽略有缺失值的样本;
  • 使用相似样本的均值添补缺失值;
  • 使用另外的机器学习算法预测缺失值。

数据预处理

        在预处理阶段需要做两件事:第一,所有的缺失值必须用一个实数值来替换,因为我们使用的NumPy数据类型不允许包含缺失值。这里选择实数0来替换所有缺失值,恰好能适用于Logistic回归。这样做的直觉在于,我们需要的是一个在更新时不会影响系数的值。回归系数的更新公式如下:

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

        如果dataMatrix的某特征对应值为0,那么该特征的系数将不做更新,即

        weights = weights

        另外,由于sigmoid(0)=0.5,即它对结果的预测不具有任何倾向性,因此上述做法也不会对误差项造成任何影响

        预处理中做的第二件事是,如果在测试数据集中发现了一条数据的类别标签已经缺失,那么我们的简单做法是将该条数据丢弃。这是因为类别标签与特征不同,很难确定采用某个合适的值来替换。采用Logistic回归进行分类时这种做法是合理的。下边的使用数据是已经预处理过的。

代码实现

def classifyVector(inX,weights):
    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 = stocGradAscent(np.array(trainingSet),trainingLabels,20)
    errorCount = 0.0;numTestVec= 0.0
    print("finished train")
    for line in frTest.readlines():
        numTestVec += 1.0
        # print(numTestVec)
        currLine = line.strip().split('\t')
        # print(currLine)
        lineArr = []
        for i in range(21):
            lineArr.append(float(currLine[i]))
        if(int(classifyVector(np.array(lineArr),trainWeights))!=int(currLine[21])):
            errorCount += 1.0
    errorRate = (float(errorCount)/numTestVec)
    print("the error rate of this test is: %f" % errorRate)
    return errorRate

        分类器判定大于0.5输出1,小于0.5输出0,然后对数据进行训练与检验

训练结果

        the error rate of this test is: 0.343284

        在多次调整参数错误率也很难降到30%以下,事实上,这个结果并不差,因为有30%的数据缺失。逻辑回归的优点是计算快,时间空间开销低,但是由于模型的简单容易产生欠拟合,精度不高。

发布了60 篇原创文章 · 获赞 89 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41685265/article/details/105354503