机器学习实战(二)logistic回归

一、logistic回归

1.逻辑回归假设函数

  • 逻辑回归一般用于分类问题较多,但是叫做“regression”,而线性回归一般不建议用于分类,因为输出的y的值可能超出0/1范围。这也就是为什么逻辑回归假设函数里面有sigmoid函数的原因了。

2.成本函数

  • 逻辑回归问题不在采用“最小均方”误差,因为里面含有非线性的sigmiod函数,也就是 h θ ( x ) h_θ(x) hθ(x),使得成本函数 J ( θ ) J(θ) J(θ)不再是一个平滑的“碗”,术语叫做非凸函数,(如左图)。可以看到,如果将梯度下降法用在这样的函数上,不能保证收敛到全局最小值,容易导致“局部最优”。所以我们希望,我们的代价函数 J ( θ ) J(θ) J(θ)是一个凸函数,也就是(右图)。

在这里插入图片描述

所以采用如下cost function:
在这里插入图片描述
最终写成:这个式子是从统计学中极大似然法得来的,最好的性质就是,他是凸函数。

3.参数学习(梯度下降)

你会发现,这个跟线性回归模型的梯度下降表达上一模一样,但是,你要知道,其中的h(x)是不一样的!

二、Logistic回归的一般过程

  • 1.收集数据:采用任意方法收集
  • 2.准备数据:由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳
  • 3.分析数据:采用任意方法对数据进行分析
  • 4.训练算法:大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数
  • 5.测试算法:一旦训练步骤完成,分类将会很快。
  • 6.使用算法:首 先,我们需要输入一些数据,并将其转换成对应的结构化数值;接着,基于训练好的回归系数就可以对这些数值进行简单回归计算,判定它们属于哪个类别;在这之后,我们就可以在输出的类别上做一些其他分析工作。

三、 基于Logistic回归和Sigmoid函数的分类

1.logistic回归的优缺点

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

2.Sigmoid函数

  • 当x为0时,函数值为0.5。随着x的增大,对应的函数值将逼近于1;而随着x的减小,函数值将逼近于0。如果横坐标刻度足够大,Sigmoid函数看起来很像一个阶跃函数。
  • 为了实现Logistic回归分类器,我们使用Sigmoid函数。我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,将这个总和代入Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分入1类,小于0.5的即被归入0类。

优点:

  • (1).Sigmoid函数的输出映射在(0,1)之间,单调连续,输出范围有限,优化稳定,可以用作输出层。
  • (2).求导容易。

缺点:

  • (1).由于其软饱和性,容易产生梯度消失,导致训练出现问题。
  • (2).其输出并不是以0为中心的。

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

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

 

2.梯度上升法的迭代公式

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

有100个样本点,每个点包含两个数值型特征:X1和X2。在此数据庥上,将通过使用梯度上升法找到最佳回归系数,也就是拟合出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])])  #将每一行的两个特征x1,x2,加上x0 = 1,组成列表并添加到数据集列表中
        labelMat.append(int(lineArr[2]))  # 将当前的行标签添加到标签列表,数据的类别号列表
    return dataMat, labelMat  # 返回数据列表,标签列表
# 定义sigmoid函数
def sigmoid(inX):
    return 1.0/(1 + exp(-inX))
# Logistic 回归梯度上升优化算法
# @dataMatIn:数据集
# @classLabels:数据标签
def gradAscent(dataMatIn, classLabels):
    dataMatrix = mat(dataMatIn)  # 将数据集列表转化成numpy矩阵
    labelMat = mat(classLabels).transpose()  # 将数据集标签列表转化成矩阵并转置成列向量
    m,n = shape(dataMatIn)  # 获得数据集矩阵dataMatrix的行、列数
    alpha = 0.001  # 向目标移动的步长,学习步长
    maxCycles = 500  # 最大迭代次数
    weights = ones((n,1))  # 生成n行1列的矩阵且值为1
    # print(weights)
    for k in range(maxCycles):  # 循环迭代次数
        h = sigmoid(dataMatrix * weights)  # dataMatrix * weights 是m * 1的矩阵,其每一个元素都会调用sigmoid()函数,h也是一个m  * 1的矩阵,求当前sigmoid函数的预测概率
        # 此处计算真实类别和预测类别的差值
        # 对logistic回归函数的对数释然函数的参数项求偏导
        error = (labelMat - h)
        # 更新权*值参数
        weights = weights + alpha * dataMatrix.transpose() * error  # 每步weights该变量
    return weights

