感知机(perception)

@TOC

感知机(Perception)

一、基本介绍

感知机是二元分类线性模型,输入为特征向量,输出为实例的类别,取值为+1-1

对于线性可分的输入实例,感知机可以通过梯度下降法学习一个分离超平面,将实例分开

1. 数学定义

假设输入空间(特征空间)是 ,输出空间是。输入表示实例的特征向量,对应于输入空间(特征空间)的点;输出表示实例的类别。由输入空间到输出空间的如下函数:

称为感知机,其中为感知机模型参数,叫做权值(weight)或权值向量(weight vector),为偏置(bias),为内积,为符号函数,

2. 图形表示

如图所示,圆和叉分别是特征空间中的点,从图中可以看出他们是线性可分的,线性方程为一个超平面,w是超平面的法向量,b是超平面的截距,超平面将空间中的点分为正负两类

注意:为什么图中的是超平面的法向量呢?

首先我们知道两个向量内积,如果两个向量垂直,那么内积就是0,也就是说,可以得到,图中的也是如此

每一个点都是一个二维的数据,任取一个点,包括,在计算过程中我们将的内积展开,也就是说我们得到,我们忽略掉,也就是说,所以得到。那么是什么呢?表示超平面沿着移动的距离。

3. 线性可分

定义:给定一个数据集,其中,如果存在某个超平面能够将数据集的正实例点和负实例点完全正确地划分到超平面的两侧,即对所有的的实例,有,对所有的实例,有,则称数据集为线性可分数据集(linearly separable data set); 否则,数据集线性不可分。

如下图所示,左边的数据集可以找到一个超平面将其分开,所以是线性可分的;右边数据集找不到一个超平面将其分开,所以是线性不可分的

二、损失函数

一共有两种定义损失函数的方法

  • 误分类点的总数:损失函数不是的连续可导函数,不易优化
  • 误分类点到超平面的总距离:采用此方法

损失函数

空间上的某点到超平面的距离

对于正确分类点时,;时,,即
对于误分类点,即,设误分类点的集合为,则所有误分类点到超平面的总距离为,为了计算简便,这里将分母去掉,也就是说感知机的最终的损失函数为

损失函数是非负的,当没有误分类点时,损失函数为0

三、感知机的求解

1. 原始形式

(1) 求解过程

首先给出具体的求解过程

给定一个数据集,其中,学习率

感知机模型:

损失函数:

  • 选取初值
  • 在训练集中选取数据
  • 判断是否为误分点,即如果,更新
  • 转到第二步,直到没有误分类点为止

直观解释:如下图所示,当有一个实例点被误分类时,即位于超平面的错误一侧时,则调整的值,使超平面向该误分类点的一侧移动,以减少该误分类点与超平面的距离,直到超平面越过该误分类点使其被正确分类,如下图所示

  • black arrow represents
  • black line is decision boundary
  • red points are positive, blue are negative
  • learning rate
  • if a linear classifier exists for the given dataset, then perceptron can converge

注意

  1. 在上述更新的时候需要计算梯度,即对于,分别对进行求导就可以得到更新的参数形式

  1. 在更新时采用随机梯度下降法。通过上述求解步骤我们发现在极小化过程中不是对所有误分类点进行求和然后梯度下降,而是一次选取一个误分类点执行梯度下降,所以是SGD

(2) 具体实例

这里引用统计学习课本上的一个实例

如图:正实例点为,负实例点是,试用原始形式求感知机模型,其中

求解

  1. 初始化
  2. 误分类
  3. 更新

  1. 正确分类
  2. 正确分类
  3. 误分类
  4. 更新

  1. 如此迭代直到没有误分类点为止

也就是说具体的迭代过程是:首先找是不是误分类点,是的话更新直到他变为正确分类点为止,然后看一看是不是误分类点,不是的话看看,如果是误分类点,那么在更新直到不是误分类点,此时到了关键的一步:不要停止,在从重新计算一遍看看是否有误分类点,假设在更新的时候超平面进行了移动,那么有可能之前本来已经处于正确分类的点又变回了误分类,哪有人会问,这样后面会影响前面的话他会收敛吗,答案是肯定的,因为已经有人证明当数据是线性可分的时候,必然会进行收敛

注意:在迭代过程中误分类点是随机选取的,也就是说随着选取顺序的不同,最终的超平面也不同,这很容易理解,因为从图上我们可以直观的看出,能够将两种类型的点区分开的超平面不止一个

下面给出迭代过程

2. 对偶形式

首先我们看一下上述损失函数对的梯度

