导读:逻辑回归(Logistic regression)即逻辑模型,属于常见的一种分类算法。本文将从理论介绍开始,搞清楚什么是逻辑回归、回归系数、算法思想、工做原理及其优缺点等。进一步经过两个实际案例深化理解逻辑回归,以及在工程应用进行实现。(本文原创,转载必须注明出处: 一步步教你轻松学逻辑回归模型算法)php
逻辑回归html
回归:假设如今有一些数据点,咱们用一条直线对这些点进行拟合(这条直线称为最佳拟合直线),这个拟合的过程就叫作回归。git
逻辑回归(Logistic Regression)是一种用于解决二分类(0 or 1)问题的机器学习方法,用于估计某种事物的可能性。好比某用户购买某商品的可能性,某病人患有某种疾病的可能性,以及某广告被用户点击的可能性等。 注意,这里用的是“可能性”,而非数学上的“几率”,logisitc回归的结果并不是数学定义中的几率值,不能够直接当作几率值来用。该结果每每用于和其余特征值加权求和,而非直接相乘。github
Sigmoid 函数算法
Sigmoid函数是一个常见的S型数学函数,在信息科学中,因为其单增以及反函数单增等性质,Sigmoid函数常被用做神经网络的阈值函数,将变量映射到0,1之间。在逻辑回归、人工神经网络中有着普遍的应用。Sigmoid函数的数学形式是:apache
对x求导能够推出以下结论:数组
下图给出了 Sigmoid 函数在不一样坐标尺度下的两条曲线图。当 x 为 0 时,Sigmoid 函数值为 0.5 。随着 x 的增大,对应的 Sigmoid 值将逼近于 1 ; 而随着 x 的减少, Sigmoid 值将逼近于 0 。若是横坐标刻度足够大, Sigmoid 函数看起来很像一个阶跃函数。网络
所以,为了实现 Logistic 回归分类器,咱们能够在每一个特征上都乘以一个回归系数,而后把全部结果值相加,将这个总和代入 Sigmoid 函数中,进而获得一个范围在 0~1 之间的数值。任何大于 0.5 的数据被分入 1 类,小于 0.5 即被纳入 0 类。因此,Logistic 回归也是一种几率估计,好比这里Sigmoid 函数得出的值为0.5,能够理解为给定数据和参数,数据被分入 1 类的几率为0.5。(注意:针对二分类问题,0.5不是惟一肯定分类的值,你能够根据需求调整这个几率值。)app
逻辑回归与线性回归的关系dom
逻辑回归(Logistic Regression)与线性回归(Linear Regression)都是一种广义线性模型(generalized linear model)。逻辑回归假设因变量 y 服从伯努利分布,而线性回归假设因变量 y 服从高斯分布。 所以与线性回归有不少相同之处,去除Sigmoid映射函数的话,逻辑回归算法就是一个线性回归。能够说,逻辑回归是以线性回归为理论支持的,可是逻辑回归经过Sigmoid函数引入了非线性因素,所以能够轻松处理0/1分类问题。
Sigmoid 函数的输入记为 z ,由下面公式获得:
若是采用向量的写法,上述公式能够写成 Sigmoid 函数计算公式向量形式 ,它表示将这两个数值向量对应元素相乘而后所有加起来即获得 z 值。其中的向量 x 是分类器的输入数据,向量 w 也就是咱们要找到的最佳参数(系数),从而使得分类器尽量地精确。为了寻找该最佳参数,须要用到最优化理论的一些知识。咱们这里使用的是——梯度上升法(Gradient Ascent)。
梯度
对于梯度这个词一些人比较陌生,咱们先看看维基百科的解释:在向量微积分中,标量场(向量场)中某一点的梯度指向在这点标量场增加最快的方向(固然要比较的话必须固定方向的长度),梯度的绝对值是长度为1的方向中函数最大的增长率,也就是说,其中
表明方向导数。
梯度形式化描述
考虑一座高度在 (x, y)点是 H(x, y)的山。H这一点的梯度是在该点坡度(或者说斜度)最陡的方向。梯度的大小告诉咱们坡度到底有多陡。这个现象能够以下数学的表示。山的高度函数 H的梯度点积一个单位向量给出了表面在该向量的方向上的斜率。这称为方向导数。
理解梯度
为了你们更容易理解什么是梯度,咱们介意向量的概念,向量是一个矢量具备大小和方向的。一样,梯度也能够类比为具有大小和方向的这么一个概念。其二者比较以下:(这里严格意义上讲是不成立的,便于你们理解。)
向量 = 值 + 方向 梯度 = 向量 梯度 = 梯度值 + 梯度方向
梯度上升
要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。若是梯度记为 ▽ ,则函数 f(x, y) 的梯度由下式表示:
这个梯度意味着要沿 x 的方向移动 ,沿 y 的方向移动
。其中,函数f(x, y) 必需要在待计算的点上有定义而且可微。下图是一个具体的例子。
上图展现的,梯度上升算法到达每一个点后都会从新估计移动的方向。从 P0 开始,计算完该点的梯度,函数就根据梯度移动到下一点 P1。在 P1 点,梯度再次被从新计算,并沿着新的梯度方向移动到 P2 。如此循环迭代,直到知足中止条件。迭代过程当中,梯度算子老是保证咱们能选取到最佳的移动方向。
上图中的梯度上升算法沿梯度方向移动了一步。能够看到,梯度算子老是指向函数值增加最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记做 α 。用向量来表示的话,梯度上升算法的迭代公式以下:
例如:y = w0 + w1x1 + w2x2 + ... + wnxn 梯度:参考上图的例子,二维图像,x方向表明第一个系数,也就是 w1,y方向表明第二个系数也就是 w2,这样的向量就是梯度。 α:上面的梯度算法的迭代公式中的阿尔法,这个表明的是移动步长(step length)。移动步长会影响最终结果的拟合程度,最好的方法就是随着迭代次数更改移动步长。步长通俗的理解,100米,若是我一步走10米,我须要走10步;若是一步走20米,我只须要走5步。这里的一步走多少米就是步长的意思。 ▽f(w):表明沿着梯度变化的方向,也能够理解该方向求导。
该公式将一直被迭代执行,直至达到某个中止条件为止,好比迭代次数达到某个指定值或者算法达到某个能够容许的偏差范围。
梯度上升与梯度降低的区别
梯度降低是你们听的最多的,本质上梯度降低与梯度上升算法是同样的,只是公司中加法变减法,梯度降低的公式以下:
在求极值的问题中,有梯度上升和梯度降低两个最优化方法。梯度上升用于求最大值,梯度降低用于求最小值。如logistic回归的目标函数:表明的是几率,咱们想求几率最大值,即对数似然函数的最大值,因此使用梯度上升算法。而线性回归的代价函数:表明的则是偏差,咱们想求偏差最小值,因此用梯度降低算法。
根据现有数据对分类边界创建回归公司,以此进行分类。回归即最佳拟合。
每一个回归系数初始化为 1 重复 R 次: 计算整个数据集的梯度 使用 步长 x 梯度 更新回归系数的向量 返回回归系数
收集数据: 采用任意方法收集数据 准备数据: 因为须要进行距离计算,所以要求数据类型为数值型。另外,结构化数据格式则最佳。 分析数据: 采用任意方法对数据进行分析。 训练算法: 大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数。 测试算法: 一旦训练步骤完成,分类将会很快。 使用算法: 首先,咱们须要输入一些数据,并将其转换成对应的结构化数值;接着,基于训练好的回归系数就能够对这些数值进行简单的回归计算,断定它们属于哪一个类别;
优势: 计算代价不高,易于理解和实现。 缺点: 容易欠拟合,分类精度可能不高。 适用数据类型: 数值型和标称型数据。
在一个简单的数据集上,采用梯度上升法找到 Logistic 回归分类器在此数据集上的最佳回归系数
开发流程
收集数据: 可使用任何方法 准备数据: 因为须要进行距离计算,所以要求数据类型为数值型。另外,结构化数据格式则最佳 分析数据: 画出决策边界 训练算法: 使用梯度上升找到最佳参数 测试算法: 使用 Logistic 回归进行分类 使用算法: 对简单数据集中数据进行分类
本文采用100行的测试集文本。其中前两列是特征1,和特征2,第三类是对应的标签。(这里特征1,特征2做为测试使用没有实际意义,你能够理解为特征1 是水里游的,特征2是有鱼鳞。类别判断是否为鱼类。)
读取文本文件,加载数据集和类标签,这里将特征集第一列加1,便于后续回归系数的计算:
'''加载数据集和类标签''' def loadDataSet(file_name): # dataMat为原始数据, labelMat为原始数据的标签 dataMat,labelMat = [],[] fr = open(file_name) for line in fr.readlines(): lineArr = line.strip().split(',') if len(lineArr) == 1: continue # 这里若是就一个空的元素,则跳过本次循环 # 为了方便计算,咱们将每一行的开头添加一个 1.0 做为 X0 dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) labelMat.append(int(lineArr[2])) return dataMat, labelMat
梯度上升算法
使用梯度上升训练算法模型,其代码实现以下:
''' 正常的梯度上升法,获得的最佳回归系数 ''' def gradAscent(dataMatIn, classLabels): dataMatrix = mat(dataMatIn) # 转换为 NumPy 矩阵 # 转化为矩阵[[0,1,0,1,0,1.....]],并转制[[0],[1],[0].....] # transpose() 行列转置函数 # 将行向量转化为列向量 => 矩阵的转置 labelMat = mat(classLabels).transpose() # 首先将数组转换为 NumPy 矩阵,而后再将行向量转置为列向量 # m->数据量,样本数 n->特征数 m, n = shape(dataMatrix) # 矩阵的行数和列数 # print(m,n) alpha = 0.001 # alpha表明向目标移动的步长 maxCycles = 500 # 迭代次数 weights = ones((n, 1)) # 表明回归系数,ones((n,1)) 长度和特征数相同矩阵全是1 for k in range(maxCycles): h = sigmoid(dataMatrix * weights) # 矩阵乘法 # labelMat是实际值 error = (labelMat - h) # 向量相减 # 0.001* (3*m)*(m*1) 表示在每个列上的一个偏差状况,最后得出 x1,x2,xn的系数的偏移量 weights = weights + alpha * dataMatrix.transpose() * error # 矩阵乘法,最后获得回归系数 return array(weights)
其中sigmoid函数实现以下:
''' sigmoid跳跃函数 ''' def sigmoid(ZVar): return 1.0 / (1 + exp(-ZVar))
代码分析:函数的两个参数是数据加载返回的特征集和标签类集合。对数据集进行mat矩阵话转化,而类标签集进行矩阵以后转置,便于行列式的计算。而后设定步长,和迭代次数。整个特征矩阵与回归系数乘积求sigmoid值,最后返回回归系数的值。运行结果以下:
[[ 4.12414349] [ 0.48007329] [-0.6168482 ]]
思考?步长和迭代次数的初始值如何设定?
随机梯度上升算法
梯度上升算法在每次更新回归系数时都须要遍历整个数据集,该方法在处理 100 个左右的数据集时尚可,但若是有数十亿样本和成千上万的特征,那么该方法的计算复杂度就过高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为 随机梯度上升算法。因为能够在新样本到来时对分类器进行增量式更新,于是随机梯度上升算法是一个在线学习(online learning)算法。与 “在线学习” 相对应,一次处理全部数据被称做是 “批处理” (batch) 。其伪代码是:
全部回归系数初始化为 1 对数据集中每一个样本 计算该样本的梯度 使用 alpha x gradient 更新回归系数值 返回回归系数值
随机梯度上升算法的代码实现以下:
''' 随机梯度上升''' # 梯度上升与随机梯度上升的区别?梯度降低在每次更新数据集时都须要遍历整个数据集,计算复杂都较高;随机梯度降低一次只用一个样本点来更新回归系数 def stocGradAscent0(dataMatrix, classLabels): m, n = shape(dataMatrix) alpha = 0.01 weights = ones(n) # 初始化长度为n的数组,元素所有为 1 for i in range(m): # sum(dataMatrix[i]*weights)为了求 f(x)的值,f(x)=a1*x1+b2*x2+..+nn*xn h = sigmoid(sum(dataMatrix[i] * weights)) # 计算真实类别与预测类别之间的差值,而后按照该差值调整回归系数 error = classLabels[i] - h # 0.01*(1*1)*(1*n) weights = array(weights) + alpha * error * array(mat(dataMatrix[i])) return array(weights.transpose())
能够看到,随机梯度上升算法与梯度上升算法在代码上很类似,但也有一些区别: 第一,后者的变量 h 和偏差 error 都是向量,而前者则全是数值;第二,前者没有矩阵的转换过程,全部变量的数据类型都是 NumPy 数组。
判断优化算法优劣的可靠方法是看它是否收敛,也就是说参数是否达到了稳定值,是否还会不断地变化?下图展现了随机梯度上升算法在 200 次迭代过程当中回归系数的变化状况。其中的系数2,也就是 X2 只通过了 50 次迭代就达到了稳定值,但系数 1 和 0 则须要更屡次的迭代。以下图所示:
针对波动问题,咱们改进了以前的随机梯度上升算法,具体代码实现以下:
''' 改进版的随机梯度上升,使用随机的一个样原本更新回归系数''' def stocGradAscent1(dataMatrix, classLabels, numIter=150): m, n = shape(dataMatrix) weights = ones(n) # 建立与列数相同的矩阵的系数矩阵 # 随机梯度, 循环150,观察是否收敛 for j in range(numIter): dataIndex = list(range(m)) # [0, 1, 2 .. m-1] for i in range(m): # i和j的不断增大,致使alpha的值不断减小,可是不为0 alpha = 4 / (1.0 + j + i) + 0.0001 # alpha随着迭代不断减少非0 # random.uniform(x, y) 随机生成下一个实数,它在[x,y]范围内 Index = int(random.uniform(0, len(dataIndex))) # sum(dataMatrix[i]*weights)为了求 f(x)的值, f(x)=a1*x1+b2*x2+..+nn*xn h = sigmoid(sum(dataMatrix[dataIndex[Index]] * weights)) error = classLabels[dataIndex[Index]] - h weights = weights + alpha * error *array(mat(dataMatrix[dataIndex[Index]])) del (dataIndex[Index]) # print(weights.transpose()) return weights.transpose()
上面的改进版随机梯度上升算法改了两处代码。
边界可视化的代码实现以下:
''' 数据可视化展现 ''' def plotBestFit(dataArr, labelMat, weights): n = shape(dataArr)[0] xcord1,xcord2,ycord1,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) """ dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) w0*x0+w1*x1+w2*x2=f(x) x0最开始就设置为1, x2就是咱们画图的y值,而f(x)被咱们磨合偏差给算到w0,w1,w2身上去了 因此: w0+w1*x+w2*y=0 => y = (-w0-w1*x)/w2 """ y = (-weights[0] - weights[1] * x) / weights[2] ax.plot(x, y) plt.xlabel('X') plt.ylabel('Y') plt.show()
运行结果分别是:
梯度上升算法可视化结果图1-1:
随机梯度上升算法可视化结果:
优化随机梯度上升算法可视化结果:
结果分析:
图1-1的梯度上升算法在每次更新回归系数时都须要遍历整个数据集,虽然分类结果还不错该方法的计算复杂度就过高了。图1-2的随机梯度上升算法虽然分类效果不是很好(分类1/3左右),可是其迭代次数远远小于图1-1迭代次数(500次)。总体性能有所改进,可是其存在局部波动现象。基于此改进后的图1-3效果显示好不少。
代码实现以下:
'''数据集决策可视化''' def simpleTest(file_name): # 1.收集并准备数据 dataMat, labelMat = loadDataSet(file_name) # 2.训练模型, f(x)=a1*x1+b2*x2+..+nn*xn中 (a1,b2, .., nn).T的矩阵值 dataArr = array(dataMat) weights = stocGradAscent1(dataArr, labelMat) # 数据可视化 plotBestFit(dataArr, labelMat, weights)
使用 Logistic 回归来预测病毒性流感预测病人的死亡问题。这个数据集中包含了医院检测病毒性流感的一些指标,有的指标比较主观,有的指标难以测量,例如人的疼痛级别。
开发流程
收集数据: 给定数据文件
准备数据: 用 Python 解析文本文件并填充缺失值
分析数据: 可视化并观察数据
训练算法: 使用优化算法,找到最佳的系数
测试算法: 为了量化回归的效果,须要观察错误率。根据错误率决定是否回退到训练阶段,
经过改变迭代的次数和步长的参数来获得更好的回归系数
使用算法: 实现一个简单的命令行程序来收集马的症状并输出预测结果并不是难事,
这能够做为留给你们的一道习题
训练数据已经给出,这里对文件处理便可,代码以下:
'''加载数据集和类标签2''' def loadDataSet2(file_name): frTrain = open(file_name) trainingSet,trainingLabels = [],[] for line in frTrain.readlines(): currLine = line.strip().split(',') # print(len(currLine)) lineArr = [] for i in range(len(currLine)-1): lineArr.append(float(currLine[i])) trainingSet.append(lineArr) trainingLabels.append(float(currLine[len(currLine)-1])) return trainingSet,trainingLabels
处理数据中的缺失值
假设有100个样本和20个特征,这些数据都是机器收集回来的。若机器上的某个传感器损坏致使一个特征无效时该怎么办?此时是否要扔掉整个数据?这种状况下,另外19个特征怎么办? 它们是否还能够用?答案是确定的。由于有时候数据至关昂贵,扔掉和从新获取都是不可取的,因此必须采用一些方法来解决这个问题。下面给出了一些可选的作法:
如今,咱们对下一节要用的数据集进行预处理,使其能够顺利地使用分类算法。在预处理须要作两件事:全部的缺失值必须用一个实数值来替换,由于咱们使用的 NumPy 数据类型不容许包含缺失值。咱们这里选择实数 0 来替换全部缺失值,刚好能适用于 Logistic 回归。这样作的直觉在于,咱们须要的是一个在更新时不会影响系数的值。回归系数的更新公式以下:
weights = weights + alpha * error * dataMatrix[dataIndex[randIndex]]
若是 dataMatrix 的某个特征对应值为 0,那么该特征的系数将不作更新,即:weights = weights
另外,因为 Sigmoid(0) = 0.5 ,即它对结果的预测不具备任何倾向性,所以咱们上述作法也不会对偏差形成任何影响。基于上述缘由,将缺失值用 0 代替既能够保留现有数据,也不须要对优化算法进行修改。此外,该数据集中的特征取值通常不为 0,所以在某种意义上说它也知足 “特殊值” 这个要求。
若是在测试数据集中发现了一条数据的类别标签已经缺失,那么咱们的简单作法是将该条数据丢弃。这是由于类别标签与特征不一样,很难肯定采用某个合适的值来替换。采用 Logistic 回归进行分类时这种作法是合理的,而若是采用相似 kNN 的方法,则保留该条数据显得更加合理。
训练算法模型代码以下:
'''测试Logistic算法分类''' def testClassier(): # 使用改进后的随机梯度上升算法 求得在此数据集上的最佳回归系数 trainWeights file_name = './HorseColicTraining.txt' trainingSet,trainingLabels = loadDataSet2(file_name) trainWeights = stocGradAscent1(array(trainingSet), trainingLabels, 500) # 根据特征向量预测结果 teststr = '2.000000,1.000000,38.300000,40.000000,24.000000,1.000000,1.000000,3.000000,1.000000,3.000000,3.000000,1.000000,0.000000,0.000000,0.000000,1.000000,1.000000,33.000000,6.700000,0.000000,0.000000' currLine = teststr.strip().split(',') lineArr = [] for i in range(len(currLine)): lineArr.append(float(currLine[i])) res = classifyVector(array(lineArr), trainWeights) # 打印预测结果 reslut = ['死亡','存活'] print('预测结果是:',int(res))
分类函数代码以下:
'''分类函数,根据回归系数和特征向量来计算 Sigmoid的值,大于0.5函数返回1,不然返回0''' def classifyVector(featuresV, weights): prob = sigmoid(sum(featuresV * weights)) print(prob) if prob > 0.9: return 1.0 else: return 0.0
为了量化回归的效果,须要观察错误率。根据错误率决定是否回退到训练阶段,经过改变迭代的次数和步长的参数来获得更好的回归系数
'''打开测试集和训练集,并对数据进行格式化处理''' def colicTest(): file_name = './HorseColicTraining.txt' trainingSet,trainingLabels = loadDataSet2(file_name) # 使用改进后的随机梯度上升算法 求得在此数据集上的最佳回归系数 trainWeights trainWeights = stocGradAscent1(array(trainingSet), trainingLabels, 500) frTest = open('./HorseColicTest.txt') errorCount = 0 ; numTestVec = 0.0 # 读取 测试数据集 进行测试,计算分类错误的样本条数和最终的错误率 for line in frTest.readlines(): numTestVec += 1.0 currLine = line.strip().split(',') 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("逻辑回归算法测试集的错误率为: %f" % errorRate) return errorRate # 调用 colicTest() 10次并求结果的平均值 def multiTest(): numTests = 10;errorSum = 0.0 for k in range(numTests): errorSum += colicTest() print("迭代 %d 次后的平均错误率是: %f" % (numTests, errorSum / float(numTests)))
其运行结果以下:
逻辑回归算法测试集的错误率为: 0.298507
源码请进【机器学习和天然语言QQ群:436303759】文件下载:
本文版权归做者全部,旨在技术交流使用。未经做者赞成禁止转载,转载后需在文章页面明显位置给出原文链接,不然相关责任自行承担。