4.1 深度学习是如何学习的
本节我们将实现盘古人工智能框架的反向传播功能,在项目中新增BackPropagation.py程序。完成后的目录结构如图4-1所示。
图4-1 chapter4_Create_AI_Framework目录结构
我们先运行chapter3_Create_AI_Framework版本的Neuron_Network_Entry.py程序,运行结果如下:
这里预测的结果有4条记录,或者说有4个输入对象,每个输入对象对两个特征(即实例集的第1列、第2列)构成的关系进行了预测。
实例集第1条记录[0,0,0]实际的结果是0,第2条记录[0,1,1]实际的结果是1,第3条记录[1,0,1]实际的结果是1,第4条记录[1,1,0]实际的结果是0;但这里预测的结果分别是0.434 227 773 156 020 07、0.430 335 772 698 682 34、0.434 791 142 332 109 86、0.430 689 935 803 851 8,显然有很大的误差。这里Neuron_Network_Entry.py代码运行时的hidden_layers=[8,4,2],表示网络层一共有5层,输入层1层,隐藏层3层(其[8,4,2]的隐藏层分别包括8个神经元、4个神经元、2个神经元),输出层1层,一共是5层。
再次启动TensorFlow团队提供的可视化TensorFlow网络的观测平台,我们的实现和TensorFlow可视化运行图中的内容是一样的:在TensorFlow的控制台增加隐藏层及神经元,第一层隐藏层有8个神经元,第二层隐藏层有4个神经元,第三层隐藏层有2个神经元。在不训练的情况下,TensorFlow直接从左侧到右侧运行1次,测试损失度和训练损失度在50%左右(测试损失度是0.505,训练损失度是0.503)。
如图4-2所示,在TensorFlow的控制台,选择Sigmoid激活函数并运行,运行的时候发现这个场景不太适合使用Sigmoid函数,因为训练损失度和测试损失度持续在50%左右。改用ReLU激活函数后,发现ReLU函数的收敛速度非常快,训练损失度和测试损失度误差越来越小。运行程序的时候1次时代就是把所有的输入数据运行一遍,如果运行了101次时代,相当于把所有的数据重复运行了101次。一个数据集运行一次,假设有1亿条数据,就相当于把所有的记录运行1次,运行1次就叫一个数据集运行1次时代。我们这里只有4条记录,把4条记录完整地运行1次,也就是1次时代。然后不断循环迭代,使训练损失度和测试损失度不断收敛,误差越来越小。
图4-2 TensorFlow可视化运行图
这里运行的结果中第一个预测值是0.434 227 773 156 020 07,但实例集中实际值是0,预测值显然和真实结果有误差。
为了形象化表达预测的结果,这里修改一下Neuron_Network_Entry.py的代码。
chapter3_Create_AI_Framework版本的Neuron_Network_Entry.py的源代码如下:
chapter4_Create_AI_Framework版本的Neuron_Network_Entry.py与chapter3_Create_AI_Framework版本的相比:
调整代码第8行print的内容,新增实例实际值的展示。实例的索引从0开始,实例的第0列、第1列是2个特征,num_of_features是2,instance[num_of_features]取索引2的列,即实例的最后一列。
重新运行Neuron_Network_Entry.py,从输入层、3个隐藏层,再到输出层运行1次计算过程,得到的预测结果如下。这个过程使用的是前向传播算法。
预测值和实际值显然有误差,第1个预测值0.397 646 891 907 928 35是一个从输入层到3个隐藏层,再到输出层的计算过程,第一次运行得到的结果,而实际值是0;同样,第2个预测值是0.398 828 992 222 874 3,而实际值是1;第3个预测值是0.396 656 767 344 808,而实际值是1;第4个预测值是0.398 216 486 318 950 74,而实际值是0。
人工智能系统的学习过程和人类的学习过程一样,你要么从过去的经验中学到一些知识,然后改进,要么就视而不见。人工智能系统的强大之处在于可以根据预测结果和实际值的误差反推之前的步骤,沿着从后往前的链条不断往前推,找到前面哪些步骤对结果产生了影响,有多大,哪些因素导致了最后这个结果。这个过程跟人类反思的过程是一样的,反思过去哪些因素导致了这个结果,修正错误,并期待下一次做同样事情的时候能够改进。这也是反向传播算法的思路。
我们从后往前推,看哪些因素对结果产生了影响以及影响的程度,要改进的是权重。此时不需要改输入的数据和中间层神经元的数据。从后往前推的时候改的是权重。但是当下一次运行程序的时候,输入实例集的第一条数据[0,0],由于都是0,所以运行结果没什么变化;而当输入实例集的第二条数据[0,1]、第三条数据[1,0]等的时候,由于权重已经发生了变化,从理论上讲,进行前向传播的第一个隐藏层所有神经元的数据要随着权重的改变而改变,与第1次时代的内容不一样了。由于权重改变了,而第二层隐藏层的值是由前一个隐藏层的值和相应的权重构成的,所以第二层隐藏层的值也会改变;同样,第三层隐藏层的值也会改变;由于第三层隐藏层的值改变了,且权重发生了改变,所以最后的输出层值也会发生改变。如果反向传播算法没有问题,预测值会由此改进。
在TensorFlow的控制台,我们恢复TensorFlow为初始状态,可以看到第三层隐藏层第一个神经元的权重是0.090,第二个神经元的权重是0.029,如图4-3所示。
图4-3 初始权重
类似地,盘古人工智能框架所有的初始状态赋值是在chapter2_Create_AI_Framework版本的NetworkConnection.py中实现的,是我们在2.3节中编写盘古人工智能框架代码时赋值的。
这里TensorFlow控制台中的0.090、0.029是初始网络状态赋值,单击TensorFlow控制台的运行按钮运行程序。这回运行了19次时代,把所有的数据运行了19次,发现第三层隐藏层第一个神经元的权重变了,从0.090变成了–1.2,第二个神经元的权重也变了,从0.029变成了–0.032,如图4-4所示。
图4-4 权重发生变化
每次时代会把训练的数据运行一遍,由于权重改变了,输出结果也改变了。输出结果改变,我们期待是向好的方向转变,而反向传播的算法就是为让它更准确,更向实际靠近,这也是神经网络最核心、最精髓的部分。
这里描述的反向传播算法过程其实就是梯度下降(Gradient Descendent)。梯度下降是指根据误差结果反推过去哪些行为要对结果负责,然后调整那些行为;调整的过程就是梯度下降过程。从数学的角度讲就是求导数,导数计算的是变化率,根据这个变化率进行调整。
回到TensorFlow的控制台,1条数据运行1次,从前往后运行产生一个误差,再从后往前看,我们看的是权重。权重代表影响因子对结果的重要性。从后往前推的过程像是一条链,会遍历每个神经元。例如,最后一个隐藏层只有2个神经元,从后往前推,看这2个神经元对误差的结果负责的程度,第一个神经元对输出结果的误差负责到什么程度,第二个神经元对输出结果的误差负责到什么程度。
为加深理解,我们运行Neuron_Network_Entry.py,看一下盘古人工智能框架的运行结果:
第一条预测值是0.477 510 246 526 957 06,实际结果是0,误差可以采用实际值和预测值相减,或者方差等计算方法。如果直接相减,0.477 510 246 526 957 06 – 0=0.477 510 246 526 957 06。那么这个误差是怎么导致的?这是最大的理解难点。回到TensorFlow的控制台,我们从后往前推看哪个节点要对误差负责的时候,要分别乘以它的权重。例如,第三层隐藏层第一个神经元对误差负责多少,就是误差乘以这个神经元和输出结果关联的权重;第三层隐藏层第二个神经元对误差负责多少,也是误差乘以这个神经元和输出结果关联的权重。根据权重计算对误差负多大的责任。接下来我们做的第一步就是看权重计算对误差负多大的责任。
我们看一个学习考试的例子。如图4-5所示,期末考试的综合成绩由作业和期末考试两个因子计算得出,这里作业因子、期末考试因子可理解为TensorFlow的可视化示意图中最后一个隐藏层的第一个神经元和第二个神经元。期末考试的综合成绩是输出层的结果,其中作业占15%,期末考试占30%,如果期末考试记录的实际值是100分,期末考试的预测值是60分,误差是100–60=40分。那么,每个神经元要对这40分的误差负多大的责任?2个神经元分别对40分的误差负15%、30%的责任,计算每个神经元负的责分别为40×15%=6分,40×30%=12分,即最后一个隐藏层的第一个神经元对40分要负责6分,最后一个隐藏层的第二个神经元对40分要负责12分,这是对神经网络最重要的理解。然后怎么调整权重,这是个求导的过程。计算误差是第一步,误差代表变化,知道误差以后,对误差进行求导,求导以后再调整权重。
图4-5 各神经元对误差承担的责任
总结一下:
第一步计算误差。从后往前推,这里计算隐藏层第三层的2个神经元对结果误差负责的程度,然后计算隐藏层第二层的4个神经元对第三层的2个神经元负责的程度,然后再往前推,依次往前推,得到一个结果。每个神经元对误差的结果分别负责,这是整个神经网络的反向传播算法的第一个步骤。注意,千万不要改变输入数据x1、x2的值。如果改变了输入数据x1、x2,这一切都没什么意义了。
第二步是梯度下降计算的过程。第一步只是从后往前遍历一遍,看谁对结果负多大程度的责任。第二步是确定在所负责任中权重的改变率是多少,而不是数量。这是梯度下降的过程,也是求导的过程。
以上过程的核心是从经验中进行反思,对神经元进行了某种处理,首先是看不同的元素负多大的责任,然后根据变化率梯度求导将责任变得越来越小,当所有的神经元对结果所负的责任变得越来越小的时候,误差也就越来越小。如果隐藏层第三层的2个神经元对结果计算没有影响,那么这2个神经元就没有责任。