python神经网络编程

目录

学习笔记:《python神经网络编程》-Tariq Rashid

神经网络如何工作

思路

多层神经网络,每一层中的神经元都与前后层的神经元互相连接,不采用创造性的方式将神经元连接起来,原因有两点:

  1. 第一是这种一致的完全连接方式事实上可以相对容易地编码成计算机指令;
  2. 第二是神经网络的学习过程将会弱化这些实际上不需要的连接(也就是这些连接的权重将趋于0),因此对于解决特定任务所需最小数量的连接冗余几个连接,也无伤大雅。

方法

使用矩阵乘法来进行神经网络的计算:

  1. 通过神经网络向前馈送信号所需的大量运算可以表示为矩阵乘法;
  2. 不管神经网络的规模如何,将输入输出表达为矩阵乘法,使得我们可以更简洁的进行书写;
  3. 更重要的是,一些计算机编程语言理解矩阵运算,并认识潜在的计算方法的相似性。并允许计算机高速高效地进行这些计算。

计算过程

以3层神经网络为例:
Xhidden=Winputhidden.I 输入层到隐藏层: X_{hidden} = W_{input-hidden} . I 输入层到隐藏层:Xhidden​=Winput−hidden​.I
Ohidden=sigmoid(Xhidden) 隐藏层输出矩阵: O_{hidden} =sigmoid(X_{hidden}) 隐藏层输出矩阵:Ohidden​=sigmoid(Xhidden​)
Xoutput=Whiddenoutput.Ohidden 输出层的组合调节输入信号: X_{output} = W_{hidden-output} . O_{hidden} 输出层的组合调节输入信号:Xoutput​=Whidden−output​.Ohidden​
Ooutput=sigmoid(Xoutput) 输出信号: O_{output} = sigmoid(X_{output}) 输出信号:Ooutput​=sigmoid(Xoutput​)

下一步,将神经网络的输出值与训练样本中的输出值进行比较,计算出误差。我们需要利用这个误差值来调整神经网络本身,进而改进神经网络的输出值。

多个输出节点反向传播误差

  1. 将输出误差标记为eoutpute_{output}eoutput​,将在输出层和隐藏层之间的链接权重标记为whow_{ho}who​。通过将误差值按权重的比利进行分割,我们计算出与每条链接相关的特定误差值。将与隐藏层节点相关联的这些误差标记为ehiddene_{hidden}ehidden​,再次将这些误差按照输入层和隐藏层之间的链接权重wihw_{ih}wih​进行分割。
    python神经网络编程
  2. 但是,对于隐藏层的节点,我们没有目标值或希望的输出值。我们只有最终输出层节点的目标值,这个目标值来自于训练样本数据。
    实际上,我们可以重组这两个链接的误差,形成这个节点的误差(可以理解为过程中误差),如下图所示:
    python神经网络编程
    ehidden,1e_{hidden,1}ehidden,1​ = 链接w1,1w_{1,1}w1,1​和链接w1,2w_{1,2}w1,2​上的分割误差之和
    = eoutput,1w1,1w1,1+w2,1e_{output,1} * \frac{w_{1,1}}{w_{1,1} + w_{2,1}}eoutput,1​∗w1,1​+w2,1​w1,1​​ + eoutput,2w1,2w1,2+w2,2e_{output,2} * \frac{w_{1,2}}{w_{1,2} + w_{2,2}}eoutput,2​∗w1,2​+w2,2​w1,2​​
    可以按照此方法继续向后传播误差,得到输入层的误差
    对于计算机语言而言,即可用矩阵的方法来计算:
    errorhidden=WhiddenoutputT.erroroutput error_{hidden} = W^T_{{hidden-output}} . error_{output} errorhidden​=Whidden−outputT​.erroroutput​

如何更新权重

到目前为止,我们已经理解了让误差反向传播到网络到每一层。接下来就是如何使用误差来指导如何调整链接权重,从而改进网络输出的总体结果。
思考一下:假如有一个3层、每层3个神经元的神经网络,如何调整输入层第一个节点和隐藏层第二个节点之间链路的权重,以使得输出层第三个节点的输出增加到0.5呢?即使我们碰运气做到了这一点,这个效果也会由于需要调整另一个权重来改进不同的输出节点而被破坏。
神经网络的误差函数取决于许多的权重参数,这些参数通常有数百个!

最优化权重参数的方法:梯度下降

  • 梯度下降法是求解函数最小值的一种很好的方法,当函数非常复杂困难,并且不能轻易使用数学代数求解函数时,这中方法可以发挥很好的作用;
  • 更重要的是,当函数有很多参数,一些其他方法不切实际,或者会得出错误答案,这种方法依然可以适用;
  • 这种方法也具有弹性,可以容忍不完善的数据,如果我们不能完美的描述函数,或我们偶尔意外走错一步,也不会错的离谱。

选择差的平方,即()2(目标值-实际值)^2(目标值−实际值)2,即(tkok)2(t_k-o_k)^2(tk​−ok​)2,不选择绝对值误差的原因:

  • 使用误差的平方,我们可以很容易使用代数计算出梯度下降的斜率;
  • 误差函数连续平滑,这使得梯度下降法很好的发挥作用——没有间断,也没有突然的跳跃;
  • 越接近最小值,梯度越小,这意味着,如果我们使用这个函数调节步长,超调的风险就会变得较小。

用图形来演示:
python神经网络编程
当函数具有多个参数时,要画出误差曲面相对较难,但是使用梯度下降寻找最小值的思想是相同的。让我们使用数学的方式写下想要取得的目标:
Ewj,k \frac{\partial E}{\partial w_{j,k}} ∂wj,k​∂E​
首先,展开误差函数,这是对目标值和实际值之差对平方进行求和,这是针对所有n个输出节点对和。
Ewj,k=wj,kn(tnon)2 \frac{\partial E}{\partial w_{j,k}} = \frac{\partial}{\partial w_{j,k}} \sum_{n}(t_n-o_n)^2 ∂wj,k​∂E​=∂wj,k​∂​n∑​(tn​−on​)2
注意,在节点k的输出oko_kok​只取决于连接到这个节点的链接wj,kw_{j,k}wj,k​,而不依赖于权重wj,bw_{j,b}wj,b​,其中,b和k之间没有链接,因此b于k无关联。权重wj,bw_{j,b}wj,b​是连接输出节点b的链接权重,而不是输出节点k的链接权重。这意味着,除了权重wj,kw_{j,k}wj,k​所链接的节点(也就是oko_kok​)外,我们可以从和中删除所有的ono_non​,即:
Ewj,k=wj,k(tkok)2 \frac{\partial E}{\partial w_{j,k}} = \frac{\partial}{\partial w_{j,k}} (t_k-o_k)^2 ∂wj,k​∂E​=∂wj,k​∂​(tk​−ok​)2
目标值tkt_ktk​是一个常数,上面表达式可表示为:(ojo_joj​是隐藏层节点的输出)
Ewj,k=Eok.okwj,k=2(tkok).okwj,k=2(tkok).wj,ksigmoid(jwj,k.oj) \frac{\partial E}{\partial w_{j,k}} = \frac{\partial E}{\partial o_k} . \frac{\partial o_k}{\partial w_{j,k}}=-2(t_k-o_k).\frac{\partial o_k}{\partial w_{j,k}}=-2(t_k-o_k).\frac{\partial}{\partial w_{j,k}}sigmoid(\sum_jw_{j,k}.o_j) ∂wj,k​∂E​=∂ok​∂E​.∂wj,k​∂ok​​=−2(tk​−ok​).∂wj,k​∂ok​​=−2(tk​−ok​).∂wj,k​∂​sigmoid(j∑​wj,k​.oj​)
根据微分的知识:
xsinmoid(x)=sigmoid(x)(1sigmoid(x)) \frac{\partial}{\partial x}sinmoid(x) = sigmoid(x)(1-sigmoid(x)) ∂x∂​sinmoid(x)=sigmoid(x)(1−sigmoid(x))
则:
Ewj,k=2(tkok).sigmoid(jwj,k.oj)(1sigmoid(jwj,k.oj)).wj,k(jwj,k.oj) \frac{\partial E}{\partial w_{j,k}} =-2(t_k-o_k).sigmoid(\sum_jw_{j,k}.o_j)(1-sigmoid(\sum_jw_{j,k}.o_j)).\frac{\partial}{\partial w_{j,k}}(\sum_jw_{j,k}.o_j) ∂wj,k​∂E​=−2(tk​−ok​).sigmoid(j∑​wj,k​.oj​)(1−sigmoid(j∑​wj,k​.oj​)).∂wj,k​∂​(j∑​wj,k​.oj​)
=2(tkok).sigmoid(jwj,k.oj)(1sigmoid(jwj,k.oj)).oj =-2(t_k-o_k).sigmoid(\sum_jw_{j,k}.o_j)(1-sigmoid(\sum_jw_{j,k}.o_j)).o_j =−2(tk​−ok​).sigmoid(j∑​wj,k​.oj​)(1−sigmoid(j∑​wj,k​.oj​)).oj​
这个表达式描述了误差函数的斜率,正负表示斜率的方向。我们只关心斜率的方向,可以把2去掉。式子第一部分,是(目标值-实际值)可用eje_jej​来表示;中间部分,sigmoid中的求和表达式就是进入最后一层节点的信号,可称之为iki_kik​,这是应用激活函数之前,进入节点的信号;最后一部分是是前一隐藏层节点j的输出。

现在,我们可以使用这个表达式,在应用每层训练样本后,更新权重。注意权重的改变方向与梯度的方向相反:
new wj,k=old wj,kαEwj,k new\ w_{j,k} = old\ w_{j,k} - \alpha \frac{\partial E}{\partial w_{j,k}} new wj,k​=old wj,k​−α∂wj,k​∂E​
注:如果斜率为正,我们希望减小权重,如果斜率为负,我们希望增加权重,因此,要对斜率取反。符号α\alphaα是一个因子,称为学习率。
这个表达式不仅适用于隐藏层和输出层之间的权重,而且适用于输入层和隐藏层之间的权重。差值就是误差梯度,我们可以使用上述两个表达式来计算误差梯度。

(w1,1 w2,1 w3,1 ...w1,2 w2,2 w3,2 ...w1,3 w2,3 wj,k ...... ... ...  ... )=(E1S1(1S1)E2S2(1S2)EkSk(1Sk)...).(o1 o2 oj ...) \begin{pmatrix} \triangle w_{1,1}\ \triangle w_{2,1}\ \triangle w_{3,1} \ ... \\ \triangle w_{1,2}\ \triangle w_{2,2}\ \triangle w_{3,2} \ ...\\ \triangle w_{1,3}\ \triangle w_{2,3}\ \triangle w_{j,k}\ ... \\ ...\quad \ ...\ \quad ...\ \ \quad ...\ \quad \\ \end{pmatrix} = \begin{pmatrix} E_1*S_1(1-S_1)\\ E_2*S_2(1-S_2)\\ E_k*S_k(1-S_k)\\ ...\\ \end{pmatrix}. \begin{pmatrix} o_1\ o_2\ o_j\ ...\\ \end{pmatrix} ⎝⎜⎜⎛​△w1,1​ △w2,1​ △w3,1​ ...△w1,2​ △w2,2​ △w3,2​ ...△w1,3​ △w2,3​ △wj,k​ ...... ... ...  ... ​⎠⎟⎟⎞​=⎝⎜⎜⎛​E1​∗S1​(1−S1​)E2​∗S2​(1−S2​)Ek​∗Sk​(1−Sk​)...​⎠⎟⎟⎞​.(o1​ o2​ oj​ ...​)
上式右边:下一层的值.前一层的值
由于学习率只是一个常数,并没有真正改变如何组织矩阵乘法,因此我们省略了α\alphaα。权重改变矩阵中包含的值,这些值可以调整链接权重wj,kw_{j,k}wj,k​,这个权重链接了当前层节点j与下一层节点k。表达式中的第一项使用下一层节点k的值,最后一项使用前一层节点j的值。仔细观察最后一项是单行的水平矩阵,是前一层ojo_joj​的输出的转置。因此,权重更新矩阵有如下的矩阵形式,可以用计算机语言进行计算:
wj,k=α.Ek.Ok(1Ok).OjT \triangle w_{j,k} = \alpha .E_k.O_k(1-O_k).O_j^T △wj,k​=α.Ek​.Ok​(1−Ok​).OjT​
由于我们简化了节点输出oko_kok​,那些sigmoid已经消失了。

python实现

待更新

上一篇:基于Java语言的国密SM2/SM3/SM4算法库 , 包含加密/解密、签名/验签、摘要计算的实现代码和测试方法


下一篇:【BFS】WJ的逃离