3.2 在每个节点上增加数据的输入和计算结果
我们已从原理上、架构上以及TensorFlow的可视化运行过程层面学习了前向传播的工作过程。接下来,我们将实现前向传播功能,在项目中新增ForwardPropagation.py程序,完成后的目录结构如图3-9所示。
图3-9 chapter3_Create_AI_Framework目录结构
先运行一下在2.1节建立的程序Neuron_Network_Entry.py,当隐藏层设置为hidden_layers=[8,4,2]时,运行结果如下:
以上运行结果显示创建了3个隐藏层,第一个隐藏层有8个神经元,N[i][j]中索引i表示第i个隐藏层,索引j表示第j个神经元,第二个隐藏层有4个神经元,第三个隐藏层有2个神经元,参见图3-6。根据实例集(instances)第1列的数据和第2列的数据得出第3列的数据,异或计算当第1个元素和第2个元素不同时,结果为1,第1个元素和第2个元素相同时,结果为0。实例集的数据是我们输入系统的,但系统并不知道要进行异或计算,神经网络的训练就是要让系统准确地预测到第一列与第二列的某种关系,由此得出第三列到底是0还是1。读者可能认为这里只有4行数据,而实际环境中可能有上“TB”级的数据量,但是系统的原理都是一样的,处理上百万条、上亿条数据在理解上和这个是完全一样的。
训练的时候会有误差,调整误差是反向传播算法的功能,看看预测的值和实际的值是否一致,误差有多大。反向传播算法是从后向前推,看哪些因素导致了误差以及误差的程度,以使下一次的表现更好。反向传播算法将在后续章节讲解。
接下来编写代码,我们的目标是从输入层出发,经过所有隐藏层的处理,最终在输出层得出结果,实现前向传播算法的功能。
前向传播算法是个业务类,在编写ForwardPropagation.py之前,由于每个神经元的分析都会产生计算结果,我们需先对Node.py的代码进行修改。例如,图3-10所示的TensorFlow的可视化运行示意图中,第一个隐藏层的第一个神经元要接收输入层的数据进行处理并得出结果,每个神经元会有2个新的方法,get_value方法接收上一个神经元的数据,set_value方法存储神经元计算的结果。
图3-10 神经元节点的输入及输出
chapter3_Create_AI_Framework版本的Node.py,在chapter2_Create_AI_Framework版本的Node.py代码的第38行之后新增代码,增加以下set_input_value、get_input_value、set_value、get_value方法。
在ForwardPropagation.py文件中调用Node.py中的set_value、get_value及set_input_value方法,其中get_value方法获取上一层中和自己有权重关系的一个神经元的值,set_value方法设置经过神经元非线性变换以后的结果,set_input_value方法设置上一层中所有和自己有权重关系的神经元的值并设置给当前神经元。
接下来进行前向传播业务类代码的编写,这是关键的部分。在service目录下创建ForwardPropagation.py文件,该程序将实现从输入层,经过若干隐藏层,最后得出输出层的值的功能。在ForwardPropagation.py文件中编写class ForwardPropagation类,定义一个applyForwardPropagation方法从神经元网络的最左侧运行到最右侧。applyForwardPropagation方法是一个工具方法,也是一个业务逻辑方法,但不是实例的方法,不需要创建类的实例来调用,因此不需要传入self参数。applyForwardPropagation方法中传入的第一个参数为nodes,表示所有的节点;传入的第二个参数为weights,表示权重。例如,图3-11所示的TensorFlow的可视化示意图中,从输入层的第一个节点,运行到第一个隐藏层的第一个节点,连接两者的只有权重。这里传入所有的权重。
图3-11 权重示意图
传入的第三个参数为实例(instance),每次传入一个实例。例如实例集第二条记录[0,1,1]是输入数据,第一个0作为x1的数据,第二个1作为x2的数据,经过一系列处理,最后得出一个计算结果(结果可能是0,也可能是其他数字),每次传入的参数是一个训练实例。
ForwardPropagation.py中的applyForwardPropagation方法的代码如下:
这里我们先不实现applyForwardPropagation方法,而在chapter2_Create_AI_Framework版本的Neuron_Network_Entry.py代码末尾新增代码。在chapter3_Create_AI_Framework版本的Neuron_Network_Entry.py入口程序中先调用applyForwardPropagation方法,代码如下。
实例集中的每个实例都会用到applyForwardPropagation方法,因此需循环遍历实例集。该实例集有4个实例,分别为[0,0,0]、[0,1,1]、[1,0,1]、[1,1,0],程序每次只处理一个实例,instances[i]的索引从0开始,在第7行代码处调用ForwardPropagation.applyForwardPropagation方法。applyForwardPropagation方法传入的第一个参数是所有节点,传入的第二个参数是权重,传入的第三个参数是实例,在第10行代码打印训练的结果。注意,每次输入一个实例是指输入实例的第1列数据x1、第2列数据x2,而实例集的第3列数据是实际值,训练的预测值和实际值可能不一致。
接下来我们将编写applyForwardPropagation的代码,该程序由以下几部分组成:
- 设置偏爱因子节点的值。
- 把数据输入到输入层。
- 隐藏层的处理,进行线性转换(在3.2节中介绍)。
- 使用Sigmoid函数作为激活函数,进行非线性转换(在3.3节中介绍)。
(1)设置偏爱因子节点的值。
因为计算发生在不同层的神经元节点之间,节点中设置了层次信息,获得神经元节点的时候也会知道节点所在的层,所以首先循环所有的节点。神经网络节点可能是神经元节点,也可能是偏爱因子节点,而计算是发生在神经元节点之间的。偏爱因子节点是一个固定的值,偏爱因子节点没有输入数据和输出数据,因此这里需将偏爱因子节点过滤掉。如果当前的节点是偏爱因子,get_is_bias_unit()等于True。这里为了简化,暂将Bias的值设置为1,偏爱因子节点不需要进行计算。偏爱因子节点不参与线性、非线性的转换过程,线性、非线性的转换过程是神经元节点涉及的。
ForwardPropagation.py的applyForwardPropagation的源代码如下:
(2)把数据输入到输入层。
回顾一下NetworkStructure.py中构建网络算法的实现,每一层神经元网络的第一个节点都是偏爱因子。NetworkStructure.py的create_nodes方法在创建输入层节点时,首先在输入层中创建偏爱因子节点,偏爱因子节点的索引ID是0;然后创建输入层的2个节点x1、x2,x1节点的索引ID是1,x2节点的索引ID是2,如图3-12所示。
图3-12 偏爱因子节点
例如处理实例集的第1个元素instance[0,1,1],训练的时候只需要前2个特征,遍历次数为2,获得该条数据中每个特征具体的值。然后通过for语句循环遍历所有的节点,索引为0的节点为偏爱因子,因此不能从0开始,要从索引为1的节点开始。第1次设置的是输入层x1节点的值0,第2次设置的是输入层x2节点的值1,这就完成了把数据输入到输入层的过程。
ForwardPropagation.py的applyForwardPropagation的源代码如下: