Python实现感知机(Perceptron)

1.感知机回归概念

感知机(Perceptron)是二分类的线性分类模型,其输入是实例的特征向量,输出是实例的类别,取+1及-1二值。感知机是在1957年由Rosenblatt提出,今天看来它的分类模型在大多数时候泛化能力不强,但是它的原理却值得好好研究。因为研究透了感知机模型,学习支持向量机的话会降低不少难度。同时如果研究透了感知机模型,再学习神经网络,深度学习,也是一个很好的起点。

假设输入空间是χRnχ⊆R^n,输出空间是γ=(+1,1)γ=(+1,−1)。输入χXχ∈X表示实例的特征向量,对应于输入空间的点;输出yγy∈γ表示实例的类别。由输入空间到输出空间的如下函数:

f(x)=sign(wx+b)f(x)=sign(wx+b)

其中,ww(权值)和bb(偏置)为感知机模型的参数,wxwx表示wwxx的内积,sign是符号函数,即:
sign(x)={+1x01x<0sign\left( x\right) =\begin{cases}+1 &x\geq 0\\-1 &x<0 \end{cases}

感知机学习策略

假设训练数据集是线性可分的,感知机学习的目标就是求得一个能够将训练集正实例点和负实例点完成正确分开的分离超平面。为了找出这样的超平面,即确定感知机模型参数wwbb,需定义损失函数并将损失函数极小化。损失函数使用误分类点到超平面SS的总距离,因此输入空间RnR^n​中任何一点x0x_0​到超平面SS的距离为:

1wwx+b\frac{1}{∥w∥}|wx+b|

在这里,w∥w∥ww的L2范数。对于误分类的数据(xi,yi)(x_i,y_i)来说,yi(wxi+b)>0−y_i(wx_i+b)>0
成立。因为当wxi+b>0wx_i+b>0时,yi=1y_i=−1,而当wxi+b<0wx_i+b<0时,yi=+1y_i=+1,因此,误分类点xix_i到超平面SS的距离是:

1wyi(wxi+b)−\frac{1}{∥w∥}y_i(wx_i+b)

这样,假设超平面SS的误分类点集合为MM,那么所以误分类点到超平面SS的总距离为:

1wxiMyi(wxi+b)−\frac{1}{∥w∥}∑_{x_i∈M}y_i(wx_i+b)

不考虑1w\frac{1}{∥w∥},就得到感知机学习的损失函数:

L(w,b)=xiεnyi(wxi+b)L(w,b)=−∑_{x_iεn}y_i(wx_i+b)

其中M为误分类点的集合。

2.感知机学习算法的原始形态

感知机算法是使得损失函数L(w,b)L(w,b)极小化的最优化问题,可以使用随机梯度下降法来进行最优化。假设误分类点集合M是固定的,那么损失函数L(w,b)L(w,b)的梯度由

wL(ω,b)=xiεnyixi\nabla _{w}L\left( \omega ,b\right) =-\sum _{x_{i\varepsilon }n}y_{i}x_{i}

bL(ω,b)=xiεnyi\nabla _{b}L\left( \omega ,b\right) =-\sum _{x_{i\varepsilon }n}y_{i}

给出,随机选取一个误分类点(xi,yi)(x_i,y_i),对wbw,b进行更新:

ww+ηyixiw\leftarrow w+\eta y_ix_i

bb+ηyib\leftarrow b+\eta y_i

其中η(0<η1)η(0<η≤1) 称为学习率(learning rate),这样通过迭代可以使得损失函数L(w,b)L(w,b)不断减小,直到为0。

感知机算法原始形式的主要训练过程,《统计学习方法》李航,P29页:
在这里插入图片描述

Python代码实现主要函数:

# 感知机学习算法的原始形态
def perceptron(dataSet,loop_max=100):
    eta=1 # 学习率
    features=dataSet.shape[1]-1   # x特征列数量
    w=np.array([x*0 for x in range(0,features)])
    b=0
    for times in range(loop_max):
        for d in dataSet:
            x=d[:-1]
            y=d[-1]
            if y*(w@x+b)<=0: # @符号作用同np.dot
                w=w+eta*y*x
                b=b+eta*y
    return w,b

3.感知机学习算法的对偶形式

假设w0w_0b0b_0均初始化为0,对误分类点通过

ww+ηyixiw←w+ηy_ix_i

bb+ηyib←b+ηy_i

逐步修改wbw,b,设修改n次,则wbw,b关于(wi,yi)(w_i,y_i)的增量分为是αiyixiα_iy_ix_iαiyiα_iy_i,这里的αi=niηα_i=n_iη,其中ni表示第i个点误分类的次数,这样最后学习到的wbw,b可以分别表示为

w=i=1Nαiyixiw=∑_{i=1}^{N}α_iy_ix_i

b=i=1Nαiyib=∑_{i=1}^{N}α_iy_i

实例点更新的次数越多,意味着它距离分离超平面越近,也就越难正确分类。

训练过程,《统计学习方法》李航,P33-34页:

输出α,bα,b,其中α=(α1,α2,...,αN)Tα=(α_1,α_2,...,α_N)^T
(1)α0,b0α←0,b←0

(2)在训练集中选取数据(xi,yi)(x_i,y_i)

(3)如果yi(j=1nαjyjxjxi+b)0y_i(∑_{j=1}^nα_jy_jx_j⋅x_i+b)≤0,则

αiαi+ηα_i←α_i+η

bb+ηyib←b+ηy_i

(4)转到(2),直到没有错误。

最后通过w=i=1Nαiyixiw=∑_{i=1}^Nα_iy_ix_i计算出ww,使用上述过程求出的bb,即计算出模型参数。

Python代码实现主要函数:

# 感知机算法对偶形式
def perceptron_dual(dataSet,loop_max=100):
    m, n = dataSet.shape
    a=np.zeros((m, 1))
    b=0
    x_fea=dataSet[:,:-1]
    G=x_fea@x_fea.T          # @符号作用同np.dot
    #G=np.dot(x_fea,x_fea.T) # 两种写法均可
    x=dataSet[:,:-1]
    y=dataSet[-1].reshape((-1,1))
    
    for times in range(loop_max):
        for i in range(0,m):
            yi=y[i]
            err=0
            err=sum(a*y*G[i].reshape((m,1)))+b
            if yi*err<=0:
                a[i]=a[i]+1
                b=b+yi
                break
    print('a:',a.T,'b:',b)
    w=(a*y).T@x
    return w,b

程序输出结果:

在这里插入图片描述

以上算法完整源代码:

# -*- coding: utf-8 -*-
"""
 @Time    : 2018/11/21 09:22
 @Author  : hanzi5
 @Email   : hanzi5@yeah.net
 @File    : perceptron.py
 @Software: PyCharm
"""

import numpy as np
#import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

# 感知机学习算法的原始形态
def perceptron(dataSet,loop_max=100):
    eta=1 # 学习率
    features=dataSet.shape[1]-1   # x特征列数量
    w=np.array([x*0 for x in range(0,features)])
    b=0
    for times in range(loop_max):
        for d in dataSet:
            x=d[:-1]
            y=d[-1]
            if y*(w@x+b)<=0: # @符号作用同np.dot
                w=w+eta*y*x
                b=b+eta*y
    return w,b

# 感知机算法对偶形式
def perceptron_dual(dataSet,loop_max=100):
    m, n = dataSet.shape
    a=np.zeros((m, 1))
    b=0
    x_fea=dataSet[:,:-1]
    G=x_fea@x_fea.T          # @符号作用同np.dot
    #G=np.dot(x_fea,x_fea.T) # 两种写法均可
    x=dataSet[:,:-1]
    y=dataSet[-1].reshape((-1,1))
    
    for times in range(loop_max):
        for i in range(0,m):
            yi=y[i]
            err=0
            err=sum(a*y*G[i].reshape((m,1)))+b
            if yi*err<=0:
                a[i]=a[i]+1
                b=b+yi
                break
    print('a:',a.T,'b:',b)
    w=(a*y).T@x
    return w,b

# 可视化结果
def plotResult2(dataSet, weight, bias):
    fig = plt.figure()
    axes = fig.add_subplot(111)

    # 取两类x1及x2值画图
    type1_x1=dataSet[dataSet[:,-1]==-1][:,:-1][:,0].tolist() 
    type1_x2=dataSet[dataSet[:,-1]==-1][:,:-1][:,1].tolist()
    type2_x1=dataSet[dataSet[:,-1]==1][:,:-1][:,0].tolist()
    type2_x2=dataSet[dataSet[:,-1]==1][:,:-1][:,1].tolist()
    
    # 画点,使用红蓝两色
    axes.scatter(type1_x1, type1_x2, marker='x', s=20, c='red')
    axes.scatter(type2_x1, type2_x2, marker='o', s=20, c='blue')
    
    # 画线
    y = (0.1 * -weight[0] / weight[1] + -bias / weight[1], 4.0 * -weight[0] / weight[1] + -bias / weight[1])
    axes.add_line(Line2D((0.1, 4.0), y, linewidth=1, color='blue'))

    # 画坐标轴标签
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.show()

if __name__ == '__main__':
    # 生成数据,《统计学习方法》李航,P29,例2.1
    dataSet=np.array([[3,3,1],[4,3,1],[1,1,-1]])
    
    # 感知机学习算法的原始形态
    w,b=perceptron(dataSet)
    print('感知机学习算法的原始形态,计算结果如下:')
    print('w:',w,'b:',b)
    print()
    
    # 感知机学习算法的对偶形式
    print('感知机学习算法的对偶形式,计算结果如下:')
    w2,b2=perceptron_dual(dataSet)
    print('w2:',w,'b2:',b)
    
    # 画图
    plotResult2(dataSet, w2.reshape((-1,1)), b2)

参考资料:
1、《机器学习实战》Peter Harrington著
2、《机器学习》西瓜书,周志华著
3、 斯坦福大学公开课 :机器学习课程
4、机器学习视频,邹博
5、《统计学习方法》李航
6、感知机原理小结
7、机器学习算法–Perceptron(感知机)算法

posted @ 2018-11-22 17:41  hanzi5  阅读(1040)  评论(0编辑  收藏  举报