逻辑回归(Logistic)虽带有回归二字,但它倒是一个经典的二分类算法,它适合处理一些二分类任务,例如疾病检测、垃圾邮件检测、用户点击率以及上文所涉及的正负情感分析等等。python
首先了解一下何为回归?假设如今有一些数据点,咱们利用一条直线对这些点进行拟合(该线称为最佳拟合直线),这个拟合的过程就称做回归。利用逻辑回归进行分类的主要思想是:根据现有数据对分类边界线创建回归公式,以此进行分类。算法
线性回归算法后面的笔记会介绍,这里简单对比一下二者,逻辑回归和线性回归的本质相同,都意在拟合一条直线,但线性回归的目的是拟合输入变量的分布,尽量让全部样本到该条直线的距离最短;而逻辑回归的目的是拟合决策边界,使数据集中不一样的样本尽量分开,因此两个算法的目的是不一样的,处理的问题也不一样。数组
咱们想要的函数应该是,能接受全部的输入而且预测出类别,好比二分类中的0或者一、正或者负,这种性质的函数被称为海维赛德阶跃函数,图像以下:app
但这种函数的问题在于从0跳跃到1的过程很是难处理,好比咱们常接触的屡次函数,可能在某种条件下须要求导解决问题;而Sigmoid函数也具备相似的性质,而且在数学上更容易处理,其公式以下:函数
下图是Sigmoid函数在不一样坐标尺度下的两条曲线图。当x为0时,Sigmoid函数值为0.5,随着x的增大,对应的Sigmoid值将逼近于1;而随着x的减少,Sigmoid值将逼近于0。若是横坐标刻度足够大,Sigmoid函数看起来就很像一个阶跃函数。学习
若咱们将Sigmoid函数的输入记做z,可推出下面公式:spa
它表示将这两个数值向量对应元素相乘而后所有相加起来获得z值,其中向量x是分类器的输入数据,向量w就是咱们要找到的能使分类器尽量准确的最佳参数。3d
由上述公式就可得:code
其中$h_w(x)$的做用就是给定输入时,输出分类为正向类(1)的可能性。例如,对于一个给定的x,$h_w(x)$的值为0.8,则说明有80%的可能输出为正向类(1),有20%的可能输出为负向类(0),两者成补集关系。视频
对于一个二分类问题而言,咱们给定输入,函数最终的输出只能有两类,0或者1,因此咱们能够对其分类。
为了运算便捷,咱们将其整合为一个公式,以下:
因为乘法计算量过大,因此能够将乘法变加法,对上式求对数,以下:
能够看出当y=1时,加号后面式子的值为0;当y=0时,加号前面式子的值为0,这与上文分类式子达到的效果是同样的。L(w)称为似然函数,l(w)称为对数似然函数,是依据最大似然函数推导而成。此时的应用是梯度上升求最大值,若是梯度降低求最小值,可在公式以前乘以$-\frac{1}{n}$。
为了学习嘛,这里再介绍一下另外一种方式,利用损失函数推导应用于梯度降低的公式;损失函数是衡量真实值与预测值之间差距的函数,因此损失函数值越小,对应模型的效果也越好,损失函数公式以下:
可能只看公式理解相对抽象,经过对应的函数图像足以理解上式,以下:
注意!!!公式后面的y*不表明纵坐标 !!!
当类标签y=1时,对应的-log(x)图像越接近于1,其距离x轴越近,表明其损失越小;反之当类标签y=0时,对应的-log(1-x)图像越接近于0,其距离x轴越近,表明其损失越小,也就是预测值越接近于真实值。
将两个损失函数综合起来得:
对于m个样本,总损失函数为:
其中m为样本个数、yi为标签,可取0或一、i为第i个样本、$p(x_i)$为预测输出。
上面已经列出了一大堆的公式,难道这又要开始一连串的大公式?
心态放平,上文虽然说公式有点多,但目的都是为了推出最后的对数似然函数或者总损失函数,掌握这两个是关键,梯度上升和梯度降低也会利用上面的公式作推导,因此两者之间存在关联。首先梯度降低你须要了解一下何为梯度?
若是将梯度记为▽,则函数f(x,y)的梯度可由下式表示:
通俗的说,即对多元函数的参数求偏导,并把求得的各个参数的偏导以向量的形式写出来。或者你只要理解这个梯度要沿着x的方向移动$\frac{\delta f(x,y)}{\delta x}$,沿着y方向移动$\frac{\delta f(x,y)}{\delta y}$足以,但f(x,y)必需要在待计算的点上有定义且可微。
下图为一个梯度降低的例子,梯度降低法在到达每个点以后都会从新评估下一步将要移动的方向。从x0开始,在计算完该点的梯度,函数就会移动到下一个点x1。在x1点,梯度会从新计算,继而移动到x2点。如此循环迭代此过程,直到知足中止条件,每次迭代过程都是为了找出当前能选取到的最佳移动方向。
以前一直在讨论移动方向,而未提到过移动量的大小。该量值称为步长,记做$\alpha$。那么就能够得出梯度上升法的迭代公式
:
因此对于上文所说起的对数似然函数$J(w)$,咱们也能够利用上述迭代的方式,一步一步移动到目标值或者无限接近于目标值,$J(w)$求偏导的公式以下:
可能有的人看到这个偏导公式有点蒙,其实这里面用到的三个函数公式都是上文所说起的,来回顾一下。
求偏导过程涉及到高数知识,即最外层函数对外层函数求偏导、外层函数对内层函数求偏导、内层函数对其元素求偏导,三者相乘可得出所需偏导。推导过程有些麻烦,这里只给出推导结果,在后面运用时咱们也只会运用到最终结果,以下:
若是利用将对数似然函数换成损失函数$J(\Theta)$,则获得的是有关计算梯度降低的公式,以下:
两个公式中的w和$\Theta$的含义是同样的,都表明咱们所求的最佳回归系数,两个公式对比能够看出梯度上升和梯度降低只有加减号区别之分。下面这个动图就能够很好的展现梯度降低法的过程:
公式推导部分至此结束了,基础偏好的伙伴可能一遍就懂了,但基础偏弱理解起来比较困难,偶当时也是对着书、跟着视频啃了很久,多啃几遍终归会理解的。
有这样一份数据集,共100个样本、两个特征(X1与X2)以及一个分类标签,部分数据和所绘制图像以下:
X1 | X2 | 类别 |
---|---|---|
0.197445 | 9.744638 | 0 |
0.078557 | 0.059736 | 1 |
-1.099458 | 1.688274 | 1 |
1.319944 | 2.171228 | 1 |
-0.783277 | 11.009725 | 0 |
在此数据集上,咱们将经过梯度降低法找到最佳回归系数,也就是拟合出Logistic回归模型的最佳参数。
该算法的伪代码以下:
每一个回归系数初始化为1 重复R次: 计算整个数据集的梯度 使用alpha*gradient更新回归系数的向量 返回回归系数
def loadDataSet(): dataMat = [] # 建立数据列表 labelMat = [] # 建立标签列表 fr = open('LRData.txt','r',encoding='utf-8') #逐行读取所有数据 for line in fr.readlines(): #将数据分割、存入列表 lineArr = line.strip().split() #数据存入数据列表 dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) #标签存入标签列表 labelMat.append(int(lineArr[2])) fr.close() return dataMat, labelMat
loadDataSet函数的做用是打开存储数据的文本文件并逐行读取。每行的前两个值分别对应X1和X2,第三个值是数据对应的类别标签。为了方便计算,函数还在X1和X2以前添加了一个值为1.0的X1,X1能够理解为偏置,即下图中的x0。
#sigmoid函数 def sigmoid(inX): return 1.0 / (1 + np.exp(-inX))
sigmoid函数就是传入一个参数(这里是一个100*1的向量),经过公式计算并返回值。
def gradAscent(dataMatIn, classLabels): # 将列表转换成numpy的matrix(矩阵) dataMatrix = np.mat(dataMatIn) # 将列表转换成numpy的mat,并进行转置 labelMat = np.mat(classLabels).T # 获取dataMatrix的行数和列数。 m, n = np.shape(dataMatrix) # 设置每次移动的步长 alpha = 0.001 # 设置最大迭代次数 maxCycles = 500 # 建立一个n行1列都为1的矩阵 weights = np.ones((n,1)) for k in range(maxCycles): # 公式中hΘ(x) h = sigmoid(dataMatrix * weights) # 偏差,即公式中y-hΘ(x) error = labelMat - h # 套入总体公式 weights = weights + alpha * dataMatrix.T * error return weights
最后weights返回的是一个3x1的矩阵,运行截图以下:
gradAscent传入参数为loadDataSet的两个返回值,而后经过numpy的mat方法将dataMatrix和labelMat 分为转化为100x3和1x100的矩阵,但labelMat 通过T转置后变成100x1的矩阵。而后初始化权重,利用的方法就是建立一个n行1列的矩阵。整个算法的关键处于for循环中,咱们先回顾一下上文的两个公式。
其中h的计算结果即$h_w(x)$,权重weight为W向量,输入矩阵dataMatrix为x向量。偏差error表明$y^{(i)}-h_w x^{(i)}$,有人可能会发现$\frac{1}{m}$没有出如今代码中,由于$\alpha$和$\frac{1}{m}$都为常数,两者相乘也为常数,因此只须要用$\alpha$代替便可。
公式中的加和部分又怎么体现呢?若是学过线性代数或者了解numpy运算的伙伴应该都理解矩阵的乘法,不理解也没有关系,看下图这个例子,当两个矩阵相乘时,对应元素之间会求和做为最终元素。
def plotDataSet(weight): #获取权重数组 weight = weight.getA() # 加载数据和标签 dataMat, labelMat = loadDataSet() # 将列表转换成numpy的array数组 dataArr = np.array(dataMat) #获取样本个数 n = np.shape(dataMat)[0] #建立4个空列表,1表明正样本、0表明负样本 xcord1 = []; ycord1 = [] xcord0 = []; ycord0 = [] # 遍历标签列表,根据数据的标签进行分类 for i in range(n): if int(labelMat[i]) == 1: # 若是标签为1,将数据填入xcord1和ycord1 xcord1.append(dataArr[i,1]); ycord1.append(dataArr[i,2]) else: # 若是标签为0,将数据填入xcord0和ycord0 xcord0.append(dataArr[i,1]); ycord0.append(dataArr[i,2]) #绘制图像 fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(xcord1, ycord1, s = 20, c = 'red', marker = '*',label = 'Class1') ax.scatter(xcord0, ycord0, s = 20, c = 'green',marker = 's',label = 'Class2') #绘制直线,sigmoid设置为0 x = np.arange(-3.0, 3.0, 0.1) y = (-weight[0] - weight[1] * x) / weight[2] ax.plot(x, y) #标题、x标签、y标签 plt.title('LRData') plt.legend(loc='upper left') plt.xlabel('X1'); plt.ylabel('X2') plt.savefig("E:\machine_learning\LR03.jpg") plt.show()
这部分代码惟一须要注意的是,将sigmoid的值设置为0,能够回忆一下文章刚开始时的Sigmoid函数图像,0是两个分类的分解处。所以,咱们设定$0=w_0x_0+w_1x_1+w_2x_2$,$x_0$的值为1,因此已知回归系数,就可求得$x_1和x_2$的关系式,从而画出决策边界。
上图能够看出分类的效果仍是不错的,根据函数绘制出的直线已经很大程度上将两类样本分隔开,100个样本中,只有五个样本分类错误,其中有三个仍是位于回归线上。
本文所讲的梯度上升公式,是属于批量梯度上升,此外还有随机梯度上升、小批量梯度上升,而批量梯度上升每次计算都要计算全部的样本,因此程序计算过程是十分复杂的,而且容易收敛到局部最优,而随机梯度上升将会对算法进行调优,下一篇文章将会介绍随机梯度上升,并分析二者之间的区别。
欢迎关注公众号【奶糖猫】,后台回复"逻辑回归"可获取数据和源码供参考,感谢阅读。