上述数值就是每次我们进行更新时的梯度大小,可以观察到每次更新的结果与有关,当一个点误分类时我们就将当前的进行一次修改,当最终找到超平面时,就会利用修改

为了更直观的理解,先给出感知机的对偶形式最终的结果

即如果设置,那么在进行迭代之后会得到

其中表示样本点在更新时被用了次,越大代表这个样本点被经常使用,说明该样本点在超平面附近,当超平面进行微小的变化该样本点就会被误分类,我们知道了以后,令,直接给出感知机模型的形式

感知机模型:

感知机的对偶形式在更新时并不是更新,而是更新

算法流程

  • 令所有,即
  • 在训练集中选取数据
  • 判断是否为误分点,即如果,则,因为,所以,更新
  • 转到第二步,直到没有误分类点为止

所以相比于原始形式,对偶形式并没有本质的区别,而是可以将样本点的特征向量以内积的形式存在于感知机的对偶形式算法中,如果事先计算好所有的内积,即Gram矩阵,就可以大大加快计算速度

四、程序实例

下面用python展示一个感知机原始形式的计算实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# 利用Python实现感知机算法的原始形式
import numpy as np
import matplotlib.pyplot as plt

def createData():
samples = np.array([[1, 2], [2, 3], [3, 3], [2, 1], [3, 2]])
labels = [1, 1, 1, -1, -1]
return samples, labels

class Perception:
def __init__(self, x, y, lr=1):
self.x = x
self.y = y
self.l_rate = lr
self.w = np.random.rand(x.shape[1], 1)
self.b = 0
self.numSimples = x.shape[0]
self.numFeatures = x.shape[1]

def sign(self, w, b, x):
y = np.dot(x, w) + b # x .w + b
if y >= 0:
y = 1
else:
y = -1
return y

def update(self, label_i, data_i):
tmp = label_i * self.l_rate * data_i # w = w + n yx
tmp = tmp.reshape(self.w.shape)
self.w = tmp + self.w
self.b = self.b + label_i * self.l_rate # b = b + n y

def train(self):
isFind = False
step = 1
while not isFind:
count = 0
for row in range(self.numSimples):
simY = self.sign(self.w, self.b, self.x[row, :])
if simY * self.y[row] <= 0: # 如果是一个误分类实例点
print("------------------------step{}----------------------".format(step) )
print('误分类点为:', self.x[row, :], '此时的w和b为:', self.w[0], self.w[1], self.b)
count += 1
step += 1
self.update(self.y[row], self.x[row])
if count == 0:
print("训练完成:")
print('最终训练得到的w和b为:', self.w[0], self.w[1], self.b)
isFind = True
return self.w, self.b


def plot_figure(samples, w, b):
plt.figure(1)
plt.title("Perception Algorithm", size=14)
plt.xlabel("x1", size=14)
plt.ylabel("x2", size=14)
x1 = np.linspace(0, 5, 100)
x2 = expression(x1, w, b)
plt.plot(x1, x2, color='r', label='data')

plt.scatter(samples[0][0], samples[0][1], s=50)
plt.scatter(samples[1][0], samples[1][1], s=50)
plt.scatter(samples[2][0], samples[2][1], s=50)
plt.scatter(samples[3][0], samples[3][1], s=50, marker='x')
plt.scatter(samples[4][0], samples[4][1], s=50, marker='x')
plt.savefig('perception.png', dpi=75)
plt.show()

def expression(x1, w, b):
x2 = (-b - w[0] * x1) / w[1]
# 注意在此,把x0,x1当做两个坐标轴,把x1当做自变量,x2为因变量
return x2

if __name__ == '__main__':
np.random.seed(2)
samples, labels = createData()
myperceptron = Perception(x=samples, y=labels, lr=1)
weights, bias = myperceptron.train()
Picture = plot_figure(samples, weights, bias)

'''输出
------------------------step1----------------------
误分类点为: [2 1] 此时的w和b为: [0.4359949] [0.02592623] 0
------------------------step2----------------------
误分类点为: [1 2] 此时的w和b为: [-1.5640051] [-0.97407377] -1
------------------------step3----------------------
误分类点为: [3 2] 此时的w和b为: [-0.5640051] [1.02592623] 0
------------------------step4----------------------
误分类点为: [1 2] 此时的w和b为: [-3.5640051] [-0.97407377] -1
------------------------step5----------------------
误分类点为: [2 3] 此时的w和b为: [-2.5640051] [1.02592623] 0
------------------------step6----------------------
误分类点为: [2 1] 此时的w和b为: [-0.5640051] [4.02592623] 1
训练完成:
最终训练得到的w和b为: [-2.5640051] [3.02592623] 0
'''