单层感知器适用于解决线性可分问题,这是前文反复提到的分类问题的一种。简单地说,当平面上的两类数据可以用一条直线(称为决策边界)分开时,就称其为线性可分的,如图6-3所示。在这一节,将以一个简单的线性分类问题为例,介绍感知器的学习算法。
图6-3
这个实例所采用的数据集来自机器学习领域的经典数据集——鸢尾花数据集(Iris数据集)。在各种分类问题中,经常会采用这个数据集作为训练数据和测试数据。相应的数据可从教材的资源平台下载。
下载数据后打开,会发现该数据集包括3类共150条数据,其中每类各含50条数据。这3类数据描述了山鸢尾(iris setosa)、杂色鸢尾(iris versicolour)和弗吉尼亚鸢尾(iris virginica)3种鸢尾花的4个特征属性,分别是花萼长度(Sepal Length)、花萼宽度(Sepal Width)、花瓣长度(Petal Length)和花瓣宽度(Petal Width)。数据形式如表6-1所示。
表6-1
接下来的目标是使用感知器,通过在这个数据集上进行训练,给出一个分类器。分类器根据一个未知类型的鸢尾花数据可以自动识别出它所属的鸢尾种类。
神经网络的学习过程,就是根据训练数据学习合适的连接权重,从而可以使用合适的连接权重来提取正确的类别特征。什么叫作合适的连接权重呢?当然是当一组连接权重可以通过特征属性输出正确的类别时,就是合适的。
为了更简单地说明神经网络的学习法则,接下来只考虑两类鸢尾——山鸢尾和杂色鸢尾,把问题变成一个二分类问题。用-1表示山鸢尾,用1表示杂色鸢尾。每一个输入信息(即每一条数据)包含4个特征属性值和1个类别值。两类数据一共有100条,可以写成
Χ={(x1,C1 ),(x2,C2 ),…,(x100,C100)}
其中表示这两个向量的内积。使用上一节定义的函数作为激活函数,则感知器的输出为
此时输出的结果与这条数据对应的真实类别值不一定相符,这是因为初始的连接权重并不合适,所以需要对它进行调整。接下来通过比较ci与y0来调整连接权重。共有如下四种情况。
①ci=1,y0=1,输出的类别正确,保持权重不变ω1=ω0;
②ci=-1,y0=-1,输出的类别正确,保持权重不变ω1=ω0;
其中ω1}表示调整后的权重,η是一个大于零的正数,叫作学习速率。上述工作流程可以不断地迭代下去(由ωk推出ωk+1),直到感知器输出的所有分类信息都是正确的,就可以停止更新权重,得到一个可用的分类器。当然在很多实际任务中,感知器即使经过很多次迭代,也未必能输出完全正确的分类信息,此时如果输出分类信息的正确率达到事先指定的水平,也可以停止继续迭代。
在上述工作流程中,可以通过随机数给出初始权重。实际上初始权重还有其他的指定方法,不同的初始权重对于算法的收敛性和收敛速度都会产生影响。更新权重的过程中出现的学习速率,也是一个重要的参数,它设定得过大可能会造成算法不收敛,设定得过小会减慢收敛速度。这种权重的更新方式可以通过严格的数学推理得出,它是神经网络中基本的学习方法,叫作梯度下降。它所依赖的数学知识是函数的导数,因为需要用到多元函数的导数,所以这里略去了它的严格推导过程。
影响神经网络算法收敛性和收敛速度的因素还有很多。参数设置不当或者算法不适用于所考虑的问题,都可能导致不收敛或者收敛速度过慢,读者可以在将来的实践操作中进行学习。但是需要说明的是,对于线性可分的问题,早在1958年,罗斯布拉特就已经严格证明了上述算法经过有限步的迭代一定会收敛到一个正确的分类器。
下面在鸢尾花数据上使用感知器算法实现分类。如果使用4个特征属性解决鸢尾花的三分类问题,其实问题是非线性可分的。为了把它变成一个单层感知器可以解决的问题,接下来只关心山鸢尾和杂色鸢尾两类数据,并且只使用其中的花萼长度和花瓣长度两个特征属性进行分类。首先需要导入将要使用的库,其中pandas用来处理和分析数据,numpy用来做数组与矩阵的运算,matplotlib用来做数据可视化。另外,为了简单起见,这里直接使用sklearn提供的感知器算法。
In[1]:import pandas as pd
In[2]:import numpy as np
In[3]:import matplotlib.pyplot as plt
In[4]:import matplotlib as mat
In[5]:from matplotlib.colors import ListedColormap
In[6]:from sklearn.linear_model import Perceptron
在Python使用matplotlib画图时,在图中使用中文说明会增强图的可读性,但如果直接用中文进行说明,会显示乱码。为了解决这个问题,可以事先指定中文字体,在画图过程中需要使用中文时,可以直接调用这个字体,就不会有乱码问题了。
In[7]:font=mat.font_manager.FontProperties(fname='C:\Wind
ows\Fonts\simsun.ttc')
从文件夹读取数据集,打印最后5条数据和数据形状,以熟悉接下来需要进行分析的数据的存储格式。
In[8]:data=pd.read_csv("iris.csv",header=None)
In[9]:print(data.tail(n=5))
0 1 2 3 45
146 146.0 6.7 3 5.2 2.3 virginica
147 147.0 6.3 2.5 5.1 9 virginica
148 148.0 6.5 3 5.2 2 virginica
149 149.0 6.2 3.4 5.4 2.3 virginica
150 150.0 5.9 3 5.1 1.8 virginica
In[10]:print(data.shape)
(151,6)
为了让读者更熟悉Python处理数据的方式,在这个案例中,将从整个鸢尾花数据集中抽取需要使用的类别和特征属性数据。
In[11]:flower_class=data.iloc[1:101,5].values
In[12]:flower_class=np.where(flower_class=="setosa",-1,1)
In[13]:flower_shape=data.iloc[1:101,[1,3]].values
在上述数据抽取的过程中,有几点需要注意。在Python中数据编号是从0开始的。例如,想读取第1条数据,那么在代码中应该告诉Python你想读取的数据编号是0,如果想读取第9条数据,那么告诉Python的数据编号应该是8。案例需要分析的是鸢尾花数据的前两类,一共100条数据。在下载的数据集中,第一行是数据项的名称,所以应该读取第2到第101条。因此在In[11]中,告诉Python的数据编号是“1:101”(不含101)。类别数据在表格中位于第6列,所以代码中的列编号为“5”。类似地,花萼长度和花瓣长度位于表格的第2列和第4列,所以在In[13]中告诉Python的编号是1和3。In[12]中,把类别“setosa”和“versicolour”转换成了-1和1,它的意思是,如果是setosa,则类别为-1,否则为1。
接下来定义绘制数据散点图的函数,通过data_show( )调用函数显示散点图(图6-4)。
In[14]:def data_show():
plt.title("鸢尾花散点图",fontproperties=font)
plt.xlabel("花瓣长度",fontproperties=font)
plt.ylabel("萼片长度",fontproperties=font)
plt.legend(loc="upper left")
plt.show()
In[15]:data_show( )
图6-4
利用sklearn提供的感知器,对抽取出的数据进行学习。学习过程一共迭代10次,学习速率设定为0.1。
In[16]:flower_classifier=Perceptron(n_iter=10,eta0=0.1)
In[17]:flower_classifier.fit(flower_shape,flower_class)
Out[17]:
Perceptron(alpha=0.0001,class_weight=None,eta0=0.1,fit_interce
pt=True,n_iter=10,n_jobs=1,penalty=None,random_stat
e=0,shuffle=True,verbose=0,warm_start=False)
学习完毕后,计算学习结果的准确率。可以看到准确率为100%(1.0)。这表明,学习结束后得到的模型在这个数据集上的分类准确率是100%,也就是对这些数据可以给出完全正确的分类。通常应该把数据集划分为训练集和检测集,通过训练集学习,再通过检测集评估结果。在这个案例中没有把数据划分为两个部分。一是因为这个问题比较简单,二是可用的数据较少。
In[18]:accuracy=flower_classifier.score(flower_shape,flower_class)
In[19]:print(accuracy)
1.0
可以定义一个绘图函数,来显示这个分类器的决策边界。结果显示如图6-5所示。
In[20]:def plot_decision_regions(x,y,classifier,resolution=0.2):
markers=('s','x','o','^','v')
colors=('red','blue','lightgreen','gray','cyan')
listedColormap=ListedColormap(colors[:len(np.
unique(y))])
x1_min=0
x1_max=8
x2_min=0
x2_max=6
new_x1=np.arange(x1_min,x1_max,resolution)
new_x2=np.arange(x2_min,x2_max,resolution)
xx1,xx2=np.meshgrid(new_x1,new_x2)
z=classifier.predict(np.array([xx1.ravel(),xx2.
ravel()]).T)
z=z.reshape(xx1.shape)
plt.contourf(xx1,xx2,z,alpha=0.4,camp=listedColor
map)
plt.xlim(xx1.min(),xx1.max())
plt.ylim(xx2.min(),xx2.max())
for idx,c1 in enumerate(np.unique(y)):
plt.scatter(x=x[y==c1,0],y=x[y==c1,1],alpha=0.8,
c=listedColormap(idx),marker=markers[id
x],label=c1)
In[21]:def decision_regions_show():
plot_decision_regions(flower_shape,flower_class,
classifier=flower_classifier)
plt.title("鸢尾花花瓣、花萼边界分割",fontproperties=
font)
plt.xlabel("花瓣长度[cm]",fontproperties=font)
plt.ylabel("花萼长度[cm]",fontproperties=font)
plt.legend(loc="upper left")
plt.show()
In[22]:decision_regions_show()
图6-5