注:肯定有读者和我一样,一开始不理解倒数第二行weights为什么会那样赋值更新?不是求梯度嘛?最后怎么梯度怎么变成 dataMatrix.transpose() error* 其实呢这里有一个数学推导,可以自己查一下(涉及最大似然估计估计模型参数),这里博主给出一个参考博客可以查看下。
https://blog.csdn.net/c406495762/article/details/77723333

4.分析数据:画出决策边界

def plotBestFit(weights):
    dataMat, labelMat = loadDataSet()
    dataArr = array(dataMat)
    n = shape(dataArr)[0] # 数据的行数,即对象的个数
    xcord1 = []; ycord1 = [] # 对类别号为 1 的对象,分 X 轴和 Y 轴的数据
    xcord2 = []; ycord2 = [] # 对类别号为 0 的对象,分 X 轴和 Y 轴的数据
    for i in range(n): # 对所有的对象进行遍历
        if int(labelMat[i]) == 1:  # 对象的类别为: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 = '*') # 对散点的格式的设置,坐标号、点的大小、颜色、点的图形(方块)
    ax.scatter(xcord2, ycord2, s = 30, c = 'green', marker = '*') # 点的图形默认为圆
    x = arange(-3.0, 3.0, 0.1)
    y = (-weights[0]-weights[1]*x) / weights[2] # 线性方程 y = aX + b,y 是数据第三列的特征,X 是数据第二列的特征
    ax.plot(x, y)
    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.show()
**5.训练算法随机梯度上升**
  • 梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理100个左右的数据集时尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。

在这里插入图片描述

def stocGradAscent0(dataMatrix, classLabels):
    m, n = shape(dataMatrix)  # 获取数据集的行数和列数
    alpha = 0.01  # 设置步长为0.01
    weights = ones(n)# 初始化权值向量各个参数为1.0
    # print(weights)
    for i in range(m):  # 循环m次,每次选取数据集一个样本更新参数
        h = sigmoid(sum(dataMatrix[i] * weights))  # 计算当前样本的sigmoid函数值
        error = classLabels[i] - h  # 计算当前样本的残差(代替梯度)
        weights = weights + alpha * error * dataMatrix[i]  # 更新权值参数
    return weights

与梯度下降算法的区别:

  • 第一,后者的变量h和误差error都是向量,而前者全部是数值;
  • 第二,前者没有矩阵的转换过程,所有变量的数据类型都是NumPy数组。

验证一下效果:
  

可视化:

从结果上来看,梯度上升分错4个,随机梯度上升分错了1/3,该方法不如直接使用梯度上升法那样完美。

直接这样比较是不公平的,我们知道在梯度上升法中迭代了500次。

一个判断优化算法优劣的可靠方法是看它是否收敛,也就是说参数是否达到了稳定值,是否还会不断的变化?通过分析,我们使用改进的随机梯度上升算法

6.改进的随机梯度上升算法

