• 读书笔记:神经网络是如何训练的


    (本文是根据 neuralnetworksanddeeplearning 这本书的第二章How the backpropagation algorithm works整理而成的读书笔记,根据个人口味做了删减)

    在上一章的学习中,我们介绍了神经网络可以用梯度下降法来训练,但梯度的计算方法却没有给出。在本章中,我们将学习一种计算神经网络梯度的方法——后向传播算法(backpropagation)。

    backpropagation 算法起源于上个世纪 70 年代,但一直到 Hinton 等人在 1986 年发表的这篇著名论文后才开始受到关注。BP 算法使得神经网络的训练速度快速提升,因此它是学习神经网络的重中之重。

    热身:一种基于矩阵的快速计算神经网络输出的方法

    在开始讨论 BP 算法之前,我们先回顾一种基于矩阵形式的计算神经网络输出的方法。

    首先,引入几个符号表示。

    假设 (w_{jk}^{l}) 表示从第 l-1 层的第 k 个神经元到第 l 层的第 j 个神经元的权值,如下图所示。

    假设 (b_{j}^{l}) 表示 l 层第 j 个神经元的偏差,(a_{j}^{l}) 表示 l 层第 j 个神经元的激活层,如下图所示:

    有了这些标记,第 l 层的第 j 个神经元的激活层 (a_{j}^{l}) 就可以和 l-1 层的激活层关联起来:

    [a_{j}^l = sigma(sum_{k}{w_{jk}^{l}a_{k}^{l-1}+b_{j}^{l}}) ag{23} ]

    其中,(sigma()) 是一个激活函数,例如 sigmoid 函数之类的。

    现在,为了方便书写,我们为每一层定义一个权值矩阵 (W^l),矩阵的每个元素对应上面提到的 (w_{jk}^{l})。类似地,我们为每一层定义一个偏差向量 (b^l) 以及一个激活层向量 (a^l)

    然后,我们将公式 (23) 表示成矩阵的形式:

    [a^l=sigma(W^la^{l-1}+b^l) ag{25} ]

    注意,这里我们对 (sigma()) 函数做了点延伸,当输入参数是向量时,(sigma()) 会逐个作用到向量的每个元素上(elementwise)。

    在 (25) 式中,有时为了书写的方便,我们会用 (z^l) 来表示 (W^la^{l-1}+b^l)。下文中,(z^l) 将会频繁出现。

    代价函数的两个前提假设

    BP 算法的目标是要计算偏导数 (partial C)/(partial w)(partial C)/(partial b),要让 BP 算法起作用,我们需要两个前提假设:

    1. 代价函数可以表示成 (C=frac{1}{n}sum_{x}{C_x}),其中 (C_x) 是每个训练样本 x 的代价函数。
    2. 代价函数用神经网络的输出作为函数的输入:

    BP 算法背后的四个基本公式

    BP 算法本质上是为了计算出 (partial C) / (partial w_{jk}^{l})(partial C) / (partial b_{j}^{l})。为了计算这两个导数,我们引入一个中间变量 (delta_{j}^{l}),这个中间变量表示第 l 层第 j 个神经元的误差。BP 算法会计算出这个误差,然后用它来计算(partial C) / (partial w_{jk}^{l})(partial C) / (partial b_{j}^{l})

    (delta_{j}^{l}) 被定义为:

    [delta _{j}^{l}=frac{partial C}{partial z_{j}^{l}} ag{29} ]

    这个定义来源于这样一个事实:代价函数 (C) 可以看作是关于 (z) 的函数,而 (z)(W)(b) 的线性组合(考虑到代价函数的两个前提假设,(C) 是关于网络输出 (a) 的函数,而 (a) 又是 (z) 的函数,所以 (C) 也可以看作是 (z) 的函数)。其实,我们也可以将它定义为:(delta_{j}^{l}=frac{partial C}{partial a_{j}^{l}})(a) 是神经网络某一层的输出),但这样会导致之后的计算十分复杂,所以,我们还是保留原先的定义。

    BP 算法基于 4 个基本公式,这些公式会告诉我们如何计算 (delta^{l}) 和代价函数的梯度。

    输出层误差 (delta^{L})的计算公式

    [delta_{j}^{L}=frac{partial C}{partial z_{j}^{L}}=frac{partial C}{partial a_{j}^{L}}sigma'(z_{j}^{L}) ag{BP1} ]

    这个公式是最直接的,只需要知道 (a^{L}=sigma(z^{L})),然后根据链式法则即可得到。

    为了更好地运用矩阵运算,我们改变一下上面式子的形式:

    [delta^{L}= abla_a C odot sigma'(z^L). ag{BP1a} ]

    其中,(odot) 表示 elementwise 运算,而 ( abla_a C) 可以看作是 (partial C / partial a_{j}^{L}) 组成的向量。

    举个例子,假设 (C=frac{1}{2}sum_{j}{(y_j - a_{j}^{L})}^2),则 (partial C / partial a_{j}^{L}=egin{bmatrix} partial C / partial a_0^l \ partial C / partial a_1^l \ vdots \ partial C / partial a_n^l end{bmatrix}=(a_{j}^{L}-y_j)=egin{bmatrix} a_0^l-y_0 \ a_1^l-y_1 \ vdots \ a_n^l-y_l end{bmatrix}),那么公式(BP1)可以表示成:(delta^{L}=(a_{L}-y) odot sigma'(z^L))

    (delta^L)(delta^{L+1})的计算公式

    [delta^L=((w^{l+1})^Tdelta^{l+1}) odot sigma'(z^l) ag{BP2} ]

    前面公式 (BP1) 可以让我们计算出最后输出层 (delta^L) 的值,而 (BP2) 这个公式可以依据最后一层的误差,逐步向前传递计算前面输出层的 (delta^L) 值。

    bias 的导数计算公式

    [frac{partial C}{partial b_j^{l}}=delta_j^l ag{BP3} ]

    这个公式表明,第 l 层偏差 bias 的导数和第 l 层的误差值相等。

    权重 W 的导数计算公式

    [frac{partial C}{partial w_{jk}^{l}}=a_{k}^{l-1}delta_{j}^{l} ag{BP4} ]

    同理,这个公式揭露出权重 W 的导数和误差以及网络输出之间的关系。用一种更简洁的方式表示为:

    [frac{partial C}{partial w} = a_{in}delta_{out} ag{32} ]

    其中,(a_{in}) 是权重 (W) 的输入,而 (delta_{out}) 是权重 (W) 对应的 (z) 的误差。用一幅图表示如下:

    公式 (32) 一个很好的效果是:当 (a_{in} approx 0) 时,梯度公式的值会很小,换句话说,当权重 (W) 的输入 (a_{in}),也就是上一层激活层的输出接近 0 时,那么这个激活层对网络的影响就变得很小,(W) 的学习也会变得很慢。

    一些启发(insights)

    根据上面四个公式,可以发现,当最后输出层的导数 (sigma'(z^L)) 变的很小时(即网络本身已经接近收敛),权重 (W) 和偏差 (b) 会逐渐停止学习(因为误差 (delta) 逐渐趋于 0)。

    当然,不单单是最后一层会影响学习速度,根据公式 (BP2),当中间层的导数 (sigma'(z^l)) 也开始趋于 0 时,那么上一层的误差 (delta^l) 也会趋于 0,从而导致上一层权重 (W) 和偏差 (b) 的学习也会开始停止。

    总之,当 (W) 的输入 (a) 变的很小或者输出层 (sigma(z^l)) 收敛时,网络权值的训练将会变得很慢。

    需要注意的一点是,这四个公式的推导适用于任何激活函数。因此,我们完全可以用其他函数来取代 (sigmoid())。比如,我们可以设计一个函数 (sigma()),这个函数的导数 (sigma'()) 永远为正,且 (sigma()) 函数值永远不会接近 0,那么就可以避免上面提到的学习停止的问题。

    最后,总结一下 BP 的 4 个基本公式:

    个人对于误差以及 BP 的理解

    根据误差 (delta) 的定义,不难发现,它其实就是代价函数关于参数 (W)(b) 的间接导数,这一点跟第一章中对梯度的定义是一致的。当 (delta) 越大时,证明网络还远没有收敛,即网络的「误差」还很大,因此需要学习更多,反之,则证明网络的「误差」比较小,学习可以停止了。

    网络中每一层的误差都需要借助前一层的误差进行计算,这个过程其实是一个导数的叠加过程,可以感性地认为,整个神经网络其实是由一个个函数复合在一起形成的,因此,导数的计算其实就是链式法则的不断应用,前面层神经元的导数需要后面层神经元导数不断叠加,这个过程就构成了后向传播算法。

    公式证明

    BP1

    公式 (BP1) 的证明是十分简单的,不过需要习惯向量或矩阵的 elementwise 的求导形式。

    我们假设 (C=f(sigma(z^L))=f(sigma(z_0^L), sigma(z_1^L), cdots, sigma(z_n^L))),根据定义 (delta_j^L=frac{partial C}{partial z_j^L}),由于 (z_j^L) 只跟 (a_j^L) 相关,于是我们用链式法则可以得到(可以画个网络图帮助理解):

    [delta_j^L=frac{partial f}{partial sigma(z_j^L)}frac{partial sigma(z_j^L)}{partial z_j^L}=frac{partial C}{partial a_j^L}frac{partial a_j^L}{partial z_j^L} ag{38} ]

    其中,(a_j^L=sigma(z_j^L)),我们也可以将它表示成另一种形式:

    [delta_j^L=frac{partial C}{partial a_j^L}sigma'(z_j^L) ag{39} ]

    上式就是 BP1 的形式了。

    BP2

    BP2 需要用到后一层计算出来的 (delta^{l+1}),因此,我们先根据 BP1 得出:(delta_k^{l+1}=frac{partial C}{partial z_k^{l+1}})

    (delta_k^{l}=frac{partial C}{partial z_k^l})(C=f(sigma(z_0^L), sigma(z_1^L), cdots, sigma(z_n^L))) 可以得到:

    [egin{eqnarray} delta_j^{l} & = & frac{partial C}{partial z_0^{l+1}}frac{partial z_0^{l+1}}{partial z_j^{l}}+cdots+frac{partial C}{partial z_n^{l+1}}frac{partial z_n^{l+1}}{partial z_j^{l}} otag \ & = & sum_k{frac{partial C}{partial z_k^{l+1}}frac{partial z_k^{l+1}}{partial z_j^j}} otag \ & = & sum_k delta_k^{l+1}frac{partial z_k^{l+1}}{partial z_j^{l}} ag{42} end{eqnarray} ]

    我们还要进一步找出 (z_k^{l+1})(z_k^{l}) 之间的关系。根据前向传播,可以得到:

    [z_k^{l+1}=sum_j{w_{kj}^{l+1}a_j^l+b_k^{l+1}}=sum_j{w_{kj}^{l+1}sigma(z_j^l)+b_k^{l+1}} ag{43} ]

    进而可以得到:

    [frac{partial z_k^{l+1}}{partial z_j^l}=w_{kj}^{l+1}sigma'(z_j^l) ag{44} ]

    将式 (44) 代入 (42) 得:

    [delta_j^l=sum_k{w_{kj}^{l+1}sigma'(z_j^l)delta_k^{l+1}}=sigma'(z_j^l)sum_k{w_{kj}^{l+1}delta_k^{l+1}} ag{45} ]

    表示成矩阵的形式就是:

    [delta^L=((w^{l+1})^Tdelta^{l+1}) odot sigma'(z^l) ]

    即 BP2 的公式,注意矩阵的转置运算。

    BP3

    [z_j^l=sum_k{W_{jk}^l a_k^{l-1}}+b_j^l ]

    [frac{partial z_j^l}{partial b_j^l}=1 ]

    [frac{partial C}{partial b_j^l}=frac{partial C}{partial z_j^l}frac{partial z_j^l}{partial b_j^l}=frac{partial C}{partial z_j^l}=delta_j^l ]

    BP4

    证明过程同 BP3:

    [z_j^l=sum_k{W_{jk}^l a_k^{l-1}}+b_j^l ]

    [frac{partial z_j^l}{partial W_{jk}^l}=a_k^{l-1} ]

    [frac{partial C}{partial W_{jk}^l}=frac{partial C}{partial z_j^l}frac{partial z_j^l}{partial W_{jk}^l}=frac{partial C}{partial z_j^l}a_k^{l-1}=delta_j^la_k^{l-1} ]

    后向传播算法(BP)

    1. Input x: Set the corresponding activation (a^1) for the input layer.
    2. **Feedforward: ** For each l = 2, 3, …, L compute (z^l=w^la^{l-1}+b^l) and (a^l=sigma(z^l)).
    3. **Output error **(delta^L): Compute the vector (delta^L= abla_a C odot sigma'(z^L)).
    4. **Backpropagate the error: **For each l = L-1, L-2, …, 2 compute (delta^l=((W^{l+1})^T delta^{l+1}) odot sigma'(z^l)).
    5. **Output: **The gradient of the cost function is given by (frac{partial C}{partial w_{jk}^l}=a_k^{l-1}delta_j^{l}) and (frac{partial C}{partial b_j^l}=delta_j^l).

    以上算法是针对一个训练样本进行的,实际操作中,通常是用随机梯度下降算法,用几个样本进行训练,因此我们将算法略微修改如下:

    1. Input a set of training examples
    2. **For each training example **x: Set the corresponding input activation (a^{x, 1}), and perform the following steps:
      • **Feedforward: **For each l = 2, 3, …, L compute (z^{x, l}=w^la^{x, l-1}+b^l) and (a^{x, l}=sigma(z^{x,l})).
      • **Output error **(delta^{x, L}): Compute the vector (delta^{x, L}= abla_a C_x odot sigma'(z^{x,L})).
      • **Backpropagate the error: **For each l = L-1, L-2, …, 2 compute (delta^{x,l}=((W^{l+1})^T delta^{x,l+1}) odot sigma'(z^{x,l})).
    3. **Gradient descent: **For each l = L, L-1, …, 2 update the weights according to the rule (W^l ightarrow W^l-frac{eta}{m} sum_x delta^{x,l}(a^{x,l-1})^T), and the biases according to the rule (b^l ightarrow b^l - frac{eta}{m} sum_x{delta^{x,l}}).

    参考

  • 相关阅读:
    AJAX
    大前端面试一(基础)
    webpack打包vue -->简易讲解
    vue实现原理
    Spring boot 线上部署
    javascript 事件
    React native采坑路 Running 1 of 1 custom shell scripts
    PHP swoole实现redis订阅和发布
    JAVA 注解和反射
    微信公众平台获取用户地理位置之开发文档详解
  • 原文地址:https://www.cnblogs.com/jermmyhsu/p/8195937.html
Copyright © 2020-2023  润新知