企业级AI技术内幕:深度学习框架开发+机器学习案例实战+Alluxio解密
上QQ阅读APP看书,第一时间看更新

4.2 实现反向传播算法

接下来我们将反向传播的过程以代码的方式实现。

(1)写一个接口程序BackPropagation.py,BackPropagation.py的实现将和我们前面描述的反向传播的过程是一样的。applyBackProgation方法的参数中,instances是所有的输入数据,nodes是所有的节点,weights是权重,learning_rate是学习率。进行一次完整的训练以后将结果返回,返回的结果是所有的节点及更新的权重。训练以后返回一个训练好的神经网络,这是TensorFlow静态图的概念,PyTorch在训练以后也可以调整,这也是PyTorch最吸引人的地方。这里返回的是节点和权重,下次在输入数据的时候,把输入数据经过权重处理,再进入下一层的节点;然后把数据再经过权重处理,再进入下一层的节点……。最后计算出结果。理想情况下,计算出的结果和实际的结果是一样的。

chapter4_Create_AI_Framework版本的BackPropagation.py接口:

(2)我们在Neuron_Network_Entry.py中要应用BackPropagation.py,一般情况下要运行很多次时代。这里假设时代为1万次,对所有训练的数据集重复运行1万次;learning_rate是一个调优参数,在优化的时候使用,这里设置为0.1,我们在chapter3_Create_AI_Framework版本的基础上对Neuron_Network_Entry.py进行修改。

chapter3_Create_AI_Framework版本的Neuron_Network_Entry.py代码如下:

chapter4_Create_AI_Framework版本的Neuron_Network_Entry.py,与chapter3_Create_AI_Framework的版本相比:

(1)在第4行之后新增BackPropagation类的导入。

(2)在第22行之后新增epoch、learning_rate变量的定义及调用BackPropagation.applyBackPragation方法。

神经网络通过BackPropagation.applyBackProgation方法训练好以后,权重都达到了最佳状态,然后对输入数据再应用一次applyForwardPropagation,将达到最佳的预测结果。这个预测结果肯定比没有使用反向传播算法的结果精确得多,否则表明梯度下降算法有问题,而梯度下降的算法基本不会有问题,因为它们都是一些数学大家几十年的研究成果。例如Sigmoid函数的梯度下降的求导和ReLU函数的梯度下降的求导,它们是不同时期的研究成果。二者有很大的不同,例如适用场景不一样,Sigmoid在一些情况下得到的测试结果并不理想,而使用ReLU算法则是非常理想的。人工智能背后的理论非常复杂,对于人工智能框架的应用而言,Python的一些框架已经将这些数学公式进行了封装,可以直接通过框架的接口进行调用。

(3)BackPropagation.py的实现。

反向传播算法是神经网络最精髓的部分。我们的盘古框架的反向传播算法的原理和TensorFlow与PyTorch的是一样的。

BackPropagation.py的实现思路如下。

第一步:从误差出发,从最右侧到最左侧(不包含输出层)遍历整个神经元网络构成的链条。

第二步:在遍历的时候计算每个神经元对误差结果应该负的责任。

第三步:进行求导计算。

第四步:通过梯度下降算法来调整权重的值以减少预测的误差。

chapter4_Create_AI_Framework版本的BackPropagation.py代码涉及ForwardPropagation.py及Node.py的代码的使用,在chapter3_Create_AI_Framework版本的基础上进行改进。

chapter3_Create_AI_Framework版本的ForwardPropagation.py的源代码如下:

chapter4_Create_AI_Framework版本的ForwardPropagation.py代码与chapter3_Create_AI_Framework版本的相比:

在代码段第4行后新增代码,用于返回神经元节点信息。

chapter3_Create_AI_Framework版本的Node.py代码如下:

chapter4_Create_AI_Framework版本的Node.py代码,与chapter3_Create_AI_Framework版本的相比:

在代码段第5行后增加代码,新增set_minor_error方法设置当前神经元对前向传播算法导致的损失度的具体责任,新增get_minor_error代码获取当前神经元对发生的前向传播算法导致的损失度的具体责任。

接下来实现反向传播算法BackPropagation.py中每个神经元节点误差值的代码编写。

chapter4_Create_AI_Framework版本的BackPropagation.py的源代码如下:

第2行代码:导入service.ForwardPropagation的ForwardPropagation类。

第5行代码:记录输入的特征的个数。实例有4条记录,len(instances[0])可获取第一条记录的长度,由于实例的最后一列是实际结果值,因此计算len(instances[0])–1为输入的特征个数。

