一、概述
分类技术是机器学习和数据挖掘应用中的重要组成部分。在数据科学中,大约70%的问题属于分类问题。解决分类问题的算法也有很多种,比如: k-近邻算法,使用距离计算来实现分类;决策树,通过构建直观易懂的树来实现分类;朴素贝叶斯,使用概率论构建分类器。
这里我们要讲的是Logistic回归,它是一种很常见的用来解决二元分类问题的回归方法,它主要是通过寻找最优参数来正确地分类原始数据。
1.Logistic Regression
逻辑回归(Logistic Regression,简称LR),其实是一个很有误导性的概念,虽然它的名字中带有"回归”两个字,但是它最擅长处理的却是分类问题。 LR分类器适用于各项广义上的分类任务,例如:评论信息的正负情感分析(二分类)、用户点击率(二分类)、用户违约信息预测(二_分类)、垃圾邮件检测 (二分类)、疾病预测(二分类)、用户等级分类(多分类)等场景。我们这里主要讨论的是二分类问题 。
2.线性回归
提到逻辑回归我们不得不提一下线性回归, 逻辑回归和线性回归同属于广义线性模型,逻辑回归就是用线性回归模型的预测值去拟合真实标签的的对数几率(一个事件的几率(odds) 是指该事件发生的概率与不发生的概率之比,如果该事件发生的概率是P,那么该事件的几率是
P
1
−
P
\frac{P}{1-P}
1−PP,对数几率就是log
P
1
−
P
\frac{P}{1-P}
1−PP。
逻辑回归和线性回归本质上都是得到一条直线,不同的是,线性回归的直线是尽可能去拟合输入变量X的分布,使得训练集中所有样本点到直线的距离最短;而逻辑回归的直线是尽可能去拟合决策边界,使得训练集样本中的样本点尽可能分离开。因此,两者的目的是不同的。
线性回归方程:
y=wx+b
此处,y为因变量,x为自变量。在机器学习中y是标签,x是特征。
2.Sigmoid函数
我们想要的函数应该是,能接受所有的输入然后预测出类别。例如在二分类的情况下,函数能输出0或1。那拥有这类性质的函数称为海维赛德阶跃函数(Heaviside step function)又称之为单位阶跃函数(如下图所示)。
单位阶跃函数的问题在于:在0点位置该函数从0瞬间跳跃到1,这个瞬间跳跃过程很难处理(不好求导)。幸运的是,Sigmoid函数也有类似的性质,且数学上更容易处理。
Sigmoid函数公式:
f(x)=
1
1
+
e
−
x
\frac{1}{1+e^-x}
1+e−x1
当x为时, Sigmoid函数值为0.5。 随着x的增大,对应的函数值将逼近于1;而随着x的减小,函数值逼近于0。所以Sigmoid函数值域为(0,1) ,注意这是开区间,它仅无限接近0和1。
import numpy as np
import math
import matplotlib.pyplot as plt
%matplotlib inline
X = np.linspace(-5,5,200) #取值范围为-5到5,取200个点,点越多,越平滑
y = [1/(1+math.e**(-x)) for x in X] #Sigmoid函数公式
plt.plot(X,y)
plt.show()
如果横坐标刻度足够大,Sigmoid函数看起来就很像一 个阶跃函数了。
X = np.linspace(-60,60,200)
y = [1/(1+math.e**(-x)) for x in X]
plt.plot(X,y)
plt.show()
3.逻辑回归
将线性模型和Sigmoid函数结合,我们可以得到逻辑回归的公式:
y=
1
1
+
e
−
(
w
x
+
b
)
\frac{1}{1+e^-(wx+b)}
1+e−(wx+b)1
这样y就是(0,1)的取值。
对式子进行变换,可得:
log
y
1
−
y
\frac{y}{1-y}
1−yy=wx+b
这个其实就是一个对数几率公式
二项Logistic回归:
P(y=0|x)=
1
1
+
e
(
w
⋅
x
)
\frac{1}{1+e^(w·x)}
1+e(w⋅x)1
P(y=1|x)=
e
(
w
⋅
x
)
1
+
e
(
w
⋅
x
)
\frac{e^(w·x)}{1+e^(w·x)}
1+e(w⋅x)e(w⋅x)
多项Logistic回归:
4.LR与线性回归的区别
逻辑回归和线性回归是两类模型,逻辑回归是分类模型,线性回归是回归模型。
二、梯度下降法
1.梯度下降求解逻辑回归
我们以最著名也最常用的梯度下降法为例,来看看逻辑回归的参数求解过程究竟实在做什么。现在有一个带两个特征并且没有截距的逻辑回归y(x1, x2),两个特征所对应的参数分别为[θ1 , θ2]。下面这个华丽的平面就是我们的损函数J(θ1,θ2 )在以θ1 , θ2和J为坐标轴的三维立体坐标系上的图像。现在,我们寻求的是损失函数的最小值,也就是图像的最低点。
那我们怎么做呢?我在这个图像上随机放一个小球, 当我松手,这个小球就会顺着这个华丽的平面滚落,直到滚到深蓝色的区域—损失函数的最低点。 为了严格监控这个小球的行为,我让小球每次滚动的距离有限,不让他一次性滚到最低点,并且最多只允许它滚动500步,还要记下它每次滚动的方向,直到它滚到图像上的最低点。
可以看见,小球从高处滑落,在深蓝色的区域中来回震荡,最终停留在了图像凹陷处的某个点上。非常明显,我们可以观察到几个现象:
首先,小球并不是一开始就直向着最低点去的,它先一口气冲到了蓝色区域边缘,后又折回来,我们已经规定了小球是多次滚动,所以可见,小球每次滚动的方向都是不同的。
另外,小球在进入深蓝色区域后,并没有直接找到某个点,而是在深蓝色区域中回震荡了数次才停下。这有两种可能: (1) 小球已经滚到了图像的最低点,所以停下了。(2) 于我设定的步数限制,小球还没有找到最低点,但也只好在500步的时候停下了。也就是说,小球不一定滚到了图像的最低处。
但无论如何,小球停下的就是我们在现有状况下可以获得的唯一点了。 如果我们够幸运,这个点就是图像的最低点,那我们只要找到这个点的对应坐标(θ*1,θ*2, Jmin),就可以获取能够让损失函数最小的参数取值[θ*1,θ*2]了。如此,梯度下降的过程就已经完成。
在这个过程中,小球其实就是一组组的坐标点(θ1, θ2, J);小球每次滚动的向就是那一个坐标点的梯度向量的方向,因为每滚动一步,小球所在的位置都发生变化,坐标点和坐标点对应的梯度向量都发生了变化所以每次滚动的方向也都不一样; 人为设置的500次滚动限制,就是设置逻辑回归的参数maxCycles, 代表着能走的最大步数,即最大迭代次数。
所以梯度下降,其实就是在众多[θ1, θ2]可能的值中遍历,一次次求解坐标点的梯度向量, 不断让损失函数的取值J逐渐逼近最小值,再返回这个最小值对应的参数取值[θ*1,θ*2 ]的过程。
2.梯度下降的种类
2.1 批量梯度下降法BGD
批量梯度下降法(Batch Gradient Descent, BGD)是梯度下降法最常用的形式,具体做法也就是在更新参数时使用
所有的样本来进行更新。
于我们有m个样本,这里求梯度的时候就用了所有样本的梯度数据。
2.2 随机梯度下降法SGD
随机梯度下降法,实和批量梯度下降法原理类似,区别在与求梯度时没有用所有的m个样本的数据,而是仅仅选取一个样本j来求梯度。 对应的更新公式是:
随机梯度下降法和批量梯度下降法是两个极端,一个采用所有数据来梯度下降,一个用一个样本来梯度下降。自然各自的优缺点都非常突出。对于训练速度来说,随机梯度下降法由于每次仅仅采用一个样本来迭代,训练速度很快,而批量梯度下降法在样本量很大的时候,训练速度不能让人满意。对于准确度来说,随机梯度下降法用于仅仅用一个样本决定梯度方向,导致解很有可能不是最优。对于收敛速度来说,由于随机梯度下降法一次迭代一 个样本,导致迭代方向变化很大,不能很快的收敛到局部最优解。但值得一提的是,随机梯度下降法在处理非凸函数优化的过程当中有非常好的表现,于其下降方向具有一定随机性, 因此能很好的绕开局部最优解,从而逼近全局最优解。
那么,有没有一个中庸的办法能够结合两种方法的优点呢?有!这就是下面的小批量梯度下降法。
2.3 小批量梯度下降法MBGD
小批量梯度下降法是批量梯度下降法和随机梯度下降法的折衷,也就是对于m个样本,我们采用x个子样本来迭代,1<x<m。一般可以取x=10,当然根据样本的数据,可以调整这个x的值。对应的更新公式是:
总结:
BGD会获得全局最优解,缺点是在更新每个参数的时候需要遍历所有的数据,计算量会很大,并且会有很多的冗余计算,导致的结果是当数据量大的时候,每个参数的更新都会很慢。
SGD以高方差频繁更新,优点是使得SGD会跳到新的和潜在更好的局部最优解,缺点是使得收敛到局部最优解的过程更加的复杂。
MBGD降结合了BGD和SGD的优点,每次更新的时候使用n个样本。减少了参数更新的次数,可以达到更加稳定收敛结果,一般在深度学习当中可以采用这种方法,将数据一个batch一个batch的送进去训练。
不过在使用上述三种方法时有两个问题是不可避免的:
1、如何选择合适的学习率(learning_ rate) 。自始至终保持同样的学习率显然是不太合适的,开始学习参数的时候,距离最优解比较远,需要-个较大的学习率能够快速的逼近最优解。当参数接近最优解时,继续保持最初的学习率,容易越过最优点,在最优点附近震荡。
2、如何对参数选择合适的学习率。对每个参数都保持的同样的学习率也是很不合理的。有些参数更新频繁,那么学习率可以适当小一点。有些参数更新缓慢,那么学习率就应该大一点。
二、使用梯度下降求解逻辑回归
testSet数据集中一共有100个点,每个点包含两个数值型特征: X1和X2。因此可以将数据在一个二维平面上展示出
来。我们可以将第一-列数据(X1)看作x轴 上的值,第二列数据(X2)看作y轴 上的值。而最后- -列数据即为分类标签。
根据标签的不同,对这些点进行分类。
在此数据集上,我们将通过批量梯度下降法和随机梯度下降法找到最佳回归系数。
1.使用BGD求解逻辑回归
批量梯度下降法的伪代码:
每个回归系数初始化为1
重复下面步骤直至收敛:
计算整个数据集的梯度
使用a1 pha* gradi ent更新回归系数的向量
返回回归系数
1.1 导入数据集
附:数据集:testSet.txt
链接:https://pan.baidu.com/s/1A-6hWVlzRFjsc83qU6X9Qg
提取码:n9ef
import pandas as pd
import numpy as np
dataSet=pd.read_table('testSet.txt',header=None)
dataSet.columns = ['x1','x2','labels']
dataSet.head() #展示前5行
1.2 定义辅助函数
Sigmoid函数
"""
函数功能:计算sigmoid函数值
参数说明:
inX:数值型数据
返回:
s:经过sigmoid函数计算后的函数值
"""
def sigmoid(inX):
s = 1/(1+np.exp(-inX))
return s
标准化函数
"""
函数功能:标准化(期望为0,方差为1)
参数说明:
xMat:特征矩阵
返回:
inMat:标准化之后的特征矩阵
"""
def regularize(xMat):
inMat = xMat.copy()
inMeans = np.mean(inMat,axis = 0)
inVar = np.std(inMat,axis = 0)
inMat = (inMat - inMeans)/inVar
return inMat
1.3 BGD算法python实现
"""
函数功能:使用BGD求解逻辑回归
参数说明:
dataSet:DF数据集
alpha:步长
maxCycles:最大迭代次数
返回:
weights:各特征权重值
"""
def BGD_LR(dataSet,alpha=0.001,maxCycles=500):
xMat = np.mat(dataSet.iloc[:,:-1].values)
yMat = np.mat(dataSet.iloc[:,-1].values).T
xMat = regularize(xMat)
m,n = xMat.shape
weights = np.zeros((n,1))
for i in range(maxCycles):
grad = xMat.T*(xMat*weights-yMat)/m
weights = weights - alpha*grad
return weights
ws = BGD_LR(dataSet,alpha=0.01,maxCycles=500)
xMat = np.mat(dataSet.iloc[:,:-1].values)
yMat = np.mat(dataSet.iloc[:,-1].values).T
xMat = regularize(xMat)
(xMat*ws).A.flatten()
p = sigmoid(xMat*ws).A.flatten()
for i,j in enumerate(p):
if j<0.5:
p[i] = 0
else:
p[i] = 1
train_error = (np.fabs(yMat.A.flatten()-p)).sum()
train_error_rate = train_error/yMat.shape[0]
train_error_rate
1.4 准确率计算函数
将上述过程封装为函数,方便后续调用
"""
函数功能:计算准确率
参数说明:
dataSet:DF数据集
method:计算权重函数
alpha:步长
maxCycles:最大迭代次数
返回:
trainAcc:模型预测准确率
"""
def logisticAcc(dataSet,method,alpha=0.01,maxCycles=500):
weightf = method(dataSet,alpha=alpha,maxCycles=maxCycles)
p = sigmoid(xMat*ws).A.flatten()
for i ,j in enumerate(p):
if j < 0.5:
p[i] = 0
else:
p[i] = 1
train_error = (np.fabs(yMat.A.flatten()-p)).sum()
trainAcc = 1-train_error/yMat.shape[0]
return trainAcc
测试函数运行效果:
logisticAcc(dataSet,BGD_LR,alpha=0.01,maxCycles=500)
2.使用SGD求解逻辑回归
随机梯度下降法的伪代码:
每个回归系数初始化为1
对数据集中每个样本:
计算该样本的梯度
使用alpha*gradient更新回归系数值
返回回归系数值
2.1 SGD算法python实现
"""
函数功能:使用SGD求解逻辑回归
参数说明:
dataSet:DF数据集
alpha:步长
maxCycles:最大迭代次数
返回:
weights:各特征权重值
"""
def SGD_LR(dataSet,alpha=0.001,maxCycles=500):
dataSet = dataSet.sample(maxCycles,replace=True)
dataSet.index = range(dataSet.shape[0])
xMat = np.mat(dataSet.iloc[:,:-1].values)
yMat = np.mat(dataSet.iloc[:,-1].values).T
xMat = regularize(xMat)
m,n = xMat.shape
weights = np.zeros((n,1))
for i in range(m):
grad = xMat[i].T*(xMat[i]*weights-yMat[i])
weights = weights - alpha*grad
return weights
SGD_LR(dataSet,alpha=0.001,maxCycles=500)
计算准确率:
logisticAcc(dataSet,SGD_LR,alpha=0.001,maxCycles=5000)
三、从疝气病症预测病马的死亡率
使用Logistic回归来预测患疝气病的马的存活问题。附:原始数据集下载地址
这里的数据包含了368个样本和28个特征。这种病不-定源自马的肠胃问题,其他问题也可能引发马疝病。该数据集中包含了医院检测马疝病的一些指标,有的指标比较主观,有的指标难以测量,例如马的疼痛级别。另外需要说明的是,除了部分指标主观和难以测量外,该数据还存在一个问题, 数据集中有30%的值是缺失的。下面首先介绍如何处理数据集中的数据缺失问题,然后再利用Logistic回归和随机梯度 上升算法来预测病马的生死。
1.准备数据
数据中的缺失值是一个非常棘手的问题,很多文献都致力于解决这个问题。那么,数据缺失究竟带来了什么问题?
假设有100个样本和20个特征,这些数据都是机器收集回来的。若机器上的某个传感器损坏导致一个特征无效时该怎么办?它们是否还可用?答案是肯定的。因为有时候数据相当昂贵,扔掉和重新获取都是不可取的,所以必须采用一些方法来解决这个问题。下面给出一些可选的做法:
●使用可用特征的均值来填补缺失值;
●使用特殊值来填补缺失值,如-1;
●忽略有缺失值的样本;
●使用相似样本的均值添补缺失值;
●使用另外的机器学习算法预测缺失值。
预处理数据做两件事:
●如果测试集中一条数据的特征值已经确实,那么我们选择实数0来替换所有缺失值,因为本文使用Logistic回归。因此这样做不会影响回归系数的值。sigmoid(0)=0.5, 即它对结果的预测不具有任何倾向性。
●如果测试集中-条数据的类别标签已经缺失,那么我们将该类别数据丢弃,因为类别标签与特征不同,很难确定采用某个合适的值来替换。
原始的数据集经过处理,保存为两个文件(一个训练,一个测试用):horseColicTest.txt和horseColicTraining.txt
链接:https://pan.baidu.com/s/1A-6hWVlzRFjsc83qU6X9Qg
提取码:n9ef
train = pd.read_table('horseColicTraining.txt',header=None)
train.head()
train.shape
train.info()
test = pd.read_table('horseColicTest.txt',header=None)
test.head()
test.shape
test.info()
2. logistic回归分类函数
得到训练集和测试集之后,我们可以利用前面的BGD_ LR或者SGD LR得到训练集的weights。这里需要定义一个分类函数,根据sigmoid函数返回的值来确定y是0还是1。
"""
函数功能:给定测试数据和权重,返回标签类别
参数说明:
inX:测试数据
weights:特征权重
"""
def classify(inX,weights) :
p = sigmoid(sum(inX*weights))
if p<0.5:
return 0
else:
return 1
构建logistic模型:
"""
函数功能:logistic分类模型
参数说明:
train:测试集
test:训练集
alpha:步长
maxCycles:最大迭代次数
返回:
retest:预测好标签的测试集
"""
def get_acc(train,test,alpha=0.001,maxCycles=5000):
weights = SGD_LR(train,alpha=alpha,maxCycles=maxCycles)
xMat = np.mat(test.iloc[:,:-1].values)
xMat = regularize(xMat)
result = []
for inX in xMat:
label = classify(inX,weights)
result.append(label)
retest = test.copy()
retest['predict'] = result
acc = (retest.iloc[:,-1]==retest.iloc[:,-2]).mean()
print(f'预测准确率为:{acc}')
return retest
get_acc(train,test,alpha=0.001,maxCycles=5000).head()