背景
继续学习浙大机器学习的课程。终于完成了第一部分支持向量机的学习。现在正式进入第二部分人工神经网络了。
这部分一上来就学到了一个超级有意思的**感知器 (Perceptron)**算法。然后课程里有一个用matlab做的可视化的例子。我并没有找到这个例子的源代码,而且我也不用matlab,所以就想着自己用python写一个。
一开始还在纠结要不要花这个时间。感觉可能挺麻烦的,对学习课程也没啥帮助。不过实际做起来发现还挺简单的。
问题
依然是解决一个基本的二分类问题:有一组样本 { X i , Y i } , Y i = ± 1 \{X_i,Y_i\},Y_i = \pm1 {Xi,Yi},Yi=±1,找到一个 ω \omega ω 和 b b b,使得对于每一个样本,都有 Y i ⋅ ( ω X + b ) > 0 Y_i \cdot (\omega X + b) > 0 Yi⋅(ωX+b)>0。
注意这个问题和支持向量机解决的问题不完全相同。支持向量机是要找到最优的超平面,而感知器算法只要找到一个这样的平面就可以了。
算法
Rosenblatt 提出了感知器算法。网上搜了一下这个大佬居然是个心理学家!?
算法过程
我想做这个算法的可视化也是因为觉得这个算法太简单太优雅了。
先定义一些东西以便描述更方便。
定义增广向量
X
i
⃗
\vec{X_i}
Xi 为
[
X
i
1
]
,
y
i
=
1
[
−
X
i
−
1
]
,
y
i
=
−
1
\begin{bmatrix} X_i \\ 1 \end{bmatrix} , y_i = 1 \\ \\ \begin{bmatrix} -X_i \\ -1 \end{bmatrix} , y_i = -1
[Xi1],yi=1[−Xi−1],yi=−1
然后问题就可以这样描述:
寻找向量 ω = [ ω , b ] \omega = \begin{bmatrix} \omega, b \end{bmatrix} ω=[ω,b],使得 ω T X i ⃗ > 0 \omega^T \vec{X_i} > 0 ωTXi>0。
寻找的过程极其简单:
先随机取一个
ω
\omega
ω,然后把存在的每一个点(对应的增广向量)代入。如果
ω
T
X
i
⃗
≤
0
\omega^T \vec{X_i} \le 0
ωTXi≤0,那就修改
ω
\omega
ω,让
ω
\omega
ω 加上
X
i
⃗
\vec{X_i}
Xi。直到所有的点都满足
ω
T
X
i
⃗
>
0
\omega^T \vec{X_i} > 0
ωTXi>0为止。
可以证明只要样本是线性可分的,那感知器算法最后一定会收敛。这里我就先略去证明了。
python 可视化
概述
程序也挺简单的。先讲一下思路:
- 先随机生成两批点。而且要保证这些点是可以用一条直线分开的。
- 每一批点我都生成了50个。
- 我事先划定两条线 y = 2 x + 0.5 y = 2x + 0.5 y=2x+0.5 和 y = 2 x − 1 y = 2x - 1 y=2x−1(随便定的,没有什么特别理由)。我让第一类点都在 y = 2 x + 0.5 y = 2x + 0.5 y=2x+0.5之上,第二类点都在 y = 2 x − 1 y = 2x - 1 y=2x−1之下,以此保证两类点一定能被分开。
- 我还限定了x, y值的范围,以此保证最后生成的点以及坐标图都在一定范围之内。
- 使用
pyplot
画图。- 使用了动态模式,使得每次感知器算法更新后图像都能更新。
代码
代码也很短很简单。
import random
import matplotlib.pyplot as plt
import numpy as np
def generate_points_A() -> (list, list):
Y = [ random.uniform(0.5, 20) for _ in range(50) ]
X = [ random.uniform(0, (y - 0.5) * 0.5) for y in Y ]
return X, Y
def generate_points_B() -> (list, list):
Y = [ random.uniform(0, 20) for _ in range(50) ]
X = [ random.uniform((y + 1) * 0.5, 20) for y in Y ]
return X, Y
START = -3
END = 23
def update_plt(om1, om2, om3, x):
y = -om1 * x / om2 - om3 / om2
plt.cla()
plt.xlim(START, END)
plt.ylim(START, END)
plt.plot(x, y, color=(0.5, 0.5, 0.5))
plt.scatter(x1, y1, s=16., color=(0., 0.5, 0.0))
plt.scatter(x2, y2, s=16., color=(0., 0.0, 0.5))
plt.pause(0.5)
if __name__ == '__main__':
x1, y1 = generate_points_A()
x2, y2 = generate_points_B()
x = np.arange(START, END, 0.1)
sample_list = [ (s[0], s[1], 1) for s in zip(x1, y1) ] + [ (-s[0], -s[1], -1) for s in zip(x2, y2) ]
plt.ion()
flag = True
om = [ 0, 1, 0 ]
update_plt(om[0], om[1], om[2], x)
input()
while flag:
flag = False
for sample in sample_list:
if om[0] * sample[0] + om[1] * sample[1] + om[2] * sample[2] < 0:
om[0] += sample[0]
om[1] += sample[1]
om[2] += sample[2]
flag = True
update_plt(om[0], om[1], om[2], x)
print(f'运行结束,已收敛')
plt.ioff()
plt.show()
呈现效果
跑了两次,录屏然后做成了gif。还是非常有趣的~