问题
为了可视化方便,我们假设数据是二维的,如图1所示。
蓝色圆点和红色叉号分别代表不同的两类,这堆数据数据可以用一条黑色直线把这两类完美分开,那么这堆数据就是线性可分的。
线性不可分数据如图2
那么我们的问题就是,如何从线性可分数据中学习出一个比较好的模型用于分类。
模型
设有数据集
,其中
是第
个训练数据,
为对应的标签,则感知机模型为
其中
为需要学习的感知机参数。
感知机的几何解释就是学出一个
为法向量,
为截距的超平面,超平面两边分别对应于
的数据,这个超平面就是图一里将两类数据分开的黑色直线。
损失函数
用误分类率做损失函数是很自然的一个想法,但是误分类点数这个东西不可导,我们需要找一些光滑可导性质的损失函数便于利用梯度进行优化。因此选用所有误分类点到超平面的距离作为损失函数,如下
先来解释上面公式,
代表误分类的点的集合,
代表点
到超平面
的距离,并且对于误分类点有如下公式
合并起来就是对于所有误分类点
,有
,因为损失函数要求非负,所以累加和前面给个符号,我们最终的目标就是要损失函数。
虽然
与
有关,但是去掉可以使优化更加简单,并且不改变损失函数表示的意思,所以我们舍去
项。
最后我们记
优化
梯度下降法求解,分别求其梯度
这里用随机梯度下降法 (SGD),每次选取一个误分类点进行更新模型,更新公式如下
其中
为学习率。
接下来我们都假设
,并且从几何的角度来解释
的更新过程。因为
可以并入
构成
,对所有的
,
,所以这里只考虑
,如图3
- 当
,对于误分类点,意味着
为了让
,也即它们之间的夹角
为锐角,我们需要将
的方向与
靠近,如图3上半部分,这时候我们得到
。
2. 当
,同理,意味着现在
与
为锐角,我们需要它们之间变为钝角,因此得到
。
合并1.2.,我们得到
的更新公式:
,和上面吻合。
收敛性
设数据集线性可分,则存在一个超平面把数据集分开,记此时的参数为
(f表示final),这里把
合并进去了,那么有
。假设第
次更新时模型权重为
,则
看起来更新后的
与
内积变大,也就越靠近,但是除了向量间夹角变小造成内积变大以外,还有可能由于模长变化造成内积增大,下面我们证明模长变化是有限的。
由于感知机模型是只在分类错误时才更新,因此必有
,因此上式可以写为
因为
,平方后可消去。从上式看到更新后
模长变化是有限制的。这里增长的幅度与数据的模长有关,是不是能够说明在训练前对整个数据集进行归一化预处理后有利于模型的收敛呢?
上面说明了每次更新,模型是向最终结果不断靠近的,接下来给出模型更新次数的一个上界,也就是最多要更新多少次。
记
,
,则上面两个公式可以得到
假设最大更新次数为
,结合上面两个公式,有
对偶形式
因为更新公式为
,如果
相对于误分类点
更新了
次,那么对
的总的梯度为
。因此对于所有的误分类点,设
初始值为0,最终
可以表示为
。对偶形式的整体流程和原始的感知机学习算法是基本一致的,只是把
替换成了
,对偶形式的感知机模型变成了
那么对偶形式相较于原始形式有什么优点呢?原始的形式只要没收敛,就需要对数据集中每个数据点都计算
,假设数据维度很高,那么这个计算是相当耗时的,并且
在学习过程中是不断更新的,没有办法预先计算
。
使用对偶形式后,我们可以预先计算所有数据点之间的内积,构成一个
的矩阵,叫做Gram矩阵。在对偶形式的感知机中需要计算内积
时直接查表取出来就可以了,相当于用空间换时间。
代码实现
原始形式
import numpy as np
# 准备数据
# 数据格式(x1, x2, y)
T = np.array([
[3, 3, 1],
[4, 3, 1],
[1, 1, -1],
])
# 初始化
w = np.zeros((1, 2))
b = 0
# 学习算法
i = 0
while i < T.shape[0]:
x = T[i, :2].reshape((-1, 1))
y = T[i, -1]
if y * (w @ x + b) <= 0:
# 误分类
# 求梯度
grad_w = -y * x.reshape(1, -1)
grad_b = -y
# 梯度下降更新
w -= grad_w
b -= grad_b
print('[Error: x{}][new_w:{}][new_b:{}]'.format(i, w, b))
# 出错了,从头开始验证数据集
i = 0
else:
i += 1
对偶形式
import numpy as np
# 准备数据
# 数据格式(x1, x2, y)
T = np.array([
[3, 3, 1],
[4, 3, 1],
[1, 1, -1],
])
# 初始化
a = np.zeros(T.shape[0])
b = 0
lr = 1
# 求Gram Matrix
gram_matrix = T[:, :-1] @ T[:, :-1].T
# 学习算法
i = 0
while i < T.shape[0]:
if T[i, -1] * ((a * T[:, -1] * gram_matrix[i, :]).sum() + b) <= 0:
# 误分类
# 更新
a[i] += 1
b += T[i, -1]
i = 0
print(a, b)
else:
i += 1
参考资料
- 机器学习基石. 林轩田, 国立台湾大学.
- 统计学习方法(第2版). 李航.