在上一节,我们学习了Rosenblatt感知器规则的工作机制,现在让我们用Python进行实现,并将其应用于第1章所介绍的鸢尾花数据集。
本章将用面向对象的方法把感知器接口定义为一个Python类,它允许初始化新的
Perceptron
对象,这些对象可以通过
fit
方法从数据中学习,并通过单独的
predict
方法进行预测。虽然在创建时未初始化对象,但可以通过调用该对象的其他方法,作为约定,我们在属性的后面添加下划线(
_
)来表达,例如
self.w_
。
如果对Python科学库不熟或需要回顾,请参考下面的资源:
下面的Python代码实现了感知器:
依托这段感知器代码,我们可以用学习速率
eta
和学习次数
n_iter
(遍历训练数据集的次数)来初始化新的
Perceptron
对象。
通过
fit
方法初始化
self.w_
的权重为向量
,m代表数据集的维数或特征数,为偏置单元向量的第一个分量+1。请记住该向量的第一个分量
self.w_[0]
代表前面讨论过的偏置单元。
另外,该向量包含来源于正态分布的小随机数,通过调用
rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1])
产生标准差为0.01的正态分布,其中
rgen
为NumPy随机数生成器,随机种子由用户指定,因此可以保证在需要时可以重现以前的结果。
不把权重初始化为零的原因是,只有当权重初始化为非零的值时,学习速率η(
eta
)才会影响分类的结果。如果把所有的权重都初始化为零,那么学习速率参数η(
eta
)只会影响权重向量的大小,而无法影响其方向。如果你熟悉三角函数,考虑一下向量v1=[1 2 3],v1和向量v2=0.5×v1之间的角度将会是0,参见下面的代码片段:
这里
np.arccos
为三角反余弦函数,
np.linalg.norm
是计算向量长度的函数(随机数从随机正态分布而不是均匀分布中抽取,以及选择标准偏差为0.01,这些决定是任意的。记住,只对小随机值感兴趣的目的是避免前面讨论过的所有向量为零的情况)。
对一维数组,NumPy索引与用
[]
表达的Python列表类似。对二维数组,第一个元素为行,第二个元素为列。例如用
X[2, 3]
来指二维数组
X
的第三行第四列。
初始化权重后,调用
fit
方法遍历训练数据集的所有样本,并根据我们在前一节中讨论过的感知器学习规则来更新权重。
为了获得分类标签以更新权重,
fit
方法在训练时调用
predict
来预测分类标签。但是也可以在模型拟合后调用
predict
来预测新数据的标签。另外,我们也把在每次迭代中收集的分类错误记入
self.errors_
列表,用于后期分析训练阶段感知器的性能。用
net_input
方法中的
np.dot
函数来计算向量点积
w
T
x
。
对数组
a
和
b
的向量点积计算,在纯Python中,我们可以用
sum([i * j for i, j in zip(a, b)])
来实现,而在NumPy中,用
a.dot(b)
或者
np.dot(a, b)
来完成。然而,与传统Python相比,NumPy的好处是算术运算向量化。
向量化
意味着基本的算术运算自动应用在数组的所有元素上。把算术运算形成一连串的数组指令,而不是对每个元素完成一套操作,这样就能更好地使用现代CPU的
单指令多数据支持
(SIMD)架构。另外,NumPy采用高度优化的以C或Fortran语言编写的线性代数库,诸如
基本线性代数子程序
(BLAS)和
线性代数包
(LAPACK)。最后,NumPy也允许用线性代数的基本知识(像向量和矩阵点积)以更加紧凑和自然的方式编写代码。
为了测试前面实现的感知器,本章余下部分的分析和示例将仅限于两个特征变量(维度)。虽然感知器规则并不局限于两个维度,但是为了学习方便,只考虑萼片长度和花瓣长度两个特征,将有利于我们在散点图上可视化训练模型的决策区域。
请记住,这里的感知器是二元分类器,为此我们仅考虑鸢尾花数据集中的山鸢尾和变色鸢尾两种花。然而,感知器算法可以扩展到多元分类,例如通过 一对多 (OvA)技术。
OvA有时也被称为 一对其余 (OvR),是可以把分类器从二元扩展到多元的一种技术。OvA可以为每个类训练一个分类器,所训练的类被视为正类,所有其他类的样本都被视为负类。假设要对新的数据样本进行分类,就可以用n个分类器,其中n为分类标签的数量,并以最高的置信度为特定样本分配分类标签。在感知器的场景下,将用OvA来选择与最大净输入值相关的分类标签。
首先,可以用
pandas
库从UCI机器学习库把鸢尾花数据集直接加载到
DataFrame
对象,然后用
tail
方法把最后5行数据列出来以确保数据加载的正确性,如图2-5所示。
图 2-5
如果无法上网或UCI的服务器(https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data)宕机,你可以直接从本书的代码集找到鸢尾花数据集(也包括本书所有其他的数据集)。可如下从本地文件目录加载鸢尾花数据,用
替换:
接下来,提取与50朵山鸢尾花和50朵变色鸢尾花相对应的前100个分类标签,然后将其转换为整数型的分类标签
1
(versicolor)和
-1
(setosa),并存入向量
y
,再通过调用pandas的
DataFrame
的
value
方法获得相应的NumPy表达式。
同样,可以从100个训练样本中提取特征的第一列(萼片长度)和第三列(花瓣长度),并将它们存入特征矩阵
X
,然后经过可视化处理形成二维散点图:
执行前面的代码示例可以看到图2-6所示的二维散点图。
图 2-6
前面的散点图显示了鸢尾花数据集的样本在花瓣长度和萼片长度两个特征轴之间的分布情况。从这个二维特征子空间中可以看到,一个线性的决策边界足以把山鸢尾花与变色山鸢尾花区分开。
因此,像感知器这样的线性分类器应该能够完美地对数据集中的花朵进行分类。
现在是在鸢尾花数据集上训练感知器算法的时候了。此外,我们还将绘制每次迭代的分类错误,以检查算法是否收敛,并找到分隔两类鸢尾花的决策边界:
执行前面的代码,我们可以看到分类错误与迭代次数之间的关系,如图2-7所示。
图 2-7
正如从图2-7中可以看到的那样,感知器在第六次迭代后开始收敛,现在我们应该能够完美地对训练样本进行分类了。下面通过实现一个短小精干的函数来完成二维数据集决策边界的可视化:
首先,我们通过
ListedColormap
根据颜色列表来定义一些颜色和标记并创建色度图。然后,确定两个特征的最小值和最大值,通过调用NumPy的
meshgrid
函数,利用特征向量来创建网格数组对
xx1
和
xx2
。因为是在两个特征维度上训练感知器分类器,所以我们需要对网格数组进行扁平化,以创建一个与鸢尾花训练数据子集相同列数的矩阵,这样就可以调用
predict
方法来预测相应网格点的分类标签
z
。
在把预测获得的分类标签
z
改造成与
xx1
和
xx2
相同维数的网格后,现在可以通过调用Matplotlib的
contourf
函数画出轮廓图,把网格数组中的每个预测分类结果标注在不同颜色的决策区域:
执行示例代码后,我们可以看到图2-8所示的决策区域。
图 2-8
如图2-8所示,感知器通过学习掌握了决策边界,从而完美地为鸢尾花训练数据子集分类。
虽然感知器可以完美地区分两类鸢尾花,但收敛是感知器的最大问题之一。Frank Rosenblatt从数学上证明,如果两个类可以通过一个线性超平面分离,那么感知器学习规则可以收敛。然而,如果两个类不能被这样的线性决策边界完全分隔,那么除非设定最大的迭代次数,否则算法将永远都不会停止权重更新。有兴趣的读者可以从下述网站找到我的讲义中关于该问题的证明概述:https://sebastianraschka.com/pdf/lecture-notes/stat479ss19/L03_perceptron_slides.pdf。