2.2 实现神经网络的节点结构
下面通过代码实现神经网络的节点结构。
chapter2_Create_AI_Framework版本的Node.py的源代码如下:
第3~10行代码:设置和访问神经元在全局所处的层(Layer)。如图2-13所示,神经网络分了很多层,需进行设置,如输入层、隐藏层、输出层等,各层都有顺序。将输入层作为整个神经网络的第0层,如果设置2个隐藏层(2个隐藏层分别包括4个、2个神经元),那么第1个隐藏层作为整个神经网络的第1层,第2个隐藏层作为整个神经网络的第2层,输出层作为整个神经网络的第3层。
图2-13 神经网络的层次
第12~20行代码:设置和访问神经元全局唯一的ID,完成身份的设置。图2-13中输入层的x1、x2等输入节点有自己的ID,第一层隐藏层的神经元(4个节点)有自己的ID,第二层隐藏层神经元(2个节点)有自己的ID,输出层的节点也有自己的ID。
第22~29行代码:设置和访问神经元的名称。神经元除了节点的ID和网络层次以外,还需设置一个节点名称。ID是一个数字,就像是节点的身份证号,label就是节点的名称。
第31~38行代码:判断当前的神经元是否是一个偏爱因子,这是神经元除了节点的ID、网络层次、名称以外,还需考虑的内容。因为我们是面向对象的实现,在神经网络中有一个偏爱因子对象,偏爱因子也是一个实体,我们在判断神经元的时候,在最开始的时候有必要判断是偏爱因子还是具体的特征,所以一个很重要的步骤,就是要判断一下元素是神经元本身还是偏爱因子。第33行在set_is_bias_unit方法中传入一个参数is_bias_unit,is_bias_unit是一个布尔值,判断是不是偏爱因子,如图2-14所示。
图2-14 偏爱因子示意图
这样最简单的神经网络的神经元节点就构建起来了。注意,这里的节点有两种类型,一种是神经元;另一种是偏爱因子。
下一步的工作是创建神经网络的结构,从面向对象编程的角度,创建神经网络结构的工作放在service中,我们创建一个service的文件夹,在service的文件夹中创建一个业务类NetworkStructure.py,在NetworkStructure.py中实现神经网络的结构逻辑。
chapter2_Create_AI_Framework版本的NetworkStructure.py(v1)的源代码如下:
第8行代码:构建create_nodes方法。要创建神经网络首先要创建神经网络的节点,神经网络中有很多的节点,怎么判断有多少个节点?最简单的方法是先去掉隐藏层,实现输入层、输出层,之后再加上隐藏层。而输入层有多少个元素取决于有多少个特征,所以在create_nodes方法中传入的第一个参数是num_of_features,第二个参数是hidden_layers,hidden_layers可能是0个隐藏层,也可能是100个隐藏层。注意,create_nodes方法这里没有使用self参数。
第10行代码:定义nodeIndex,每个节点有自己的节点ID,神经元的ID默认从0开始,神经元的ID是全局的。
第12~25行代码:构建输入层的节点。注意,因为目前还不知道输入层的数据是怎样的,因此输入层的神经元节点代码暂不好编写,从我们能够入手的角度出发,从输入层到下一层有偏爱因子,所以先创建偏爱因子,偏爱因子是一个节点,如图2-15所示。
图2-15 创建输入层的偏爱因子
- 第15行代码:构建偏爱因子的节点实例。
- 第17行代码:通过set_index方法是设置偏爱因子节点的index,偏爱因子是输入层的第0个节点。
- 第18行代码:将偏爱因子节点的名称设置为“+1”。
- 第19行代码:设置偏爱因子节点的set_is_bias_unit为True,即设置为偏爱因子节点。
- 第20行代码:在创建偏爱因子节点以后,将其加入到所有节点的数组集中。
- 第21行代码:创建偏爱因子节点以后,将节点的索引号nodeIndex加1。
第27~56行代码:构建隐藏层的节点。隐藏层中既有神经元节点本身,也有偏爱因子节点,首先要看隐藏层里面有多少层,对应每一层去创建不同的节点。创建隐藏层使用2个循环,外层循环遍历神经网络有多少层,每层创建一个偏爱因子节点;内层循环构建每个隐藏层的每一个神经元节点本身。
- 第28~40行代码:外层循环构建每个隐藏层的偏爱因子节点,如图2-16所示。第35行代码设置隐藏层偏爱因子的节点名称为“+1”。第36行代码设置偏爱因子节点的set_is_bias_unit为True。
- 第42~53行代码:内层循环构建每个隐藏层的神经元节点本身,如图2-17所示。第43行代码:for循环是一个内层循环,每一个隐藏层的神经元节点有多少个取决于传进来的这一层的隐藏层hidden_layers[i]有多少个元素,然后在内层循环里面创建该隐藏层内部的神经元。
第48行代码:设置神经元节点名称。将神经元节点的名称设置为“+1”。
第49行代码:设置神经元节点的set_is_bias_unit为False。因为创建的是神经元节点本身,而不是偏爱因子节点。
图2-16 外层循环构建隐藏层的偏爱因子节点
图2-17 内层循环构建隐藏层的神经元节点本身
构建出神经网络的隐藏层,将每个隐藏层的神经元节点创建出来,每个隐藏层的偏爱因子也创建出来了。
第58~66行代码:构建输出层的节点,如图2-18所示。
图2-18 构建输出层的节点
- 第62行代码:设置输出层节点的名称,将神经元节点命名为Output。
- 第63行代码:设置输出层节点的set_is_bias_unit为False,因为输出层的节点显然不是偏爱因子。
第68行代码:返回create_nodes方法构建的所有节点的结果。在create_nodes方法中创建整个神经网络的所有节点,创建一个数组节点存放所有的节点,从输入层、输出层、隐藏层三个角度来考虑,实现输入层、隐藏层、输出层不同层次的节点创建,create_nodes方法最后返回所有节点的结果。
这样就初步创建了神经网络节点。NetworkStructure.py(v1)代码中创建了输入层的偏爱因子节点、隐藏层所有的偏爱因子节点、隐藏层的所有神经元节点、输出层的节点。注意,这里还没有创建输入层的节点,后续我们将进行迭代完善,加上这部分代码。
做完这一步,下面需要通过一个应用程序看一下构建节点的情况,创建一个Neuron_Network_Entry.py文件构建一个入口,将在Neuron_Network_Entry.py文件调用构建神经网络NetworkStructure.py的方法。
chapter2_Create_AI_Framework的Neuron_Network_Entry.py(v1)的源代码如下:
第6~9行代码:构建输入数据。为了方便读者学习,这里使用二维数组硬编码,二维数组的第一行成员是[0,0,0],第二个成员是[0,1,1],第三个成员是[1,0,1],第四个成员是[1,1,0]。二维数组有4行3列,观察发现,二维数组的四行成员,它的第一列、第二列数字如果不同,第三列为1;第一列、第二列数字如果相同,第三列为0,其实就是逻辑运算中的异或操作(Exclusive OR),机器学习可以通过这个规律预测数据是0还是1,这里的数据只有2列,当然也可以是200列,可从数据中关联出某种关系,基于这种关系推断出结果是0还是1。第1列和第2列是2个特征,第3列是结果。
第11行代码:设置输入特征数。输入数据中有多少个输入特征?这里是除了结果列的其他列的列数。实例instances的第0行的长度是3,但是它的特征是2个,第一列和第二列是特征,第三列是根据某种关系(数学公式或者某种运算法则)得出的结果。
第13行代码:设置隐藏层每一层的神经元节点数,[4,2]表示第一层隐藏层的神经元有4个节点,第二层隐藏层神经元有2个节点。
第15行代码:调用NetworkStructure的create_nodes方法,传入的第一个参数为输入特征数,传入的第二个参数为隐藏层每一层的神经元节点数列表。Python带self与不带self在调用时的区别为:没有加self是静态方法,可以直接调用;如果加上self,必须先创建一个实例,然后调用。
(1)带self,要创建一个实例来调用方法。例如:
(2)不带self,直接调用这个方法。例如:
在Spyder中使用快捷键Ctrl + A全选Neuron_Network_Entry.py的代码,然后使用快捷键Shift + Enter运行代码,在Spyder界面中会显示相应的结果。
在hidden_layers=[4,2]的情况下,Neuron_Network_Entry.py的运行结果如下。简单的创建神经元网络节点的代码运行成功。这里创建了2个隐藏层,第1个隐藏层有4个元素,第2个隐藏层有2个元素。运行结果第一行中的第一个“+1”表示输入层的偏爱因子节点的名字,“Hidden layer creation:”字符串之后的第一个“+1”表示第一个隐藏层的偏爱因子节点的名字,其后的4个“+1”分别表示第一个隐藏层的4个神经元,其中第4个“+1”显示时落到了第二行;运行结果第二行中的“Hidden layer creation:”字符串之后的第一个“+1”表示第二个隐藏层的偏爱节点的名字,其后的2个“+1”分别表示第二个隐藏层的2个神经元。在运行结果的第三行打印输出层的节点名字Output。
在hidden_layers=[4]的情况下,只有1个隐藏层,第1个隐藏层有4个神经元的情况下,Neuron_Network_Entry.py的运行结果如下。第一行中的第一个“+1”表示输入层的偏爱因子节点的名字,“Hidden layer creation:”字符串之后的第一个“+1”表示第一个隐藏层的偏爱因子节点的名字,其后的4个“+1”分别表示第一个隐藏层的4个神经元,其中第4个“+1”显示时落到了第二行;在运行结果的第二行打印输出层的节点名字Output。
为了更清晰地显示运行结果,接下来进行输出结果的格式化。将chapter2_Create_AI_Framework版本的NetworkStructure.py的程序另存为NetworkStructure.py(v1)并修改相关行的代码:
(1)调整NetworkStructure.py(v1)代码中第35行的节点名字,将偏爱因子节点的名字从“+1”修改为i+1。
(2)调整NetworkStructure.py(v1)代码中第48行的节点名字,我们要访问具体的神经元节点本身,要在神经元节点名称中凸显这个节点在什么位置,具体获取神经元节点处于哪一层,及处在这一层中的第几个元素,通过N[i][j]的方式命名。
格式化输出以后的运行结果如下。在hidden_layers=[4,2]时,运行结果第一行中的第一个“+1”表示输入层的偏爱因子的名字;第二行“Hidden layer creation:”后的1是第1层隐藏层的偏爱因子的名字,之后是第一个隐藏层的4个神经元节点的名字;第三行“Hidden layer creation:”后的2是第2层隐藏层的偏爱因子的名字,之后是第2个隐藏层的2个神经元节点的名字。第四行打印输出层的节点名字Output。