logistic回归预测病马死亡率

Logistic 回归 概述

Logistic 回归虽然名字叫回归,但是它是用来做分类的。其主要思想是: 根据现有数据对分类边界线建立回归公式,以此进行分类。

基础概念

Sigmoid 函数

回归 概念

假设现在有一些数据点,我们用一条直线对这些点进行拟合(这条直线称为最佳拟合直线),这个拟合的过程就叫做回归。进而可以得到对这些点的拟合直线方程,那么我们根据这个回归方程,怎么进行分类呢?请看下面。

二值型输出分类函数

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

Sigmoid 函数计算公式

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

Sigmoid 函数在不同坐标下的图片

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

基于最优化方法的回归系数确定

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

Sigmoid 函数计算公式

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

梯度上升法

梯度的介绍

需要一点点向量方面的数学基础

向量 = 值 + 方向  
梯度 = 向量
梯度 = 梯度值 + 梯度方向

梯度上升法的思想

要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为 ▽ ,则函数 f(x, y) 的梯度由下式表示:

梯度上升计算公式

这个梯度意味着要沿 x 的方向移动 f(x, y)对x求偏导 ,沿 y 的方向移动 f(x, y)对y求偏导 。其中,函数f(x, y) 必须要在待计算的点上有定义并且可微。下图是一个具体的例子。

梯度上升

上图展示的,梯度上升算法到达每个点后都会重新估计移动的方向。从 P0 开始,计算完该点的梯度,函数就根据梯度移动到下一点 P1。在 P1 点,梯度再次被重新计算,并沿着新的梯度方向移动到 P2 。如此循环迭代,直到满足停止条件。迭代过程中,梯度算子总是保证我们能选取到最佳的移动方向。

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

梯度上升迭代公式

该公式将一直被迭代执行,直至达到某个停止条件为止,比如迭代次数达到某个指定值或者算法达到某个可以允许的误差范围。

介绍一下几个相关的概念:

例如:y = w1x1 + w2x2 + ... + wnxn
梯度:参考上图的例子,二维图像,x方向代表第一个系数,也就是 w1,y方向代表第二个系数也就是 w2,这样的向量就是梯度。
α:上面的梯度算法的迭代公式中的阿尔法,这个代表的是移动步长。移动步长会影响最终结果的拟合程度,最好的方法就是随着迭代次数更改移动步长。
步长通俗的理解,100米,如果我一步走10米,我需要走10步;如果一步走20米,我只需要走5步。这里的一步走多少米就是步长的意思。
▽f(w):代表沿着梯度变化的方向。

Note: 我们常听到的是梯度下降算法,它与这里的梯度上升算法是一样的,只是公式中的加法需要变成减法。因此,对应的公式可以写成

梯度下降迭代公式

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

局部最优现象

梯度下降图_4

上图表示参数 θ 与误差函数 J(θ) 的关系图,红色的部分是表示 J(θ) 有着比较高的取值,我们需要的是,能够让 J(θ) 的值尽量的低。也就是深蓝色的部分。θ0,θ1 表示 θ 向量的两个维度。

可能梯度下降的最终点并非是全局最小点,可能是一个局部最小点,如我们上图中的右边的梯度下降曲线,描述的是最终到达一个局部最小点,这是我们重新选择了一个初始点得到的。

看来我们这个算法将会在很大的程度上被初始点的选择影响而陷入局部最小点。

Logistic 回归 原理

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

所有回归系数初始化为 1
重复 R 次:
    计算整个数据集的梯度
    使用 步长 x 梯度 更新回归系数的向量
返回回归系数

训练算法:随机梯度上升 找到最佳参数

所有回归系数初始化为 1
对数据集中每个样本
  计算该样本的梯度
  使用 alpha x gradient 更新回归系数值
返回回归系数值

梯度上升法每次更新回归系数都需要便利整个数据集,计算复杂度高,改进的方法一次只用一个样本更新回归系数

训练算法:改进的随机梯度上升 找到最佳参数

改进的地方有两处
一、alpha在每次迭代是都会调整,alpha随着每次迭代都会减小,但永远不会减小到0,保证多次迭代之后新数据仍然有一定影响力
二、随机选取样本更新回归系数

Logistic 回归 算法特点

优点: 计算代价不高,易于理解和实现。
缺点: 容易欠拟合,分类精度可能不高。
适用数据类型: 数值型和标称型数据。

 为什么会是 alpha * dataMatrix.transpose() * error ?因为这就是我们所求的梯度,也就是对 f(w) 对 w 求一阶导数。具体推导如下:

 

python3已经实现 代码要补充注释

# -*- coding: utf-8 -*-
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
#定义sigmoid函数
def sigmoid(inX):
    return 1.0/(1+exp(-inX))
#梯度上升法
def gradAscent(dataMatIn, classLabels):
    dataMatrix = mat(dataMatIn)             #转换为NumPy矩阵类型
    labelMat = mat(classLabels).transpose() #转换为NumPy矩阵类型,并求转置
    m,n = shape(dataMatrix)
    alpha = 0.001#步长
    maxCycles = 500#迭代次数
    weights = ones((n,1))#初始回归系数
    for k in range(maxCycles):              #循环500次
        h = sigmoid(dataMatrix*weights)     #向量相乘 h也是向量
        error = (labelMat - h)              #向量相减
        weights = weights + alpha * dataMatrix.transpose()* error #调整回归系数
    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()
#随机梯度上升法
def stocGradAscent0(dataMatrix, classLabels):
    m,n = shape(dataMatrix)
    alpha = 0.01       #步长
    weights = ones(n)  #出初始回归系数
    for i in range(m):
        h = sigmoid(sum(dataMatrix[i]*weights))
        error = (classLabels[i] - h)
        weights = weights + alpha * error * dataMatrix[i]
    return weights
#改进的随机梯度上升法
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
    m,n = shape(dataMatrix)
    weights = ones(n)   #初始回归系数
    for j in range(numIter):
        dataIndex = list(range(m))#此处注意Python2和python3的区别
        for i in range(m):
            alpha = 4/(1.0+j+i)+0.0001    #动态调整步长
            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
#***********************完成具体的分析任务**************************#
#输出分类结果
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 = stocGradAscent1(array(trainingSet), trainingLabels, 1000)
    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
#多次测试的函数
def multiTest():
    numTests=10;
    errorSum=0;
    for k in range(numTests):
        errorSum+=colicTest()
    print ('after %d iterations the average error rate is: %f ' %(numTests,errorSum/float(numTests)))

 结果如下:

the error rate of this test is: 0.313433
the error rate of this test is: 0.358209
the error rate of this test is: 0.358209
the error rate of this test is: 0.402985
the error rate of this test is: 0.253731
the error rate of this test is: 0.417910
the error rate of this test is: 0.283582
the error rate of this test is: 0.283582
the error rate of this test is: 0.328358
the error rate of this test is: 0.417910
after 10 iterations the average error rate is: 0.341791 

考虑到数据缺失问题,这个结果并不差

 

注意几个方法和概念

分类函数,梯度上升法,随机梯度上升法,改进的随机梯度上升法

posted on 2018-05-03 20:11  Aaron12  阅读(881)  评论(0编辑  收藏  举报

导航