1感知机算法的对偶形式
笔记1中已经详细叙述过感知机学习算法的原始形式,详情可见统计学习笔记1:感知机学习算法的原始形式
对偶形式的基本想法是将w和b表示为实例和标签
的线性组合的形式,通过求解其系数来间接求得w和b。学习过程中不断修改w和b,设修改n次,则w关于(
,
)的增量分别是
和
,这里
,所以最后学习得到的w,b可以分别表示为
故感知机算法的对偶形式实现需要以下步骤:
(1)
(2)在训练集中选取数据(,
)
(3)如果,则需要更新
和b的值如下
一直迭代直至没有误分类数据。
上文可以看出判断误分类的依据中存在的内积形式,方便起见,可以预先将训练集中各个实例之间的内积计算出来并以矩阵形式存储,这就是所谓的Gram矩阵
2感知机学习算法对偶形式python代码实现
2.1 问题描述
问题与笔记1一样来源于李航老师《统计学习方法》中的例2.2。正样本点为,
,负样本点是
,试用感知机学习算法对偶形式求感知机模型。
2.2数据导入
在数据导入的过程中可以顺便计算出Gram矩阵方便后续使用,代码如下
def loadData():
data=np.array([[3,3],[4,3],[1,1]]) #导入数据
labels=np.array([1,1,-1]) #导入标签
gram=np.ones((len(labels),len(labels)))
for i in range(len(labels)):
for j in range(len(labels)):
gram[i][j]=int(np.dot(data[i,:],data[j,:])) #求各个实例之间的内积,存储在gram矩阵中
return data,labels,gram
此时打印data与gram矩阵可得
data=[[3 3]
[4 3]
[1 1]]
gram=[[18. 21. 6.]
[21. 25. 7.]
[ 6. 7. 2.]]
2.3 初始化
与感知机原始形式不同,初始化中权重w在对偶形式中被替换为,设学习率
,可得初始化代码如下
class PLA:
def __init__(self,data,labels,gram,a=1): #初始化
self.data=data #实例的数据
self.labels=labels #实例的标签
self.gram=gram #提前计算样本的gram矩阵
self.a=a #a为学习率
self.alpha=np.zeros((data.shape[0],1)) #初始权重为0
self.bias=0; #bias为偏置
self.numdata=data.shape[0] #样本数
self.numfeatures=data.shape[1] #特征数
2.4写sign函数
由于误分类点的判断条件写在了后文模型训练的部分,故此处的sign函数就为最基础的符号函数,代码如下
def sign(self,flag):
if flag>0:
return 1
else:
return -1
2.5更新参数
参照上文步骤(3)所示的更新参数 ,代码如下
def update(self,i,labels_i):
self.alpha[i,:]+=1 #alpha=alpha+eta(学习率)
self.bias+=labels_i #b=b+eta*yi
由于此案例中学习率,故更新
时直接加1了。
2.6模型训练
将样本点代入模型进行迭代运算,直至不再出现误分类点,即感知机模型训练完成。对偶形式相对于原始形式,其误分类点的判断形式计算量较大,可以分步计算括号内的各项,大不了多设几个变量,代码如下
def trainPLA(self):
bMisClassify=True
while bMisClassify:
mMisClassifyNum=0
for i in range(self.numdata):
flag=0
for j in range(self.numdata):
flag=flag+self.alpha[j,:]*self.labels[j]*self.gram[j][i]
flag=self.labels[i]*(flag+self.bias) #计算判别条件
if self.sign(flag)==-1:
mMisClassifyNum+=1
self.update(i,self.labels[i])
if mMisClassifyNum==0:
bMisClassify=False
print('The PLA Training has finished! The alpha is :\n',self.alpha)
return self.alpha,self.bias
返回值为以及偏置b,计算过程中可以直接调用在数据初始化过程中已经计算过的Gram矩阵。
2.7可视化
可视化中需要将转换成w,其转换公式为
,要注意的是其中涉及的矩阵运算较多,需要保证各个矩阵的维度满足运算要求,当不满足要求时需要将矩阵进行类似转置之类的操作,代码如下。
class Plot:
def __init__(self,x,y,alpha,bias):
self.alpha=alpha
self.bias=bias
tmp=np.zeros((1,x.shape[1]))
w=np.zeros((x.shape[1],1))
plt.figure(1)
plt.title('Perceptron Learning Algorithm')
plt.xlabel('X0')
plt.ylabel('X1')
xdata=np.arange(0,7) #x轴范围
for i in range(x.shape[0]):
tmp+=self.alpha[i][0]*y[i]*x[i,:]
w=tmp.T #对tmp进行转置操作
ydata=-(self.bias+w[0][0]*xdata)/w[1][0]
plt.plot(xdata,ydata,c='r')
for i in range(len(y)):
if y[i]==1:
plt.scatter(x[i,0],x[i,1],c='g',marker='o')
else:
plt.scatter(x[i,0],x[i,1],c='b',marker='x')
plt.savefig('Blog1.png')
plt.show()
2.8整体代码
添加主程序后,整体代码如下
import numpy as np
import matplotlib.pyplot as plt
def loadData():
data=np.array([[3,3],[4,3],[1,1]]) #导入数据
labels=np.array([1,1,-1]) #导入标签
gram=np.ones((len(labels),len(labels)))
for i in range(len(labels)):
for j in range(len(labels)):
gram[i][j]=int(np.dot(data[i,:],data[j,:])) #求各个实例之间的内积,存储在gram矩阵中
return data,labels,gram
class PLA:
def __init__(self,data,labels,gram,a=1): #初始化
self.data=data #实例的数据
self.labels=labels #实例的标签
self.gram=gram #提前计算样本的gram矩阵
self.a=a #a为学习率
self.alpha=np.zeros((data.shape[0],1)) #初始权重为0
self.bias=0; #bias为偏置
self.numdata=data.shape[0] #样本数
self.numfeatures=data.shape[1] #特征数
def sign(self,flag):
if flag>0:
return 1
else:
return -1
def update(self,i,labels_i):
self.alpha[i,:]+=1 #alpha=alpha+eta(学习率)
self.bias+=labels_i #b=b+eta*yi
def trainPLA(self):
bMisClassify=True
while bMisClassify:
mMisClassifyNum=0
for i in range(self.numdata):
flag=0
for j in range(self.numdata):
flag=flag+self.alpha[j,:]*self.labels[j]*self.gram[j][i]
flag=self.labels[i]*(flag+self.bias) #计算判别条件
if self.sign(flag)==-1:
mMisClassifyNum+=1
self.update(i,self.labels[i])
if mMisClassifyNum==0:
bMisClassify=False
print('The PLA Training has finished! The alpha is :\n',self.alpha)
return self.alpha,self.bias
class Plot:
def __init__(self,x,y,alpha,bias):
self.alpha=alpha
self.bias=bias
tmp=np.zeros((1,x.shape[1]))
w=np.zeros((x.shape[1],1))
plt.figure(1)
plt.title('Perceptron Learning Algorithm')
plt.xlabel('X0')
plt.ylabel('X1')
xdata=np.arange(0,7) #x轴范围
for i in range(x.shape[0]):
tmp+=self.alpha[i][0]*y[i]*x[i,:]
w=tmp.T #对tmp进行转置操作
ydata=-(self.bias+w[0][0]*xdata)/w[1][0]
plt.plot(xdata,ydata,c='r')
for i in range(len(y)):
if y[i]==1:
plt.scatter(x[i,0],x[i,1],c='g',marker='o')
else:
plt.scatter(x[i,0],x[i,1],c='b',marker='x')
plt.savefig('Blog1.png')
plt.show()
if __name__=='__main__':
data,labels,gram=loadData()
print(data)
print(gram)
myPLA=PLA(data,labels,gram)
alpha,bias=myPLA.trainPLA()
Plot(data,labels,alpha,bias)
运行后输出和权重矩阵w的结果为
The PLA Training has finished! The alpha is :
[[2.]
[0.]
[5.]]
w=[[1.]
[1.]]
绘制出的分离超平面如下图所示
与笔记1中得到的分离超平面一致,完全将正负实例点分离,具体迭代过程见书中下表
3学习心得
感知机的对偶形式相较于感知机的原始形式更具有一般性,两者的大致思路相近,故在掌握感知机学习算法的原始形式之后,仅需在原始形式代码的基础上稍加修改即可。本章学习了感知机模型,它是根据输入实例的特征向量x对其进行二分类的线性分类模型,将学习的过程转化为使用梯度下降法不断极小化损失函数 。下一章就要学习k近邻法了,继续加油!