机器学习(六):Logistic回归(基础篇)
Logistic回归与梯度上升算法
Logistic回归是众多分类算法的一员,与其他线性回归不一样,Logistic通常适用与分类。在日常生活中,Logistic回归用于二分类问题,例如:预测明天是否会下雨。它也可以用于多分类问题。让我们先了解一下什么是Ligistic回归
1.Logistic回归
说到回归,大家都会先想到线性回归。那什么是回归呢?假设现在有一些数据点,我们利用一条直线对这些点进行拟合(该线称为最佳拟合直线),这个拟合过程就称作为回归,如下图所示:
Logistic回归是分类方法,它利用的是Sigmoid函数阈值在[0,1]这个特性。Logistic回归进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,以此进行分类,其实,Logistic本质上是一个基于条件概率的判别模型(Discriminative Model)。
所以要直到Logistic回归是什么,我们要先看一看Sigmoid函数,我们也可以称它为Logistic函数。公式如下:
将其整合成一个公式:
z是一个矩阵,
θ
\theta
θ是参数列向量(要求解的),x是样本列向量(给定数据集)
θ
T
\theta ^T
θT表示
θ
\theta
θ的转置。g(z)函数实现了任意实数到[0,1]的映射,这样我们的数据集([
x
0
,
x
1
,
.
.
.
,
x
n
x_0,x_1,...,x_n
x0,x1,...,xn]),不管是大于1或者小于0,都可以映射到[0,1]区间进行分类,
h
θ
(
x
)
h_{\theta}(x)
hθ(x)给了输出为一的概率。比如当
h
θ
(
x
)
h_{\theta}(x)
hθ(x)=0.7,那么说明有70%的概率输出为1,输出为0的概率是输出为1的补集,也就是30%。
下面用python代码来绘画一下sigmoid函数:
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1.0 / (1 + np.exp(-x))
sigmoid_inputs = np.arange(-10, 10, 0.01)
sigmoid_outputs = sigmoid(sigmoid(sigmoid_inputs))
print("Sigmoid Function Input :: {}".format(sigmoid_inputs))
print("Sigmoid Function Output :: {}".format(sigmoid_outputs))
plt.plot(sigmoid_inputs, sigmoid_outputs)
plt.xlabel("Sigmoid Inputs")
plt.ylabel("Sigmoid Outputs")
plt.show()
输出如下图:
如果我们有合适的参数列向量
θ
(
[
θ
0
,
θ
1
,
.
.
.
,
θ
n
]
T
)
\theta([\theta_0,\theta_1,...,\theta_n]^T)
θ([θ0,θ1,...,θn]T),以及样本列向量$x([x_0,x_1,…,x_n]),那么我们对样本的x分类就可以通过上述式子计算出一条概率,如果这个概率大于0.5我们就可以说样本是正样本,否则样本是负样本。
之前写的那个对于“垃圾邮件判别问题”,对于给定的邮件(样本),我们定义非垃圾邮件为正类,垃圾邮件为负类。我们通过计算概率值即可以判断邮件是否是垃圾邮件。
如何得到合适的参数 θ \theta θ?
根据sigmoid函数的性质,我们可以假设:
式子即为在已知样本x和参数
θ
\theta
θ的情况下,样本x属性属于正样本(y=1)和负样本(y=0)的条件概率。一个样本属于正样本的概率为0.51,那么我们可以把它定义为正样本,如果另一个样本等于0.99,那么我们也可以说明这个样本等于正样本。但是很明显的可以看出,属于0.99的样本概率更高,更具说服力。我们可以把上述两个概率公式合二为一:
合并出来的Cost,我们称之为代价函数(Cost Function).当y等于1的是,(1-y)项第二项为0.另一个同理。为了简化问题,我们对整个表达式求对数,(将指数问题对数化是数据处理中一个比较常见的方法:
这个代价函数,是对于一个样本而言的,给定一个昂本骂我们可以通过这个代价函数求出样本所属类别的概率,这个概率越大越好,所以求解这个代价函数的最大值。
概率离不开最大似然估计,假定样本与样本之间相互独立,那么整个样本集生成的概率即为所有样本生成概率的乘积,再将公式对数化,便可以得到下面的公式:
其中,m为样本的总数,y(i)表示第i个样本的类别,x(i)表示第i个样本,需要注意的是θ是多维向量,x(i)也是多维向量。
综上所述,满足J(θ)的最大的θ值即是我们需要求解的模型。
怎么求解使J(θ)最大的θ值呢?因为是求最大值,所以我们需要使用梯度上升算法。如果面对的问题是求解使J(θ)最小的θ值,那么我们就需要使用梯度下降算法。面对我们这个问题,如果使J(θ) := -J(θ),那么问题就从求极大值转换成求极小值了,使用的算法就从梯度上升算法变成了梯度下降算法,它们的思想都是相同的,学会其一,就也会了另一个。本文使用梯度上升算法进行求解。
梯度上升算法
我们先来看一个函数:
f
(
x
)
=
−
x
2
+
3
x
f(x) = -x^2 + 3x
f(x)=−x2+3x
用python画出图像:
想要求它的极大值,我们应该先去求导函数:
f
′
(
x
)
=
−
2
x
+
3
f'(x) = -2x +3
f′(x)=−2x+3
令f’(x)等于0,可求出x=1.5时即取得函数的f(x)的极大值,极大值为2.25.。
但是真实环境中的函数不会像上面这么简单,就算求出了函数的导数,也很难精确计算出函数的极值。此时我们就可以用迭代的方法来做。就像爬坡一样,一点一点逼近极值。这种寻找最佳拟合参数的方法,就是最优化算法。爬坡这个动作用数学公式表达即为:
其中,α为步长,也就是学习速率,控制更新的幅度。比如从(0,0)开始,迭代路径就是1->2->3->4->…->n,直到求出的x为函数极大值的近似值,停止迭代。我们可以编写Python3代码,来实现这一过程:
import numpy as np
import matplotlib.pyplot as plt
def Gradient_Asscent_test():
def f_prime(x_old):
return -2*x_old +3
x_old = 1
x_new = 0
alpha = 0.01
presision = 0.0000001
while abs(x_new - x_old)>presision:
x_old = x_new
x_new = x_old + alpha*f_prime(x_old)
print(x_new)
Gradient_Asscent_test()
输出结果如下:
如图所示,输出结果十分接近于零。这个慢慢爬坡的过程就叫做梯度上升算法。那么J(θ)函数的极值,也可以这么求解。公式写为:
原本J(θ)为:
sigmoid函数为:
那么,现在我只要求出J(θ)的偏导,就可以利用梯度上升算法,求解J(θ)的极大值了。
我们看一看求导的过程:
这里借用了其他辅助求导公式来帮助我们求导。可以很好的简化了求导问题。
其中:
在机器学习中,对数log与ln有着一样的作用,所以在求导的过程在log可以当作ln对带。
再由:
可得:
最后一部分:
将三个综合起来:
因此,我们的梯度上升公式为:
其实上面写了这么多公式,看起来很复杂,其实,只有最后一条公式需要我们记忆,其他公式是为了帮我们进行推导理解。
python实现
1.数据集的描述
数据集下载:数据集
这个数据有两维特征,因此可以将数据在一个二维平面上展示出来。我们可以将第一列数据(X1)看作x轴上的值,第二列数据(X2)看作y轴上的值。而最后一列数据即为分类标签。根据标签的不同,对这些点进行分类。
看看数据集的分布:
import numpy as np
import matplotlib.pyplot as plt
def Gradient_Asscent_test():
def f_prime(x_old):
return -2*x_old +3
x_old = 1
x_new = 0
alpha = 0.01
presision = 0.0000001
while abs(x_new - x_old)>presision:
x_old = x_new
x_new = x_old + alpha*f_prime(x_old)
print(x_new)
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])]) #添加数据集
labelMat.append(int(lineArr[2])) #添加标签集
fr.close() #关闭文件
return dataMat,labelMat
def plotData():
dataMat, labelMat = loadDataSet() #数据集和标签集
dataArr = np.array(dataMat) #将数据集转化为数组
n = len(dataArr) #数据的个数
right_x = []; right_y=[] #正确数据的x值和y值
wrong_x = []; wrong_y=[] #错误数据的x值和y值
for i in range(n): #循环遍历
if labelMat[i] == 1: #如果是正确饿值
right_x.append(dataArr[i][1]) #保存数据
right_y.append(dataArr[i][2])
else:
wrong_x.append(dataArr[i][1])
wrong_y.append(dataArr[i][2])
plt.scatter(right_x, right_y, s=20, c='red', marker='s', alpha=.5, label='right') # 绘制正样本 #画正确的图
plt.scatter(wrong_x, wrong_y, s=20, c='green', alpha=.5, label='wrong') # 绘制负样本 #画错误的图
plt.title('DataSet') # 绘制title
plt.xlabel('x')
plt.ylabel('y') # 绘制label
plt.legend(loc='lower left')
plt.show()
plotData()
我们看看运行结果:
可以看得出来,绿色在一块,红色在一块。事实证明该数据集可以做较好的分类。
从上图可以看出数据的分布情况。假设Sigmoid函数的输入记为z,那么z=
w
0
x
0
w_0x_0
w0x0 +
w
1
x
1
w_1x_1
w1x1 +
w
2
x
2
w_2x_2
w2x2,即可将数据分割开。其中,
x
0
x_0
x0为全是1的向量,
x
1
x_1
x1为数据集的第一列数据,
x
2
x_2
x2为数据集的第二列数据。另z=0,则0=
w
0
w_0
w0 +
w
1
x
1
w_1x_1
w1x1 +
w
2
x
2
w_2x_2
w2x2。横坐标为
x
1
x_1
x1,纵坐标为
x
2
x_2
x2。这个方程未知的参数为
w
0
w_0
w0,
w
1
w_1
w1,
w
2
w_2
w2,也就是我们需要求的回归系数(最优参数)。
训练算法
在编写代码之前,让我们回顾下梯度上升迭代公式:
将上述公式矢量化:
根据矢量化的公式,编写代码如下:
import numpy as np
import matplotlib.pyplot as plt
def gradAscent(dataSet, labelSet):
dataMatrix = np.mat(dataSet)
labelMatrix = np.mat(labelSet).T
m, n = np.shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weight = np.ones((n, 1))
for k in range(maxCycles):
h = sigmoid(dataMatrix * weight)
error = labelMatrix - h
weight = weight + alpha*dataMatrix.T*error
return weight.getA()
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
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])]) #添加数据集
labelMat.append(int(lineArr[2])) #添加标签集
fr.close() #关闭文件
return dataMat,labelMat
dataMat, LabelMat = loadDataSet()
print(gradAscent(dataMat, LabelMat))
输出如下所示:
可以看出,我们已经求解出回归系数[w0,w1,w2]。通过求解出的参数,我们就可以确定不同类别数据之间的分隔线,画出决策边界。
绘制边界函数
我们已经解出了一组回归系数,它确定了不同类别数据之间的分隔线。现在开始绘制这个分隔线,编写代码如下:
import numpy as np
import matplotlib.pyplot as plt
def gradAscent(dataSet, labelSet):
dataMatrix = np.mat(dataSet)
labelMatrix = np.mat(labelSet).T
m, n = np.shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weight = np.ones((n, 1))
for k in range(maxCycles):
h = sigmoid(dataMatrix * weight)
error = labelMatrix - h
weight = weight + alpha * dataMatrix.T * error
return weight.getA()
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
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])]) # 添加数据集
labelMat.append(int(lineArr[2])) # 添加标签集
fr.close() # 关闭文件
return dataMat, labelMat
def plotBestFit(weights):
dataMat, labelMat = loadDataSet() #加载数据集
dataArr = np.array(dataMat) #转换成numpy的array数组
n = np.shape(dataMat)[0] #数据个数
xcord1 = []; ycord1 = [] #正样本
xcord2 = []; ycord2 = [] #负样本
for i in range(n): #根据数据集标签进行分类
if int(labelMat[i]) == 1:
xcord1.append(dataArr[i,1]); ycord1.append(dataArr[i,2]) #1为正样本
else:
xcord2.append(dataArr[i,1]); ycord2.append(dataArr[i,2]) #0为负样本
fig = plt.figure()
ax = fig.add_subplot(111) #添加subplot
ax.scatter(xcord1, ycord1, s = 20, c = 'red', marker = 's',alpha=.5)#绘制正样本
ax.scatter(xcord2, ycord2, s = 20, c = 'green',alpha=.5) #绘制负样本
x = np.arange(-3.0, 3.0, 0.1)
y = (-weights[0] - weights[1] * x) / weights[2]
ax.plot(x, y)
plt.title('BestFit') #绘制title
plt.xlabel('X1'); plt.ylabel('X2') #绘制label
plt.show()
if __name__ == '__main__':
dataMat, labelMat = loadDataSet()
weights = gradAscent(dataMat, labelMat)
plotBestFit(weights)
结果如下:
这个分类结果相当不错,从上图可以看出,只分错了几个点而已。但是,尽管例子简单切数据集很小,但是这个方法却需要大量的计算(300次乘法)。
总结
Logistic回归的一般过程:
- 收集数据:采用任意方法收集数据。
- 准备数据:由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳。
- 分析数据:采用任意方法对数据进行分析。
- 训练算法:大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数。
- 测试算法:一旦训练步骤完成,分类将会很快。
- 使用算法:首先,我们需要输入一些数据,并将其转换成对应的结构化数值;接着,基于训练好的回归系数,就可以对这些数值进行简单的回归计算,判定它们属于哪个类别;在这之后,我们就可以在输出的类别上做一些其他分析工作。