第7~41行代码:在一个时代中,循环遍历所有的训练集数据,记录每个节点所负责的误差。

第10行代码:执行一次前向传播算法,通过输入层、隐藏层、输出层,获取神经元节点集。

第12行代码:获取神经网络的预测值。盘古人工智能框架applyForwardPropagation的前向传播算法过程中,实例集每一行记录有2个特征值,输入实例集数据,从输入层开始,经过若干的隐藏层(例如hidden_layers=[8,4,2]),最后得出输出层的值。最后一个节点的值是预测值。

第15行代码:获得当前实例的实际值。

第16行代码:计算预测值和真实值之间的误差。

第17行代码:设置输出层输出节点的误差值。

第21~41行代码:循环遍历节点,计算除输入层节点外的每个节点要负的责任。

第21行代码:range函数的计数起始位置从最后一个隐藏层的最后一个神经元节点开始,因为输出节点已经计算完误差,所以将神经网络节点长度减2;range函数的计数终点位置是输入特征数num_of_features,因为输入层的偏爱因子及输入层的神经元节点不参与误差计算。range函数的步长为–1,表明是倒序取值,从后往前推,每次往前进行一个节点,但是不需要计算输入层的节点。

第22行代码:神经元节点本身的index,基于节点索引target_index获取权重的反推链。

第25~39行代码:循环遍历所有的权重来获得以target_index为出发点的所有权重,计算每个神经元误差的总和,记录每个神经元最终负的责任。

第41行代码:设置节点的误差值。

反向传播算法误差计算过程中,actual_value表示实际值(实例集最后的一列),predicted_value表示预测值,预测值和实际值之间的误差值用minor_error表示。如果我们的隐藏层设置为hidden_layers=[8,4,2],首先计算输出层节点的误差值minor_error,然后从后往前循环遍历神经网络的神经元节点(输入层的x1x2除外)。例如,计算第三层隐藏层第二个神经元节点N[3][2]的误差,其误差值为affecting_theta * affected_minor_error,即N[3][2]节点与输出节点的连接权重乘以输出节点的误差值minor_error;同理,也可以计算出N[3][1]节点的误差,如图4-6所示。

又如计算第二层隐藏层的第一个神经元节点N[2][1]节点的误差值(见图4-7),先获取N[3][1]节点的误差,乘以N[2][1]与N[3][1]之间的连接权重(),再获取N[3][2]节点的误差,乘以N[2][1]与N[3][2]之间的连接权重(),然后将两者相加计算出N[2][1]节点的误差值。

隐藏层的网络节点如下:

这样通过循环遍历计算每个神经元(输入层的x1x2除外)对误差结果应该负的责任。

实现反向传播功能最关键的地方是使用梯度下降算法进行求导来调整权重。因为我们在前向传播算法中使用的是Sigmoid激活函数,所以这里是对Sigmoid函数进行求导。导数的变化率根据梯度或误差进行计算,梯度根据链式推导从后往前推,以减小梯度或误差。最后得出的结论是误差由输入数据和最后一个神经元的值导致,最后一个神经元的值是根据前一个神经元的值乘以权重导致的,而前一个神经元的值又是根据它的前一个神经元的值乘以权重导致的,而这个神经元的值又是根据输入层的值乘以权重导致的,而输入的值来自训练数据集,输入数据的值是不变的。因此,到底是什么导致误差?是权重的分配导致了误差!构建神经元网络的时候,初始化权重为(–1,1),由输入的数据经计算得出最后的结果,导致误差最终的原因是由于权重!从计算的角度讲,由于导致误差是因为权重不准确,所以权重需要调整。

图4-6 计算N[3][2]节点的误差

图4-7 计算N[2][1]节点的误差

chapter4_Create_AI_Framework版本的BackPropagation.py梯度下降代码如下:

计算出神经元各节点的误差后,接下来在反向传播算法中应用梯度下降算法进行求导,更新调整权重。我们在前向传播计算中使用的是Sigmoid激活函数,即f(x)=1/(1+e-x),因此在反向传播算法中对Sigmoid进行求导。求导公式为:df(x)/dx=f(x)(1–f(x)),如图4-8所示。

图4-8 梯度求导

通过循环遍历每一个权重,再从前往后依次更新权重。在上面的代码中第21行、第22行为梯度计算的代码,更新权重的时候是将导数乘以学习率参数,通过第25行代码更新权重,这是下次计算的时候能够更加准确的关键,因为已把上面的计算成果运用在调整神经元网络的权重上了。