def stocGradAscent1(dataMatrix, classLabels, numInter = 150):
    # 将数据集列表转化为numpy数组
    # dataMat = array(dataMatrix)
    m, n = shape(dataMatrix)  # 获取数据集的行数和列数
    weights = ones(n)  # 初始化权值参数向量每个维度均为1
    for j in range(numInter):  # 迭代次数
        dataIndex =list( range(m))  # 获取数据集行下表列表
        for i in range(m):  # 对所有对象的遍历
            alpha = 4 / (1.0 + j + i) + 0.01  # 对步长的调整,添加了固定步长0.01
            randIndex = int(random.uniform(0, len(dataIndex)))  # 随机生成一个整数,介于0到m
            h = sigmoid(sum(dataMatrix[randIndex] * weights))  # 对随机选择的对象计算类别的数值(回归系数值)
            error = classLabels[randIndex] - h  # 根据实际类型与计算类型值的误差,损失函数
            weights = weights + alpha * error * dataMatrix[randIndex]  # 每步weights的改变值,权值更新
            del(dataIndex[randIndex])  # 去除已经选择过的对象,避免下次选中
    return weights

该程序在原有基础上做了三点优化:

  • 1.alpha每次迭代的时候都会调整,4/(1+j+i)+0.01,使得alpha随着迭代次数不断减小,但永远不会减小到0。这样做的原因是为了保证在多次迭代之后新数据仍然具有一定的影响。
  • 2.随机选取样本来更新回归系数,这种做法会减少周期性波动
  • 3.增加了迭代参数的设置。默认为150次,如果给定,将会按照新的参数值进行迭代。

看看效果:
  

注:默认迭代次数150次,效果接近于梯度上升,但计算量更少。

五、示例:从疝气病症预测病马的死亡率

1.使用logistic回归估计马疝病的死亡率

  • 收集数据:给定数据文件
  • 准备数据:解析文本并填充缺失值
  • 分析数据:可视化并观察数据
  • 训练算法:使用优化算法,找到最佳的系数
  • 测试算法:为了量化回归的效果,需要观察错误率。根据错误率决定是否回退到训练阶段,通过改变迭代的次数和步长等参数来得到更好的回归系数。
  • 使用算法:

注意:数据集中有30%的值是缺失的。

2.准备数据:处理数据中的缺失值

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

Numpy数据类型不允许包含缺失值,选择用0来替换缺失值,这样恰好能适用于Logistic回归,这样做在于,有值时更新回归系数,如果为0(即缺失值),不更新回归系数。

def classifyVector(inX, weights):
    prob = sigmoid(sum(inX * weights))  # 计算logistic回归系数
    if prob > 0.5:  # 大于0.5预测为1
        return 1.0
    else:  # 否则为0.0
        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]))  # 将该样本的特征存入lineArr列表
        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  # 样本总数加1
        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  # 如果预测错误,错误数加1
    errorRate = (float(errorCount) / numTestVec)  # 计算测试集总的预测错误率
    print ("the error rate of this test is : %f" % errorRate)  # 打印错误率大小
    return errorRate  # 返回错误率

#多次测试算法求取预测误差平均值
def multiTest():
    numTests = 10;errorSum = 0.0  # 设置测试次数为10次,并统计错误率总和
    for k in range(numTests):  # 每一次测试算法并统计错误率
        errorSum += colicTest()
    print("after %d iterations the average error rate is : %f" % (numTests,errorSum / float(numTests)))  # 打印出测试10次预测错误率平均值

小结

对于逻辑回归而言,其实际就是一个函数套上一个回归模型,对于输入有输出,所以只需要对其中的参数进行估计即可,对于参数估计问题,涉及到代价函数,而逻辑回归的代价函数可以用最大似然估计得到。因为从概率的角度来讲,对于已知的样本,通常认为在某一参数下取到这些样本的概率一定是比较大的,所以可以通过挑选参数集合中最大的参数取值来使得估计概率最大。最后可以采用梯度下降法,为了提高效率也可以使用随机梯度下降来求参数,因为这里是求最大值,只需要将梯度方向符号改为+即可。

参考:
《机器学习实战》
https://www.cnblogs.com/MarsMercury/p/5189563.html

https://blog.csdn.net/chinagreenwall/article/details/81113539

https://www.jianshu.com/p/0cfabca442d9

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值