感知机原理
-
感知机是二分类的线性模型,其输入是实例的特征向量,输出的是事例的类别,分别是+1和-1,属于判别模型。 假设训练数据集是线性可分的,感知机学习的目标是求得一个能够将训练数据集 正实例点和负实例点完全正确分开的分离超平面 。如果是非线性可分的数据,则最后无法获得超平面
-
点到线的距离
- 公式中的直线方程为 , 点 的坐标为 。
-
样本到超平面距离
- 我们假设超平面是 , 其中 , 样本点 到超平面的距离如下:
-
超平面(Hyperplanes)
- 超平面是在空间中的一个子空间。在2维空间中的超平面是一条线,在3维空间中的超平面是-一个平面。
感知机模型
- 定义 2.1 假设输入空间(特征空间)是 , 输出空间是 输入 表示实例的特征向量, 对应于输入空间(特征空间)的点; 输出 表示实例的类别。有输入空间到输出空间的如下函数称为感知机。其中和为感知机模型参数, 叫做权值(weight)或权值向量(weight vector), 叫作偏置(bias), 表示 和 的内积。 是符号函数,即:
- 感知机是一种线性分类模型,属于判别模型
- 感知机的几何解释是线性方程:
对应于特征空间中的一个超平面,其中是从超平面的法向量,是超平面的截距。
这个超平面将特征空间划分为两个部分。位于两部分的点(特征向量)分别被分为 正、负两类 。
因此,超平面S成为分离超平面(separating hyperplane),如图2.1所示。
感知机学习,由训练数据集(实例的特征向量及类别) 其中 , 求得感知机模型 , 即求得模型参数 感知机预测, 通过学习得到的感知机模型, 对于新的输入实例给出其对应的输出类别。
- 证明为什么w是直线(高维空间下为超平面)的法向量
- 证明为什么w是直线(高维空间下为超平面)的法向量
感知机的学习策略
损失函数
-
损失函数的一个自然选择是误分类点的总数, 但是这样损失函数不是参数和的连续可到函数, 不易优化。 损失函数的另一个选择是误分类点到超平面的总距离, 这是感知机所采用的。为此,首先写出输入空间 中任一点 到超平面S的距离 ,这里, 是w的 范数。其次, 对于误分类的数据 来说, 成立。因为当 时, , 而当 时, 。因此, 误分类点 到超平面S的距离是 这样, 假设超平面的误分类点集合为 那么所有误分类点到超平面S的总距离为 不考虑 , 就得到感知机学习的损失函数。
-
为什么不考虑?? 有人说 是个定值, 但是我觉得平面不唯一, 这个值肯定也会变。通过参考他人观点结合思考, 觉得原因可以列为以下两点。
- 不影响 正负的判断, 即不影响学习算法的中间过程。因为感知机学习算法是误分类驱幼的, 这里需要注意 的是所谓的 “误分类驱动" 指的是我们只需要判断 的正负来判断分关的正确与否, 而 并不影响正负值的 判断。所以 对感知机学习算法的中间过程可以不考虑。
- 不影响感知机学习算法的最终结果。因为感知机学习算法最终的终止条件是所有的输入都被正确分关, 即不存在误分类的点。则此时损失函数为 0 . 对应于 , 即分子为 0 . 则可以看出 对最终结果也无影响。
综上所述, 即使忽略 , 也不会对感知机学习算法的执行过程产生任何影响。反而还能简化运算, 提高算法执行效率。
感知机学习算法
原始形式
- 算法 2.1 (感知机学习算法的原始形式)
输入: 训练数据集 ; 学习率 ;
输出: , ; 感知机模型
- 选取初值 ,
- 在训练集中选取数据
- 如果
- 转至,直至训练集中没有误分类点。
- 当一个实例点被误分类,及位于分离超平面的错误一侧时,则调整w, b的值,使分离超平面向该误分类点的一侧移动,以减少该误分类点与超平面的距离,直至超平面越过该误分类点使其被正确分类。
对偶形式
-
算法 2.2 (感知机学习算法的对偶形式) 输入:线性可分的数据集, 其中 ; 学习率 输出:感知机模型 其中
- 在训练集中选取数据
- 如果
- 转至直到没有误分类数据。
对偶形式中训练实例仅以内积的形式出现,为了方便,可以预先将训练集中实例间的内积计算出来并以矩阵形式存储,这个矩阵就是所谓的Gram矩阵(Gram matrix)
-
问题
-
Gram矩阵是如何计算的?
-
对偶形式求得的浮点数如何处理? 不用必须是整数,浮点数也可以
-
怎么理解?? 表示的是第 个样本点被误判的次数,而感知机一般形式中的 其实就是每个样本点被误判的次数乘以的累加和,也就是。在每次迭代的时候,表示的是到当前为止,第 个样本点被误判的次数,这个很重要。因为要反复让样本点中的输入两两相乘(这个在一般形式中计算的时候也要这样,自己模拟一遍就发现了),所以提前搞成一个矩阵存起来,类似于平时刷算法题说的打表。所以两个形式本质上是一样的,不过把用另外一种形式表示。
-
思考
为训练集大小,为特征数量
- 对偶形式:扫一遍,计算每条数据在之前被加了几个()次(当取时,相当于第i组数据的梯度被加了几次,找到一个误分点后直接加上,而不是每次加),因为已经被提前计算在Gram矩阵中,所以每次是,那么扫一遍就是。
- 原始形式:每次计算,计算此内积复杂度为
所以看下来,选择哪种计算方法取决于训练集和特征数量的大小。
代码实现
原始形式
- 对于输入空间,感知机通过以下函数将其映射至 的输出空间
- 对于所有的错分类点 , 都有 , 因此我们可以定义如下的损失函数作为优化准则:
- 通过求解损失函数的梯度,
- 很容易就可以得到感知机学习算法的原始形式
- 整个算法流程如下:
- 选取初值
- 在训练集中任意选取点
- 如果 则按照式更新
- 重复直到没有被误分的点
-
from __future__ import division import random import numpy as np import matplotlib.pyplot as plt def sign(v): if v>=0: return 1 else: return -1 def train(train_num,train_datas,lr): w=[0,0] b=0 for i in range(train_num): x=random.choice(train_datas) x1,x2,y=x if(y*sign((w[0]*x1+w[1]*x2+b))<=0): w[0]+=lr*y*x1 w[1]+=lr*y*x2 b+=lr*y return w,b def plot_points(train_datas,w,b): plt.figure() x1 = np.linspace(0, 8, 100) x2 = (-b-w[0]*x1)/w[1] plt.plot(x1, x2, color='r', label='y1 data') datas_len=len(train_datas) for i in range(datas_len): if(train_datas[i][-1]==1): plt.scatter(train_datas[i][0],train_datas[i][1],s=50) else: plt.scatter(train_datas[i][0],train_datas[i][1],marker='x',s=50) plt.show() if __name__=='__main__': train_data1 = [[1, 3, 1], [2, 2, 1], [3, 8, 1], [2, 6, 1]] # 正样本 train_data2 = [[2, 1, -1], [4, 1, -1], [6, 2, -1], [7, 3, -1]] # 负样本 train_datas = train_data1 + train_data2 # 样本集 w,b=train(train_num=800,train_datas=train_datas,lr=0.01) plot_points(train_datas,w,b)
对偶形式
- 简而言之,感知机的对偶形式就是把对 的学习变成了对 的学习,原始形式中, 在每一轮迭代错分时都需要更新, 而采用对偶式时,对于某一点发生错分时,我们只需要更新其对应的 即可,最后按照式即可一次计算出 . 同时我们上述步骤中的 可以看出, 仅以内积的形式出现, 因此我们可以是先计算出的矩阵存 储起来,这样正式训练时只需要查表就可以得到 的值, 这样做可以方便程序的优化, 提高运算的速度。 原始形式和对偶形式对参数b的处理是相同的。 式为
-
from __future__ import division import random import numpy as np import matplotlib.pyplot as plt def train(train_num,train_datas,lr): w=0.0 b=0 datas_len = len(train_datas) alpha = [0 for i in range(datas_len)] train_array = np.array(train_datas) gram = np.dot(train_array[:,0:-1] , train_array[:,0:-1].T) for idx in range(train_num): tmp=0 i = random.randint(0,datas_len-1) yi=train_array[i,-1] for j in range(datas_len): tmp+=alpha[j]*train_array[j,-1]*gram[i,j] tmp+=b if(yi*tmp<=0): alpha[i]=alpha[i]+lr b=b+lr*yi for i in range(datas_len): w+=alpha[i]*train_array[i,0:-1]*train_array[i,-1] return w,b,alpha,gram def plot_points(train_datas,w,b): plt.figure() x1 = np.linspace(0, 8, 100) x2 = (-b-w[0]*x1)/(w[1]+1e-10) plt.plot(x1, x2, color='r', label='y1 data') datas_len=len(train_datas) for i in range(datas_len): if(train_datas[i][-1]==1): plt.scatter(train_datas[i][0],train_datas[i][1],s=50) else: plt.scatter(train_datas[i][0],train_datas[i][1],marker='x',s=50) plt.show() if __name__=='__main__': train_data1 = [[1, 3, 1], [2, 2, 1], [3, 8, 1], [2, 6, 1]] # 正样本 train_data2 = [[2, 1, -1], [4, 1, -1], [6, 2, -1], [7, 3, -1]] # 负样本 train_datas = train_data1 + train_data2 # 样本集 w,b,alpha,gram=train(train_num=500,train_datas=train_datas,lr=0.01) plot_points(train_datas,w,b)