本章采用面向对象的方法将感知机接口定义为Python中的一个类,这样可以初始化新的Perceptron对象,而且这些对象可以运行fit方法从数据中学习,还可以通过predict方法预测标签。按照惯例,在对象初始化时,变量名后面加一个下划线(_)表示未赋值的属性。可以通过调用对象的其他方法来初始化这些未赋值的属性,例如调用self.w_。
Python科学计算资源
如果你不熟悉或需要重温Python科学计算库,可以参考以下资源:
·NumPy:https://sebastianraschka.com/blog/2020/numpy-intro.html。
·pandas:https://pandas.pydata.org/pandas-docs/stable/user_guide/10min.html。
·Matplotlib:https://matplotlib.org/stable/tutorials/introductory/usage.html。
以下为感知机的Python代码实现:
基于上述感知机实现,可以使用学习率eta( η )和学习次数n_iter(训练模型时遍历训练数据集的次数)初始化新的Perceptron对象。
通过fit方法,初始化偏置self.b_为初始值0,初始化权重self.w_为一个属于R m 的向量,其中 m 表示数据集中的维数或特征数。
请注意,初始化权重向量的命令为rgen.normal(loc=0.0,scale=0.01,size=1+X.shape[1]),产生均值为0、标准差为0.01的正态分布随机数。其中,rgen是NumPy的一个随机数生成器,用户可以设定随机种子,这样可以在需要时复现以前运行的结果。
从技术上讲,可以将权重初始化为0(实际上最初的感知机算法就是这么做的)。然而,如果这样做,学习率 η (eta)就失去了对决策边界的影响。当所有权重都初始化为0时,学习率参数eta只影响权重向量的大小,而不影响其方向。如果熟悉三角函数,考虑向量 v 1 =[1 2 3], v 2 =0.5× v 1 ,那么向量 v 1 与向量 v 2 间的夹角为0度,如下述代码所示:
这里np.arccos是反余弦函数,np.linalg.norm是一个计算向量长度的函数。(这里使用标准差为0.01的正态随机数并非故意为之,也可以使用均匀分布的随机数。切记,使用值很小的随机数是为了避免向量中所有元素为0的情况)。
作为阅读本章后的选做练习题,可以将self.w_=rgen.normal(loc=0.0,scale=0.01,size=X.shape[1])替换为self.w_=np.zeros(X.shape[1]),并改变eta的值,运行下一小节介绍的感知机训练代码,会发现决策边界没有变化。
NumPy数组索引
NumPy一维数组索引方式与Python中使用方括号([])的列表一样。对于NumPy二维数组,第一个索引是数组的行,第二个索引是数组的列。例如,使用X[2,3]表示二维数组X中位于第三行第四列的元素。
初始化权重后,调用fit方法遍历训练数据集中的所有样本,并根据上一节讨论的感知机学习规则更新权重。
在训练阶段,predict函数被fit方法调用,获得预测标签以更新权重。也可以在模型拟合后调用predict函数预测新样本的类别标签。此外,在每次迭代中使用fit方法收集分类错误的样本并记入self.errors_列表中,用于后期分析感知机在训练阶段的表现。net_input方法使用np.dot函数计算两个向量的点积。
向量化:使用向量计算替代for循环
为了计算数组a和b的向量点积,在Python中,可以用sum([i * j for i,j in zip(a,b)])实现,如果使用NumPy,则可以用a.dot(b)或np.dot(a,b)实现。然而,与使用Python相比,使用NumPy的好处是可以实现算术运算向量化。向量化意味着自动将算术运算应用于数组的所有元素。算术运算向量化不对每个元素完成一套运算,而是把算术运算形成一连串的数组指令,这样就能更好地使用CPU的单指令多数据(Single Instruction Multiple Data,SIMD)架构。另外,NumPy采用高度优化的C或Fortran语言编写线性代数库,例如基本线性代数子程序(Basic Linear Algebra Subprograms,BLAS)和线性代数包(Linear Algebra Package,LAPACK)。最后,使用NumPy我们可以借助线性代数的基本知识(如向量和矩阵点积)将计算机代码写得更紧凑、更自然。