感知器模型分析及实现
感知器 本质上是一个线性模型,可以实现“线性分类”。
1. 感知器模型
假设输入样本 ,感知器实现线性分类的效果主要取决于权值 和偏置 的大小。
感知器包含一个线性组合器
和激活过程
,其结构如图
所示:
图1
From Fig 1.1 《Neural Networks and Learning Machines》
显然,由图 可以得到:
线性组合器的输出:
感知器的输出是对线性组合器的输出进行激活的结果:
常用的激活函数 可以是符号函数 ,本文采用的是:
一般地,为了更好地表示线性模型,将感知器模型描述为图
中的表示:
图2
From Fig 1.3 《Neural Networks and Learning Machines》
如图 所示,将感知器模型中的偏置 也看作是一个权值 ,那么:
,
为了便于用向量表示线性模型,将训练样本 进行扩展:
,
因此,图 中的感知器模型可以表示为:
线性组合器的输出:
感知器的输出:
【注】为了方便表示,本文后续部分直接使用 代替 ,用 代替
2. 几何意义
- 线性可分的情况——图
以输入样本 为例,在图 中有 个训练样本: ,其中 ,其余样本属于 。
感知器的参数
实际上是在
中确定了一条直线,如图
所示,能够将正例和负例进行区分的直线并不只有一种选择。
图3
感知器模型的几何意义(线性可分、线性不可分)
- 线性不可分的情况——图 “异或”问题
异或问题无法使用 条直线(分类面)来实现,或者在图 中使用 条直线来区分,或者使用图 中的不规则分类面。
图 中使用 条直线实现对数据集的分类,实际上就是采用多层感知器 的结构来实现。
3. 感知器模型的训练
感知器的训练过程,就是为了确定模型的参数
。以图
为例,也就是为了确定
中的某条直线的参数
。
图4
感知器的作用是,确定 中的分界面(直线)将 和 分隔开
线性组合器的输出:
感知器的(实际)输出值:
考虑训练样本集:
,其中 为(期望输出的)目标值
对于训练样本 ,其目标值
训练规则基于“误差修正学习”:
, 其中 为学习率
(1)若感知器的输出值 与目标值 一致时,说明分类面正确区分了当前的训练样本,不需要改变当前分类面,无需调整权值,
(2)若感知器的输出值 与目标值 不一致,说明分类面错误区分了当前的训练样本,需要调整权值的大小,来改变分类面的方向,使得当前样本 能够被正确划分
考虑具体样本时的“误差修正学习”规则:
- 当训练样本 被送入感知器
若输出值
(判定
,无误差),权值不会发生改变
若输出值
(判定
,有误差),权值更新为
图5
权值更新公式 中取 号的解释【当训练样本 被错误划分】
(1)当训练样本 被(旧的)蓝色分类面错误划分,取 号调整权值,才能让旧的分类面旋转到能够正确划分 的红色分类面的方向
(2)训练过程中,学习率 决定了权值变化的幅度(图中为 )
(3)已经正确划分的样本不会改变权值,训练过程只会针对图中 样本,通过一步步调整 (蓝色虚线向量) 来实现正确划分
- 当训练样本 被送入感知器
若输出值
(判定
,无误差),权值不会发生改变
若输出值
(判定
,有误差),权值更新为
图6
权值更新公式 中取 号的解释【当训练样本 被错误划分】
(1)当训练样本 被(旧的)蓝色分类面错误划分,取 号调整权值,才能让旧的分类面旋转到能够正确划分 的红色分类面的方向
(2)训练过程中,学习率 决定了权值变化的幅度(图中为 )
(3)已经正确划分的样本不会改变权值,训练过程只会针对图中 样本,通过一步步调整 (蓝色虚线向量) 来实现正确划分
4. 批处理训练过程
4.1 训练数据的规范化
由图
,假设分类面
可以实现正确的分类。那么对于训练样本
而言,如果
,则
,如果
,则
。
图7
训练数据的规范化过程【取自《模式分类》Fig 5.8】
图 (a) 为原始训练数据,黑色训练样本满足 ,红色训练样本满足
图 (b) 为规范化后的数据,红色点取反后,满足
训练数据的规范化
操作可以简化训练过程的描述:
(1)考虑
类中训练数据满足
(2)将
类中训练数据取反:
(3)此时,寻找权向量
只需要针对新的数据集
,计算
即可。
4.2 批处理感知器算法
定义感知器代价函数:
其中, 表示被错误划分的训练样本集。
若 被错分,那么 ;若 被错分,那么 。因此,选择 作为代价函数,被错分的训练样本越多,代价函数的值就越大。
如果 为空集,则表明所有训练样本都被正确分类,代价函数的值就越小。
采用梯度下降法求解:
因此,迭代公式为:
实现代码
import numpy as np
import matplotlib.pyplot as plt
def singleperceptron(xhat,target,eta,mode='seq'):
iteration = 0
flag = 1
if mode=='seq':
weight = np.random.random(xhat.shape[1])
y = np.zeros((len(xhat),1))
while flag:
# sequential
for i in range(len(xhat)):
tmp = np.dot(weight,xhat[i,:])
if tmp > 0:
y[i] = 1
else:
y[i] = 0
weight = weight - eta*(y[i]-target[i])*xhat[i,:]
print(weight)
iteration = iteration + 1
if np.sum(np.abs(y-target))==0:#此处的迭代终止条件只针对线性可分数据
flag = 0
if mode=='batch':
weight = np.random.random((xhat.shape[1],1))
xhat1 = xhat*np.where(target>0,1,-1)
print(weight.flatten())
while flag:
# batch
y1 = np.dot(xhat,weight)
y1 = np.where(y1>0,1,0)
t = np.dot(xhat1.T, np.abs(y1-target))
weight += eta*t
print(weight.flatten())
iteration = iteration + 1
if np.sum(t)==0:#此处的迭代终止条件只针对线性可分数据
flag = 0
weight = weight.flatten()/np.sum(weight)
return weight
def gen_lineardata(weight,interval):
y = -(weight[0]*interval + weight[2])/weight[1]
return y
def halfmoon(rad, width, dist, n_samp):
if n_samp%2 != 0:
n_samp += 1
data = np.zeros((3,n_samp))
rd = np.random.random((2,n_samp//2))
radius = (rad-width//2) + width*rd[0,:]
theta = np.pi*rd[1,:]
x1 = radius*np.cos(theta)
y1 = radius*np.sin(theta) + dist/2
label1 = np.ones((1,len(x1))) # label= 1 for Class 1
x2 = radius*np.cos(-theta) + rad
y2 = radius*np.sin(-theta) - dist/2
label2= np.zeros((1,len(x2))) # label= 0 for Class 2
data[0,:]=np.concatenate([x1,x2])
data[1,:]=np.concatenate([y1,y2])
data[2,:]=np.concatenate([label1,label2],axis=1)
shuffle_seq = np.random.permutation(np.arange(n_samp))
data_shuffle = data[:,shuffle_seq]
return data,data_shuffle
if __name__ == "__main__":
# dataset1: halfmoon
tnum = 8000
data,data_shuffle = halfmoon(20,15,3,tnum)
pos_data = data[:,0:tnum//2]
neg_data = data[:,tnum//2:tnum]
training_data = data_shuffle.T
tmp1 = training_data[0:tnum,0:2]
tmp2 = np.ones((tnum,1))
xhat = np.concatenate((tmp1,tmp2),axis=1)
target = training_data[0:tnum,2:]
interval = np.linspace(-30,50,100)
# dataset2: or
# data = np.array([[0,0],[0,1],[1,1],[1,0]])
# pos_data = data[0:1,:].T
# neg_data = data[1:,:].T
# target = np.array([1,0,0,0]).reshape(4,1)
# xhat = np.concatenate((data,np.ones((4,1))),axis=1)
# interval = np.linspace(-0.2,0.6,100)
# main
weight = singleperceptron(xhat,target,0.2)
print('sequential:',weight)
y = gen_lineardata(weight,interval)
plt.figure(1)
plt.plot(interval,y,'k')
plt.plot(pos_data[0,:],pos_data[1,:],'bo',markerSize=1)
plt.plot(neg_data[0,:],neg_data[1,:],'ro',markerSize=1)
plt.title('sequential')
weight = singleperceptron(xhat,target,0.2,'batch')
print('batch:',weight)
y = gen_lineardata(weight,interval)
plt.figure(2)
plt.plot(interval,y,'k')
plt.plot(pos_data[0,:],pos_data[1,:],'bo',markerSize=1)
plt.plot(neg_data[0,:],neg_data[1,:],'ro',markerSize=1)
plt.title('batch')
plt.show()
某一次运行结果
(1)半月形数据
sequential模式:
[0.01727369 5.59047961 0.9740486 ]
batch模式:
[-0.06142 1.06444655 -0.00302655]
(2)OR数据:
sequential模式:
[-0.22742997 -0.31955376 0.1226006 ]
batch模式:
[ 0.53262345 0.62096392 -0.15358737]
(3)线性不可分数据