一、介绍
- Logistic回归是一个广义线性回归,模型形式:Y = WX+b 。
- Logistic回归的因变量可以是二分类也可以是多分类的,实际最常见的是二分类的Logistic回归。
- Logistic优点:计算代价不高,易于理解和实现; 缺点:容易欠拟合,分类精度可能不高;适用数据类型:数值型和标称型数据
logistic回归是一种分类方法,用于两分类问题。其基本思想为:
a) 寻找合适的假设函数,即分类函数,用以预测输入数据的判断结果
b) 构造代价函数,即损失函数,用以表示预测的输出结果与训练数据的实际类别之间的偏差;
c) 最小化代价函数,从而获取最优的模型参数
二、原理
2.1 sigmoid函数
激活函数(Activation Function)有很多种,我们这里主要介绍sigmoid函数。
我们知道线性形式表达式:Y=WX (W>0),Y的大小是随着X各个维度的叠加和的大小线性增加的,如图2-1所示(x为了方便取1维):
sigmoid函数数学表达式:g(z)=1/(1+e(-z)) 函数图像如图2-2所示
由图2-2和图2-1所示可知,逻辑函数Y值不再随X各维度之和的大小而呈线性变化了,而是变得更加平滑,这种变化在x=0时,Y变化非常快,然而在X各维度之和持续增大或减小时,Y值变化越来越小,逐渐接近1和0。这种因变量和自变量的变化形式就被称为Logistic变化。利用sigmoid函数做激活函数,我们就可以让线性函数变成表达0~1之间的曲线。
做二分类分类函数时,假设输入的特征是(X0,X1,X2,.....,Xn),我们将每个特征乘以一个回归系数(W0,W1,W2,.....,Wn)然后累加得到一个值Z。
Z = W0X0+W1X1+W2X2+......+WnXn,我们可以写成Z=WTX, W为回归系数,X为输入的特征集,Z为输出的值。输出的值在0~1之间,Z值如果大于0.5,输出为1;小于0.5,则输出0。
所以我们需要找出最佳回归系数,即(W0,W1,W2,......,Wn)。
2.2 凸函数(Convex Function)
优化算法之前,先介绍一下凸函数
凸集:在实数域R上的向量空间中,如果集合S中任意两点的连接上的点都在S内;凸集和凹集如图2-3所示:
凸函数被定义为某个向量空间的凸子集C上的实值函数f。如果在其定义域C上的任意两点x1,x2,以及α∈[0,1],都有f(ax1+(1-a)X2)<= af(X1)+(1-a)f(X2) ;凸函数示意图如图2-4所示:
注意:关于凸函数,国内外定义会有稍许差别,有的会称上凸和下凸函数,有的称上凸为凹函数,下凸为严格凸函数,以具体教材为准。
判定方法:
可利用定义法、已知结论法以及函数的二阶导数,对于实数集上的凸函数,一般的判别方法是求它的二阶导数,如果其二阶导数在区间上恒大于0,就称为凹函数(上凸) 。如果其二阶导数在区间上恒小于0,就称为严格凸函数(下凸)。
凸函数性质:
- 凸优化的任一局部极小(大)点也是全局极小(大)点,且全体极小(大)点的集合为凸集。
- 凸优化的任一局部最优解都是它的整体最优解。
2.3 代价函数
为了找到最优解的目的函数,我们需要引入代价函数,作用是为了计算算法预测值与实际值之间的误差大小。
代价函数如图2-5所示:
由以上式子可知:当预测准确(即y=1且预测值h接近于1,或当y=0且h接近于0)时,cost接近于0,即误差接近于0;而当预测错误(即y=1而预测值h接近于0,或当y=0而h接近于1)时,cost就为趋于无穷大,即误差趋于无穷大。
将以上式子合并,如图2-6所示:
我们将m个训练样本代价误差累加起来,得到J(w)如图2-7所示:
hw(x) 代表算法作出的预测值,即将训练样本的数据特征x与回归系数w进行向量乘法,并作为sigmoid函数得到的输出。过程如图2-8所示
我们希望这个代价函数的函数值向0趋近,所以求梯度,即J(w)对回归系数w的三项各自求偏导过程,如图2-9所示:
其中有j项特征w求偏导
那么当j=0时,也就是对w0求导,等于 xi0
所以我们的代价函数就是:
2.4 梯度上升与梯度下降
梯度上升与梯度下降都是优化算法的一种。本身具有良好的通用性,多数算法都可以使用,
2.4.1 梯度上升
对梯度上升来说,要找到某个函数的最大值,最好最快的方法就是沿着函数的梯度方向探寻,。如果梯度记为▽,那么函数f(x,y)的梯度表示:
,如果实值函数F(x)在点a处可微且有定义,那么函数F(x)在a点沿着梯度方向∇F(a) 上升最快
它求的是函数关于各个变量的偏导数,所以它总是代表函数值增长最快的方向,移动方向确定了,我们只需要确定步长α(学习率),梯度上升公式为:W:=W+a∇wf(W) ,其中f(W)为代价函数。f(W)= (y - hw(x))xj
2.4.2 梯度下降
梯度下降与梯度上升区别在于沿梯度方向不同。
如果实值函数F(x)在点a处可微且有定义,那么函数F(x)在a点沿着梯度相反的方向−∇F(a) 下降最快。
梯度下降公式为:W:=W-a∇wf(W) ,其中f(W)为代价函数。f(w)= f(W)= (hw(x) - y)xj
我们只需要不断迭代更新我们的W,直到迭代次数完毕或误差达到允许范围,以此找出最佳回归系数W。
2.4.3 随机梯度上升(下降)
之前的方式是将所有的训练样本代入,最终所求得的解也是全局最优解,求解出来的参数将使损失函数最小。如果将所有样本矩阵带入进行计算,这就是所谓的批量梯度上升(下降)。
但在实际应用场景中,最大的问题就是样本矩阵可能非常大。比如大到放不进内存,比如大到进行一轮迭代需要的运算时间非常长,这个时候,批量梯度上升(下降)就不是那么好用了。这个时候,我们可以采用考虑随机梯度上升(下降)。
批量梯度上升是一次训练带入所有样本,随机梯度上升则是每来一次样本进行一次计算:
i表示是第i个样本,j表示样本第j个维度。
随机梯度上升(下降)是通过每个样本来迭代更新。如果样本的数量很多,有可能才迭代了一小部分样本,就已经得到了W的解。所以随机梯度上升(下降)的收敛速度可能比批量梯度上升(下降)要快,而且运算量小。但是随机梯度上升(下降)的问题是每次迭代并不是全局最优解的方向,尤其是遇到噪声数据,影响会比较大。
三、代码实现
参考《机器学习实战》第五章Logistic回归样例
训练集如图3-1所示:
导入训练样本代码,如下所示:
#加载训练集
def loadDateSet():
dataMat =[];labelMat =[] #训练数据集,标签List
fr = open('testSet.txt')
for line in fr.readlines():
lineArr = line.strip().split()
dataMat.append([1.0,float(lineArr[0]),float(lineArr[1])]) # 属性值x0,x1,x2
labelMat.append(int(lineArr[2])) #标签
return dataMat,labelMat
loadDataSet用来从文本里面获取样本数据集和样本标签。要注意的是dataMat.append([1.0,float(lineArr[0]),float(lineArr[1])]) ,人为地给每个样本多加了一个特征X0,且设置值为1.0
梯度上升计算最佳回归系数代码,如下所示:
def gradAscent(dataMatIn,classLables):
dataMatrix = np.mat(dataMatIn) #转换成二维数组 100X3的矩阵
labelMat = np.mat(classLables).transpose()#transpose转置 100X1的矩阵
m,n = np.shape(dataMatrix) #(样本数:100)列数(特征:3)
alpha = 0.001 #设置梯度上升或下降的步长
maxCycles =500#迭代次数
weight = np.ones((n,1))#初始回归系数 3X1的矩阵
for k in range(maxCycles):
h = sigmoid(dataMatrix*weight) #通过激活函数转换成非线性函数
error = (labelMat-h) #代价函数
weight = weight + alpha * dataMatrix.transpose()*error #得到每一梯度的W值
return weight
sigmoid函数计算sigmoid值,sigmoid(z)=1/(1+e-z)
gradAscent函数就是梯度上升法训练过程的函数。
样本数据集是DataMatIn;标签List经过转置(transpose)后变成了列向量;weights是列向量,包含了3个回归系数,初始化为1.0,如图3-2所示:
梯度上升伪代码如下:
- 每个回归系数初始化为1
- 重复R次:
- 计算整个数据集梯度
- 使用alpha x gradient更新回归系数的向量
- 返回回归系数
我们从gradAscent函数定义中可以看到error= (labelMat-h); h=sigmoid(dataMatrix * weights), h是每个特征Xj与系数Wj乘积累加,后通过sigmoid函数得到的输出,是一个100X1列的向量。
我们将之前写的梯度公式和那行代码对照来看,如图3-3所示:
由W:=W+a∇wf(W)可知,dataMatrix.transpose()* error就是我们之前求解的代价函数(yi-hw(xi))xij
经过500次迭代后,我们得出最佳回归系数结果代码如下:
dataArr,labelMat = loadDateSet()
weight = gradAscent(dataArr,labelMat)
print(weight)
plotBestFit(weight.getA())
输出如图3-4所示:
通过matplotlib中的pyplot画出所有数据点和分类界线。代码如下所示:
def plotBestFit(weights):
dataMat,labelMat = loadDateSet()#导出数据
dataArr = np.array(dataMat) #矩阵对象
n = np.shape(dataArr)[0]
xcord1= []; ycord1=[]
xcord2 = []; ycord2=[]
#根据类别分别将数据导入x1、x2、y1、y2中
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)
plt.xlabel('X1');plt.ylabel('X2')
plt.show()
y=(-weights[0]-weights[1]*x)/weights[2],我们根据之前所说的sigmoid函数性质,在横坐标为0的时候,分成两个类别(0和1),所以我们令函数 输入为0,得到:W0X0+W1X1+W2X2=0
其中x0为1,所以 x2=(-w0-w1x1) / w2。我们令x1为横坐标,令x2为纵坐标得出如图3-5所示图像:
四、Logistic的拓展-多分类问题
上面介绍了Logistic回归解决二分类问题,实际上Logistic回归也是可以解决多分类问题,原理很简单,举例我们要将一个集合,分成三类。我们只需要进行三次二分类操作
(1)将class1看作一类,class2和class3看作另一类,如图4-1所示:
(2)将class2看作一类,class1和class3看作另一类,如图4-2所示:
(3)将class3看作一类,class1和class2看作另一类,如图4-3所示:
这时我们有三个分类函数,由Logistic回归我们可以算出每一类的概率,哪一类的概率越大,便分为哪一类。这样就可以根据二分类的原理来解决多分类问题。
五、总结
综上,我们了解了Logistic回归的原理和实现步骤
- 首先,我们知道用线性函数y=WX+b来划分类别,但是为了更好的用函数来进行分类表示,所以我们引入了sigmoid函数,将WX+b作为sigmoid输入,输出0~1的值,我们以大于或小于0.5为界来划分类别。
- 为了找到最合适的划分函数,我们需要找到合适的最佳回归系数W与b。这里我们将b常数项也放入在W矩阵中一起求解。
- 然后我们找到合适的代价函数来描述预测值与真实值之间的误差,当然代价函数也可以由自己来定义。
- 根据凸函数定义,我们使用梯度上升(下降)等优化函数沿着梯度方向寻找最佳回归系数时,找到的局部极大值或极小值就是我们要找的最佳回归系数。
六、参考资料
- 《机器学习实战》第五章Logistic回归
- https://blog.csdn.net/louishao/article/details/54813073
- https://blog.csdn.net/CharlieLincy/article/details/70767791?locationNum=11&fps=1