一个粗糙的 neural network 框架

一个粗糙的 neural network 框架

之前的一篇博客 利用 matrix derivative 思考 back propagation 介绍了反向传播的公式推导。

主要公式如下: \[
\begin {align}
\cfrac {\partial J} {\partial \mathbf {W}} &= \Big (\cfrac {\partial J} {\partial \mathbf {A}^{[l]}} \ast g'(\mathbf {Z}^{[l]}) \Big) {\mathbf {A}^{[l-1]}}^T \\
\cfrac {\partial J} {\partial \mathbf {b}} &= \Big (\cfrac {\partial J} {\partial \mathbf {A}^{[l]}} \ast g'(\mathbf {Z}^{[l]}) \Big) \mathbf {K}^T \\
\cfrac {\partial J} {\partial \mathbf {A}^{[l-1]}} &= \mathbf {W}^T \Big (\cfrac {\partial J} {\partial \mathbf {A}^{[l]}} \ast g'(\mathbf {Z}^{[l]}) \Big)
\end {align}
\]
看完公式,你应该知道,根据梯度下降,如果我们想更新第 \(l\) 层的 \(\mathbf {W}^{[l]}\) (weight) 和 \(\mathbf {b}^{[l]}\) (bias),就必须得到上面的 \(\cfrac {\partial J} {\partial \mathbf {W}^{[l]}}\)\(\cfrac {\partial J} {\partial \mathbf {b}^{[l]}}\)

而它们和 下一隐层 (准确地说,是前向传播的下一层,反向传播其实可以理解为上一层) 的梯度传递 \(\cfrac {\partial J} {\partial \mathbf {A}^{[l]}}\) 相关,同时还和这一层的输入,上一层的输出 \({\mathbf {A}^{[l-1]}}^T\) 有关,此外,我们还需要这一层的输出用于计算激活函数的梯度 \(g'(\mathbf {Z}^{[l]})\)

有了这些基础知识,我们就知道怎么构建神经网络系统了

1. 基本原理

下图展示了前向传播和反向传播的流程。

所有权重和偏置参数都存放在 parameters 中,parameters["W1"]parameters["b1"] 表示 第 1 层 权重和偏置参数

基本流程如下:

  1. 初始化所有 \(L\) 层的参数

  2. 前向传播,每一层包括 全连接 + 激活函数,每一层我们存储缓存变量 cache = (A, W, b) ,(cache 包含输入矩阵 A,权重矩阵 W 以及偏置矩阵 b),循环 \(L\) 层得到所有缓存变量 caches.append(cache) ,最终输出结果为 AL ,表示第 \(L\) 层输出 (一般为 sigmoid 激活函数)
    注意: 这里小心,第 \(i\) 层对应的缓存变量为 cache[i-1]

  3. 计算损失函数 cost , 对于交叉熵损失函数: \[
    -\frac{1}{m} \sum\limits_{i = 1}^{m} (y^{(i)}\log\left(a^{[L] (i)}\right) + (1-y^{(i)})\log\left(1- a^{[L](i)}\right) )
    \]
    numpy 计算如下:

  4. 后向传播:
    损失函数梯度

    逐层向前计算梯度,公式在前面已经给出
    参数存放在 grads 中,grads["dWi"] , grads["dbi"], dWidbi 表示第 \(i\) 层的参数梯度 (\(i=1,2,\ldots, L\) )
    注意: grads["dAL"] 表示第 \(L\) 层,即输出层往前一层传播的梯度,或者说第 \(L-1\) 层的误差输入梯度,因此 grads["dAL"] 不等于上面的 dAL

  5. 梯度下降法,逐层更新参数,比如更新第一层参数:

2. 前向传播

每层神经网络,前向传播,实现两个函数: \[
\begin {align}
\mathbf {Z}^{[l]} &= \mathbf {W} \mathbf {A}^{[l-1]} + \mathbf {b} \\
\mathbf {A}^{[l]} &= g(\mathbf {Z}^{[l]})
\end {align}
\]
因此我们应该实现两个函数:

该函数实现 全连接相乘,同时我们必须保存中间变量 和 权重、偏置矩阵变量

该函数实现 激活函数计算

两者组合实现 linear_activation_forward(A_prev, W, b, activation) 函数

因此 \(L\) 层网络前向传播如下:

3. 反向传播

由于我们前面存储了 中间变量,现在可以利用公式计算反向传播了,

计算过程如上图所示,利用损失函数对每个权重、偏置变量求偏导。从输出层开始逐层往前计算。

下面是 relu 梯度传播

全连接层的反向传播:

注: 这里为什么出现 1/m 的原因在前篇博客中提到了。

将上面两个函数结合,可以得到如下的函数:


# GRADED FUNCTION: L_model_backward# GRADED 

def L_model_backward(AL, Y, caches):
    """
    Implement the backward propagation for the [LINEAR->RELU] * (L-1) -> LINEAR -> SIGMOID group
    
    Arguments:
    AL -- probability vector, output of the forward propagation (L_model_forward())
    Y -- true "label" vector (containing 0 if non-cat, 1 if cat)
    caches -- list of caches containing:
                every cache of linear_activation_forward() with "relu" (it's caches[l], for l in range(L-1) i.e l = 0...L-2)
                the cache of linear_activation_forward() with "sigmoid" (it's caches[L-1])
    
    Returns:
    grads -- A dictionary with the gradients
             grads["dA" + str(l)] = ... 
             grads["dW" + str(l)] = ...
             grads["db" + str(l)] = ... 
    """
    grads = {}
    L = len(caches) # the number of layers
    m = AL.shape[1]
    Y = Y.reshape(AL.shape) # after this line, Y is the same shape as AL
    
    # 计算误差
    dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
    
    # Lth layer (SIGMOID -> LINEAR) gradients. Inputs: "AL, Y, caches". Outputs: "grads["dAL"], grads["dWL"], grads["dbL"]
    current_cache = caches[L-1]
    grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(dAL, current_cache, 'sigmoid')

    
    for l in reversed(range(L-1)):
        # lth layer: (RELU -> LINEAR) gradients.
        current_cache = caches[l]
        # l 是 cache 的索引,当前是第 (l+1) 层
        # 这里的下标有点容易弄错,注意传入的第一个参数是下一层的dA(按照前向传播的方向看)
        dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads["dA" + str(l+2)], current_cache, 'relu')
        grads["dA" + str(l + 1)] = dA_prev_temp
        grads["dW" + str(l + 1)] = dW_temp
        grads["db" + str(l + 1)] = db_temp

    return grads

返回的 grads 就是每一层的参数梯度

4. update parameter

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: