5.5 逻辑回归
上次讲到了感知机,感知机的最大缺点就是只能解决线性可分的问题,即只能使用直线分类的情况。所以,感知机也被称为简单感知机或者是单层感知机,虽然单层感知机很弱,但是多层感知机就是我们所熟悉的神经网络了,所以感知机也是学习分类的基础。
既然感知机不能用于线性不可分的问题,那么有什么方法能解决线性不可分问题呢?那么就引入了这个小节我们要讲的内容,即逻辑回归。虽然叫逻辑回归,实际上它做的是分类的问题,主要是用于二分类,即只分为两类,是某个事物或者不是某个事物。而逻辑回归不同于感知机之处在于,逻辑回归是把分类作为概率来考虑的。
5.5.1 sigmoid函数
我们首先给出sigmoid函数的表达式,即:
现在我们来探究一下sigmoid函数是如何起作用的,如图5-5-1所示:
图5-5-1
左边的坐标轴我们用于表示线性回归所预测出来的y值,而如右图所示的曲线为sigmoid函数,我们将线性回归预测出的y值当作自变量映射到sigmoid函数中,如图虚线所示。由于sigmoid的函数的取值范围在0-1之间,所以说我们可以利用这一特性把分类作为概率来考虑,这就是逻辑回归的原理。这就是为什么说逻辑回归名字叫回归却为什么干着分类的事,实际上是线性回归的一种映射,映射的效果是分类。现在我们将t换成线性回归的表达式
所以sigmoid函数表达式可以写成如下:
我们用上次讲感知机的时候讲的矩形分为横向或者是纵向来研究,如图5-5-2所示:
图5-5-2
我们可以把表达式写为这样:
1表示分类为横向,0表示分类为纵向。我们知道矩形有宽高两个特征,即宽(x1)高(x2),我们先对θ进行随意取个值便于理解,
这个不等式表示的范围就是分类为横向的范围,如图5-5-3所示:
图5-5-3
同理上图虚线的左边就被分类为纵向,我们称这条用于决策分类的线为决策边界。很明显这个虚线并不能正确的分类矩形的横纵向,所以我们的目标就是求得正确的θ。
5.5.2似然函数
如何来求参数θ的更新表达式呢?其实跟梯度下降迭代的表达式类似,不过损失函数的表达式不太一样,我们这里用到似然函数。假设P(y=1|x)表示图像为横向的概率,P(y=0|x)表示图像为纵向的概率,如图所示对于6个训练数据,我们期待的最大概率如表5-5-4所示:
表5-5-4
假设所有的训练数据都是互不影响、独立发生的,这种情况下整体的概率就可以用下面的联合概率来表示:
将概率全部乘起来的意思就是连续发生的概率,例如仍骰子,第一次仍1点,第二次仍2点,第三次仍3点的概率为三个六分之一连乘。回归的时候处理的是误差,所以要最小化,而现在考虑联合概率,我们希望尽可能大,这里的目标函数L(θ)被称为是似然函数。我们可以公式化的表示似然函数,这样即简洁又有利于我们之后的计算:
利用任何数的0次方都为1的特性,当y(i)带入1和0时,都能够正确的表示。
5.5.3对数似然函数
我们直接把似然函数作为损失函数进行微分会很难,首先它是联合概率。概率都是1以下,如果训练数据过多,那么概率乘法的值会越来越小,编程时也会出现精度问题。如何使之计算量变小呢?我们知道与乘法相比加法的计算量要小很多,那我们可不可以使之,转化为加法运算而不影响得出的结果呢?如果学习过高数,那我们就可能联想到对数运算,对数内的乘法可以拆分为加法运算,且对数是具有单调性的,如图5-5-5所示:
图5-5-5
那么x1<x2时,log(x1)< log(x2)也是成立的,那么似然函数L(θ1)< L(θ2)时,
也有logL(θ1)< log L(θ2),那么求似然函数的对数的最大值对应的θ也就是求似然函数最大值对应的θ。
既然这样,那我们只需要在似然函数等式两边加上log即可:
顺着我们利用对数将乘法转加法的思路,将对数似然函数变形:
对数似然函数的变形主要用到了,对数函数的特性
log(ab)=loga + logb
logab = bloga
所以我们将这个对数似然函数作为目标函数,像梯度下降法一样对各个参数θj求微分,得到θj的更新表达式即可。详细推导步骤如下图5-5-6所示:
图5-5-6
不过我们与梯度下降法不同的是,我们要以最大化为目标,迭代方向要与微分结果符号相同的方向移动,即:
这就是我们所要求得的目标θ的迭代公式了。
既然知道了θ的更新表达式,那么逻辑回归的代码就容易很多了:
import numpy as np
import matplotlib.pyplot as plt
# 读入训练数据
train = np.loadtxt('images2.csv', delimiter=',', skiprows=1)
train_x = train[:,0:2]
train_y = train[:,2]
# 参数初始化
theta = np.random.rand(3)
# 标准化
mu = train_x.mean(axis=0)
sigma = train_x.std(axis=0)
def standardize(x):
return (x - mu) / sigma
train_z = standardize(train_x)
# 增加 x0
def to_matrix(x):
x0 = np.ones([x.shape[0], 1])
return np.hstack([x0, x])
X = to_matrix(train_z)
# sigmoid 函数
def f(x):
return 1 / (1 + np.exp(-np.dot(x, theta)))
# 分类函数
def classify(x):
return (f(x) >= 0.5).astype(np.int)
# 学习率
ETA = 1e-3
# 重复次数
epoch = 5000
# 更新次数
count = 0
# 重复学习
for _ in range(epoch):
theta = theta - ETA * np.dot(f(X) - train_y, X) # θ的更新表达式
# 日志输出
count += 1
print('第 {} 次 : theta = {}'.format(count, theta))
# 绘图确认
x0 = np.linspace(-2, 2, 100)
plt.plot(train_z[train_y == 1, 0], train_z[train_y == 1, 1], 'o')
plt.plot(train_z[train_y == 0, 0], train_z[train_y == 0, 1], 'x')
plt.plot(x0, -(theta[0] + theta[1] * x0) / theta[2], linestyle='dashed')
plt.show()
迭代运行结果如下图:
截图和代码引自《白话机器学习的数学》