2.1 感知器
2.1.1 从神经元到感知器
我们首先要了解神经网络的历史。
1943年,W.McCullock和W.Pitts发表了一篇讨论简单的人类大脑细胞的论文A Logical Calculus of the Ideas Immanent in Nervous Activity[1],在该论文中将Neuron定义为大脑中相互连接的神经细胞,用于处理和传递各种化学信号和生物电信号。在该论文中,这样的神经元被定义为一个简单的逻辑门,该逻辑门接收多个信号输入并将其整合到一起,如果信号叠加值超过某个阈值,该神经元就会输出信号到下一个神经元。
1957年,F.Rosenblatt在The Perceptron, a Perceiving and Recognizing Automaton[2]一文中提出了感知器(Perceptron)的概念及对应的算法,该算法能够自动学习权重,使得其与输入的乘积能够决定一个神经元是否产生输出。更严谨地说,我们可以把这个问题描述为一个二分类问题,把类别定义为1(输出)和0(不输出)。然后,我们可以把输入和权重的乘积进行叠加,最终产生输出,在输出值上可以定义某种转换函数(Transfer Function)来将结果转换到我们需要的分类上,这个转换函数通常又被称为激活函数(Activation Function)。整个流程如下所示:
其中,我们把w称为权重,把x称为输入特征,把z称为网络输入(NetworkInput),是决定输出的激活函数。
由此,我们应该能够理解为什么要把称为激活函数。这是因为对于早期的脑神经研究来说,要么输出(1),要么不输出(0),如果输出的话,我们就认为该神经元被激活了,所以就有了激活函数的概念。
同时,在w、x、z、这几组数据中可以看到:
◎ 在监督训练阶段,w不可知,是需要训练和学习的目标。我们通过训练集中已知的x和输出值来反推w的值,这个反推的过程被称为反向传播,通常使用梯度下降(Gradient Descent)算法,这在后面会进行详细讲解。这个过程需要反复循环多次,计算量通常较大,需要大量的计算资源和时间。
◎ 在实际运行和预测阶段,我们用训练好的w权重和实时输入来计算最后的输出值,因为基本上只进行少数几次矩阵运算,所以复杂度比反向传播要低很多。
我们在图2-1中可以看到基本的感知器工作流程:神经元(Neuron)收到输入x,输入x和权重w相乘后叠加合并形成网络输入;网络输入被传送到激活函数,生成输出(Output,1或0)。如果在训练阶段,则输出将被用于和真实输出进行误差计算,并依此更新权重w。
图2-1 感知器
这里还没谈到神经网络。到目前为止,我们只需知道神经网络由神经元构成,而感知器指的是神经元的工作方式。要了解神经网络,则首先要了解单个神经元是如何工作的。
2.1.2 实现简单的感知器
我们现在按照图2-1的思路来实现一个简单的感知器。为了展示基本的感知器思路,这段代码不涉及任何第三方库,甚至不使用Numpy。这里只设一个权重w,并设置一个bias参数。因此network_input为wx+bias(注意,bias也可被视为权重w0,对应一个输入为1的常数。把bias视为权重参数之一有利于统一计算),同时,我们使用上面定义的作为激活函数,根据网络输入的值来输出1或0,这就是最简单的感知器模型实现。
我们的训练集和测试数据与第1章的示例相同,用10组数据作为训练集,用4组数组作为测试集,整体的代码(Simple_perceptron.py)实现如下:
我们来看看这些代码都做了什么。
第1~6行:设置初始化参数lr(用于调整训练时的步长,即learning rate学习率)、iterations(迭代次数)、权重w与bias。
第9~29行:这是模型训练的核心代码。根据iterations的设定,我们用同样的训练数据反复训练给定的次数,在每一次训练中都根据每一对数据对参数进行调整,即模型训练。在训练集中有真实标签y,注明当前输入x的真实类别,然后就可以根据我们设定的y’(预测值)=(wx+bias)来获得y’的预测值。这个预测是通过第23~24行的predict函数实现的,它实际上是根据第19~20行的network_input输入做出的判断。
在第14行,我们首先获得真实值y与预测值y'的偏差,却并不直接用这个偏差来计算和调整w、bias,而是乘以一个较小的参数lr。我们希望每次都对w和bias进行微调,通过多次迭代和调整后让w和bias接近最优解(Optimized Value),所以我们希望每次调整的幅度都不要太大。这是一个较难两全的事情:lr值过小可能会导致训练时间过长,难以在实际实验中判断是否收敛;lr值过大则容易造成步长过大而无法收敛。在第14行获得需要更新的update值之后,我们可以按照在本章参考文献[2]中给出的感知器的学习率来计算如何调整w和bias(这里暂时不去仔细分析这两个公式是怎么得来的,因为不同算法的实现各不相同,我们会在2.2.2节讲解梯度下降时再去分析):
第15~16行:仅仅是每次都把w和bias根据上面的误差更新进行调整。
那么,这样一个简单模型的运行效果如何呢?我们在第28~39行引入了在第1章中使用的训练数据和测试数据,看看训练效果如何。运行该代码,我们可以看到以下输出:
可以看到,对于输入的4组数据,运行结果完全正确。
最后两行输出的是w和bias的数值。
以上便是神经网络的最初起源及用Python进行的具体实现。当然,这个例子是非常简单的:①我们只处理单个输入;②分类也仅仅是很简单的二分;③我们的激活函数是一个非常直接的二分输出,在实际情况下基本不可能这么设置。但是,这基本解释了神经网络运行的基本原理和模式。
本章后面几节将仔细讲解基于梯度下降的算法实现(即如何基于梯度下降来调整权重)和相关的一些概念。当然,正如本书反复强调的,我们将通过Python代码来实现所有这些概念,而不会停留在名词解析层面。