一、概述
本文是《统计学习方法》第二章的读书笔记和手写代码实现。一套下来,耗时一天。希望能够坚持下来,系统地学习完整本书。本文引用原著讲解,加入了自己的理解。对书中算法采用Python实现。
二、感知机模型
感知机(perceptron)是二类分类的线性分类模型,其输入为特征向量x,输出为类别,取+1,-1二值。感知机将实例划分为正负两类的分离超平面,属于判别模型。感知机学习旨在求出训练数据进行线性划分的超平面,属于判别模型。为此,导入基于误分类的损失函数,利用梯度下降法对损失函数进行极小化,求得感知机模型。感知机学习算法具有简单而易于实现的优点,分为原始形式和对偶形式。同时,感知机算法是支持向量机和神经网络的基础。
2.1 感知机模型
对于感知机,由输入空间到输出空间的由如下函数给出:
其中,w和b感知机参数,w叫做权值,b叫做偏置。w*x表示w和x的内积。sign是符号函数,也即:
感知机是一种线性分类模型,属于判别模型。感知机模型的假设空间是定义在特征空间中的所有线性分类模型或者线性分类器,即函数集合{
f
∣
f
(
x
)
=
w
∗
x
+
b
f|f(x) = w*x+b
f∣f(x)=w∗x+b }。
2.2 感知机的学习策略
2.2.1 数据的线性可分性
对于一个给定的数据集 T T T={( x 1 , y 1 x_1,y_1 x1,y1),( x 2 , y 2 x_2,y_2 x2,y2),…,( x n , y n x_n,y_n xn,yn)},其中 y ∈ Γ y\in\Gamma y∈Γ={+1,-1},如果存在某个超平面 S S S能够将数据集的正实例点和负实例点完全正确地划分到超平面两侧,则称数据集 T T T为线性可分数据集;否则,称数据集 T T T为线性不可分。
2.2.1 感知机学习策略
假设数据集是线性可分的,我们需要制定感知机的学习策略。也就是定义损失函数。
一个朴素的想法是误分类点的总数。然而这样的损失函数不是参数
w
,
b
w,b
w,b的连续可导函数,不易优化。感知机采用的是误分类点到超平面
S
S
S的总距离。为此,首先需要写出任意一个样本点
x
0
x_0
x0到超平面
S
S
S的距离:
∣
∣
w
∣
∣
−
1
∗
∣
w
∗
x
0
+
b
∣
||w||^{-1}*|w*x_0+b|
∣∣w∣∣−1∗∣w∗x0+b∣,其中
∣
∣
w
∣
∣
||w||
∣∣w∣∣是
w
的
L
2
范
数
w的L_2范数
w的L2范数。
又因为对于误分类点来说,
−
y
i
(
w
∗
x
i
+
b
)
>
0
-y_i(w*x_i+b)>0
−yi(w∗xi+b)>0一定成立,因此可以将上式的绝对值去掉,得到任意误分类点到超平面
S
S
S的距离是:
−
∣
∣
w
∣
∣
−
1
∗
y
i
∗
(
w
∗
x
0
+
b
)
-||w||^{-1}*y_i*(w*x_0+b)
−∣∣w∣∣−1∗yi∗(w∗x0+b)
这样,假设超平面
S
S
S的误分类点集合为
M
M
M,那么所有误分类点到
S
S
S的距离之和可以表示为:
不考虑分母,因为分母一定是一个正的常数,对结果影响不大。那么可以得到感知机的损失函数:
显然,该损失函数
L
(
w
,
b
)
L(w,b)
L(w,b)是非负的。如果没有误分类点,损失函数值是0。而且,误分类点越少,误分类点离超平面越近,损失函数值就越小。
2.3 感知机的学习算法
感知机学习问题转化为求解上述损失函数式子的最优化问题。最优化问题方法很多,本文采用随机梯度下降法。本节叙述感知机学习的具体算法,包括原始形式和对偶形式,并证明线性可分训练数据集下感知机学习算法的收敛性。
2.3.1 感知机学习算法的原始形式
给定一个数据集,结合上述损失函数式可知,感知机学习算法是误分类驱动的,具体采用随机梯度下降法。首先,任意选取一个
w
0
,
b
0
w_0,b_0
w0,b0,利用梯度下降法不断极小化目标函数。极小化过程中不是一次使集合
M
M
M中所有的误分类点的梯度下降,而是随机选取一个误分类点使其梯度下降。其具体公式如下:
上述等式中负梯度前的希腊字母表示学习率,是一个0到1之间的数。
这种学习算法直观上有如下解释:当一个点被误分类,则调整
w
和
b
w和b
w和b的值,使分离超平面向该误分点的一侧移动,以减少该误分类点与超平面之间的距离。直到越过该误分类点使其被正确分类。这种算法是感知机的原始形式。感知机算法简单且易于实现。接下来,就利用一个例子,借助python实现这一算法。
2.3.1 感知机学习算法的python实现
下面是代码:
// A code block
var foo = 'bar';
import copy
training_set = [[(3, 3), 1], [(4, 3), 1], [(1, 1), -1]]
w = [0, 0]
b = 0
history = []
def check():
"""
check if the hyperplane can classify the examples correctly
:return: true if it can
"""
for li in training_set:
if (li[0][0]*w[0]+li[0][1]*w[1]+b)*li[1] <= 0:
update(li)
return False
# draw a graph to show the process
return True
def update(item):
"""
update parameters using stochastic gradient descent
:param item: an item which is classified into wrong class
:return: nothing
"""
global w, b, history
w[0] += 1 * item[1] * item[0][0]
w[1] += 1 * item[1] * item[0][1]
b += 1 * item[1]
history.append([copy.copy(w), b])
if __name__ == "__main__":
for i in range(1000):
if check(): break
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
x, y, x_, y_ = [], [], [], []
for p in training_set:
if p[1] > 0:
x.append(p[0][0])
y.append(p[0][1])
else:
x_.append(p[0][0])
y_.append(p[0][1])
plt.plot(x, y, 'bo', x_, y_, 'rx')
plt.axis([-6, 6, -6, 6])
plt.grid(True)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Perceptron Algorithm')
return line, label
print(history)
运行代码得到的结果是感知机迭代过程中所有的超平面 S S S的集合,最后的结果对应的是w=[1,1],y=-3。读者可以将这条直线自行带入样本点集合,可以发现这条直线完美地分开了正负样本点。当然,需要指出的是,这样的直线(超平面)并不唯一。此外,需要指出的是,若样本空间线性不可分,感知机算法不收敛,迭代结果会发生震荡。