2020-6-25 吴恩达-NN&DL-w4 深层NN(课后编程1-Building your Deep Neural Network: Step by Step)

原文链接

如果打不开,也可以复制链接到https://nbviewer.jupyter.org中打开。

在前面你已经学习了训练单隐层NN

本节课你将搭建一个深层NN,至于用多少层,你说了算。

  • 本节中,你将实现几个函数用于搭建深层NN
  • 这些函数将在下节编程中用于搭建一个深层NN,实现图像识别分类

完成本节编程后,你可以

  • 使用非线性单元ReLU改善你的模型
  • 使用超过一个隐藏层搭建深度NN
  • 实现一个方便使用的NN分类器

符号说明

  • 上标[l][l]表示第 ll
    例如: a[L]a^{[L]}表示第 LL 层的激活函数;W[L]W^{[L]}b[L]b^{[L]}都是第 LL 层的参数。

  • 上标(i)(i) 表示第 ii 个样本
    例如:x(i)x^{(i)}是第 ii 个训练样本

  • 小写 ii 表示向量的第 ii 个输入
    例如: ai[l]a^{[l]}_i 表示第 ll 层激活函数的 第 ii 个输入

1.本文涉及的基本库

  • numpy :是用Python进行科学计算的基本软件包
  • matplotlib:是一个著名的库,用于在Python中绘制图表
  • dnn_utils_v2:提供了在本作业中会使用的一些必要的函数
  • testCases_v2:提供了一些测试样本来评估你的函数的正确性
  • np.random.seed(1): 用于保持所有随机函数的一致性. 请不要修改
import numpy as np
import h5py
import matplotlib.pyplot as plt
from testCases_v2 import * 
from dnn_utils_v2 import sigmoid, sigmoid_backward, relu, relu_backward  


#%matplotlib inline #Jupyter Notebook中使用
plt.rcParams['figure.figsize'] = (5.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

#%load_ext autoreload #Jupyter Notebook中使用
#%autoreload 2 #Jupyter Notebook中使用

np.random.seed(1)

2.任务概述

为了搭建你的NN,你将实现几个辅助函数。这些辅助函数将在下一节中用于搭建一个2层NN和1个 LL 层NN。
这些辅助函数会有详细的说明,指导你完成必要的步骤。

下面是任务概述,你将会

  • 为1个2层网络和1个 LL 层网络初始化参数
  • 实现前向传播模块(如下图紫色部分)
    • 计算一层的中线性部分(结果保存在 Z[l]Z^{[l]}
    • 激活函数会提供给你(relu/sigmoid)
    • 把前面两步(线性–>激活)合并入一个前向传播函数
    • 从第1层到第 L1L-1 层执行 (线性–>Relu)前向传播函数L1L-1次,最后第LL层执行(线性–>sigmod)前向传播函数。最终会给你一个新的LL层模型前向传播函数L_model_forward function
  • 计算损失
  • 实现反向传播函数(如下图红色部分)
    • 计算一层反向传播步骤中的线性部分
    • 激活函数的梯度会提供给你(relu_backward/sigmoid_backward)
    • 把前面两步(线性–>激活)合并入一个反向传播函数
    • 执行 (线性–>Relu)反向传播函数L1L-1次,再加上执行一次(线性–>sigmod)反向传播函数。最终会给你一个新的LL层模型反向传播函数L_model_backward function
  • 更新参数

在这里插入图片描述

注意:每个前向函数都关联一个反向函数。这就是为什么你前向模块中的每一步都需要保存一些值到cache中的原因。这些cache中的值将用于在反向模块中计算梯度。下面会向你详细展示如何执行这些步骤。

3.初始化

你将要编写2个函数来初始化你模型的参数。
第一个函数初始化2层模型参数。
第二个函数泛化初始化过程到LL层模型。

3.1 2层NN

创建和初始化2层NN的参数。

说明:

  • 模型的结构是:LINEAR -> RELU -> LINEAR -> SIGMOID
  • 随机初始化权重矩阵,np.random.randn(shape)*0.01
  • 偏移值b初始化为0,np.zeros(shape)

该函数在训练单隐层NN中已经构建过,代码如下

def initialize_parameters(n_x, n_h, n_y):
    """
    此函数是为了初始化两层网络参数而使用的函数
    Argument:
    n_x -- size of the input layer 输入层大小(节点数量)
    n_h -- size of the hidden layer 隐藏层
    n_y -- size of the output layer 输出层
    
    Returns:
    parameters -- python dictionary containing your parameters: 包含你的参数的python字典:
                    W1 -- weight matrix of shape (n_h, n_x)  权重矩阵,维度为(n_h,n_x)
                    b1 -- bias vector of shape (n_h, 1)  偏向量,维度为(n_h,1)
                    W2 -- weight matrix of shape (n_y, n_h)
                    b2 -- bias vector of shape (n_y, 1)
    """
    
    np.random.seed(1)
    
    ### START CODE HERE ### (≈ 4 lines of code)
    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros(shape=(n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros(shape=(n_y, 1))
    ### END CODE HERE ###
    
    #使用断言确保我的数据格式是正确的
    assert(W1.shape == (n_h, n_x))
    assert(b1.shape == (n_h, 1))
    assert(W2.shape == (n_y, n_h))
    assert(b2.shape == (n_y, 1))
    
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    
    return parameters

测试一下

parameters = initialize_parameters(2,2,1)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))

结果如下

W1 = [[ 0.01624345 -0.00611756]
 [-0.00528172 -0.01072969]]
b1 = [[ 0.]
 [ 0.]]
W2 = [[ 0.00865408 -0.02301539]]
b2 = [[ 0.]]

3.2 L层NN

L层NN的初始化会更加复杂,因为会有更多的权重矩阵和偏移矢量。完成参数初始化,你需要确保每层(矩阵)之间维度匹配。再次说明一下, n[l]n^{[l]} 表示 ll 层的单元数量。

例:输入矩阵 XX 维度是 (12288,209)(12288, 209),样本数量 m=209m=209 ,所以
在这里插入图片描述

请记住,在python中会通过广播机制计算WX+bW X + b

例:
W=[jklmnopqr]      X=[abcdefghi]      b=[stu](2) W = \begin{bmatrix} j & k & l\\ m & n & o \\ p & q & r \end{bmatrix}\;\;\; X = \begin{bmatrix} a & b & c\\ d & e & f \\ g & h & i \end{bmatrix} \;\;\; b =\begin{bmatrix} s \\ t \\ u \end{bmatrix}\tag{2}

WX+bWX + b就是
WX+b=[(ja+kd+lg)+s(jb+ke+lh)+s(jc+kf+li)+s(ma+nd+og)+t(mb+ne+oh)+t(mc+nf+oi)+t(pa+qd+rg)+u(pb+qe+rh)+u(pc+qf+ri)+u](3) WX + b = \begin{bmatrix} (ja + kd + lg) + s & (jb + ke + lh) + s & (jc + kf + li)+ s\\ (ma + nd + og) + t & (mb + ne + oh) + t & (mc + nf + oi) + t\\ (pa + qd + rg) + u & (pb + qe + rh) + u & (pc + qf + ri)+ u \end{bmatrix}\tag{3}

下面我们来实现LL层NN的初始化。

说明:

  • 模型的结构是[LINEAR -> RELU] ×\times (L-1) -> LINEAR -> SIGMOID。它有 L1L-1 层使用Relu激活函数和输出层使用sigmoid激活函数。

  • 随机初始化权重矩阵,np.random.randn(shape)*0.01

  • 偏移值b初始化为0,np.zeros(shape)

  • 使用变量layer_dims保存每一层的单元数n[l]n^{[l]}
    例:在平面数据分类中,layer_dims= [2,4,1]。表示:2个输入,包含4个单元的1个隐藏层,和包含1个输出单元的输出层。所以W1维度是(4,2),b1维度是(4,1),W2维度是(1,4),b2维度是(1,1)。

  • 以下是 L=1L=1(一层NN)的实现。你可以参照实现LL层NN。

if L == 1:
      parameters["W" + str(L)] = np.random.randn(layer_dims[1], layer_dims[0]) * 0.01
      parameters["b" + str(L)] = np.zeros((layer_dims[1], 1))

LL层实现代码如下

# GRADED FUNCTION: initialize_parameters_deep

def initialize_parameters_deep(layer_dims):
    """
    此函数是为了初始化多层网络参数而使用的函数。
    Arguments:
    包含我们网络中每个图层的节点数量的列表
    layer_dims -- python array (list) containing the dimensions of each layer in our network
    
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
	    权重矩阵,维度为(layers_dims [1],layers_dims [1-1])
                    Wl -- weight matrix of shape (layer_dims[l], layer_dims[l-1])
	    偏向量,维度为(layers_dims [1],1)
                    bl -- bias vector of shape (layer_dims[l], 1)
    """
    
    np.random.seed(3)
    parameters = {}
    L = len(layer_dims)            # number of layers in the network

    for l in range(1, L):
        ### START CODE HERE ### (≈ 2 lines of code)
        parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l - 1]) * 0.01
        parameters['b' + str(l)] = np.zeros((layer_dims[l], 1))
        ### END CODE HERE ###
        
        #确保我要的数据的格式是正确的
        assert(parameters['W' + str(l)].shape == (layer_dims[l], layer_dims[l - 1]))
        assert(parameters['b' + str(l)].shape == (layer_dims[l], 1))

        
    return parameters

测试一下

parameters = initialize_parameters_deep([5,4,3])
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))

运行结果如下

W1 = [[ 0.01788628  0.0043651   0.00096497 -0.01863493 -0.00277388]
 [-0.00354759 -0.00082741 -0.00627001 -0.00043818 -0.00477218]
 [-0.01313865  0.00884622  0.00881318  0.01709573  0.00050034]
 [-0.00404677 -0.0054536  -0.01546477  0.00982367 -0.01101068]]
b1 = [[ 0.]
 [ 0.]
 [ 0.]
 [ 0.]]
W2 = [[-0.01185047 -0.0020565   0.01486148  0.00236716]
 [-0.01023785 -0.00712993  0.00625245 -0.00160513]
 [-0.00768836 -0.00230031  0.00745056  0.01976111]]
b2 = [[ 0.]
 [ 0.]
 [ 0.]]

4.前向传播模块

4.1线性部分

完成参数初始化后,你可以开始实现前向传播模块。
你将依次实现以下3个函数

  • LINEAR
  • LINEAR -> ACTIVATION,ACTIVATION是ReLU或者Sigmoid
  • [LINEAR -> RELU] ×\times (L-1) -> LINEAR -> SIGMOID,全模型。

全部样本线性前向模块矢量化计算公式如下
Z[l]=W[l]A[l1]+b[l]Z^{[l]} = W^{[l]}A^{[l-1]} +b^{[l]}
其中A[0]=XA^{[0]} = X

调试过程中,你可以利用打印W.shape来确保维度匹配。

线性部分实现代码如下

def linear_forward(A, W, b):
    """
    Implement the linear part of a layer's forward propagation. 实现前向传播的线性部分。

    Arguments:
    来自上一层(或输入数据)的激活,维度为(上一层的节点数量,样本的数量)
    A -- activations from previous layer (or input data): (size of previous layer, number of examples)
    权重矩阵,numpy数组,维度为(当前层的节点数量,前一层的节点数量)
    W -- weights matrix: numpy array of shape (size of current layer, size of previous layer)
    偏移向量,numpy数组,维度为(当前层节点数量,1)
    b -- bias vector, numpy array of shape (size of the current layer, 1)

    Returns:
    激活函数的输入,也称为预激活参数
    Z -- the input of the activation function, also called pre-activation parameter 
    一个包含“A”,“W”和“b”的字典,存储这些变量以有效地计算后向传递 
    cache -- a python dictionary containing "A", "W" and "b" ; stored for computing the backward pass efficiently
    """
    
    ### START CODE HERE ### (≈ 1 line of code)
    Z = np.dot(W, A) + b
    ### END CODE HERE ###
    
    assert(Z.shape == (W.shape[0], A.shape[1]))
    cache = (A, W, b)
    
    return Z, cache

testCases_v2.py的测试函数linear_forward_test_case()测试一下效果

A, W, b = linear_forward_test_case()

Z, linear_cache = linear_forward(A, W, b)
print("Z = " + str(Z))

运行结果如下

Z = [[ 3.26295337 -1.23429987]]

4.2 激活部分

在这里,你使用了2个激活函数。

  • Sigmoid函数:σ(Z)=σ(WA+b)=11+e(WA+b)\sigma(Z) = \sigma(W A + b) = \frac{1}{ 1 + e^{-(W A + b)}}。在dnn_utils_v2.py中已经有实现好的函数sigmoid。函数返回2项,一个是激活函数的结果值aa,另一个是包含”Z” 的”cache“值 ,这个我们在后向传播过程需要用到。调用方法如下
A, activation_cache = sigmoid(Z)
  • ReLU函数:A=RELU(Z)=max(0,Z)A = RELU(Z) = max(0, Z)。在dnn_utils_v2.py中已经有实现好的函数relu。函数返回2项,一个是激活函数的结果值aa,另一个是包含”Z” 的”cache“值 ,这个我们在后向传播过程需要用到。调用方法如下
A, activation_cache = relu(Z)

4.2.1 2层前向(线性->激活)步骤实现

为了便于使用,我们把线性和激活2个部分合并到一个函数中。这个函数中先进行线性前向步骤,再进行激活步骤。

数学公式是: A[l]=g(Z[l])=g(W[l]A[l1]+b[l])A^{[l]} = g(Z^{[l]}) = g(W^{[l]}A^{[l-1]} +b^{[l]}),激活函数"g"可以是sigmoid()或者relu()。

实现代码如下

# GRADED FUNCTION: linear_activation_forward

def linear_activation_forward(A_prev, W, b, activation):
    """
   实现一层中的前向传播线性-->激活
    Implement the forward propagation for the LINEAR->ACTIVATION layer

    Arguments:
   来自上一层(或输入层)的激活函数输出,维度为(上一层的节点数量,样本数)
    A_prev -- activations from previous layer (or input data): (size of previous layer, number of examples)
    权重矩阵,numpy数组,维度为(当前层的节点数量,前一层的大小)
    W -- weights matrix: numpy array of shape (size of current layer, size of previous layer)
    偏移向量,numpy数组,维度为(当前层的节点数量,1)
    b -- bias vector, numpy array of shape (size of the current layer, 1)
    当前层中使用的激活函数名,字符串类型,【"sigmoid" | "relu"】
    activation -- the activation to be used in this layer, stored as a text string: "sigmoid" or "relu"

    Returns:
    激活函数的输出,也称为激活后的值
    A -- the output of the activation function, also called the post-activation value 
    一个包含“linear_cache”和“activation_cache”的字典,我们需要存储它以有效地计算后向传递
    cache -- a python dictionary containing "linear_cache" and "activation_cache";
             stored for computing the backward pass efficiently
    """
    
    if activation == "sigmoid":
        # Inputs: "A_prev, W, b". Outputs: "A, activation_cache".
        ### START CODE HERE ### (≈ 2 lines of code)
        Z, linear_cache = linear_forward(A_prev, W, b)
        A, activation_cache = sigmoid(Z)
        ### END CODE HERE ###
    
    elif activation == "relu":
        # Inputs: "A_prev, W, b". Outputs: "A, activation_cache".
        ### START CODE HERE ### (≈ 2 lines of code)
        Z, linear_cache = linear_forward(A_prev, W, b)
        A, activation_cache = relu(Z)
        ### END CODE HERE ###
    
    assert (A.shape == (W.shape[0], A_prev.shape[1]))
    cache = (linear_cache, activation_cache)

    return A, cache

测试一下

A_prev, W, b = linear_activation_forward_test_case()

A, linear_activation_cache = linear_activation_forward(A_prev, W, b, activation = "sigmoid")
print("With sigmoid: A = " + str(A))

A, linear_activation_cache = linear_activation_forward(A_prev, W, b, activation = "relu")
print("With ReLU: A = " + str(A))

运行结果

With sigmoid: A = [[ 0.96890023  0.11013289]]
With ReLU: A = [[ 3.43896131  0.        ]]

注意:在DL中,“线性->激活”2步计算被视为NN的单层,而不是2层

4.2.2 LL层前向(线性->激活)步骤实现

为了方便LL层NN的实现,你需要实现一个函数,其中用传入Relu的linear_activation_forward()重复L1L-1次,然后再使用1次传入 SIGMOID的linear_activation_forward()。
在这里插入图片描述

要实现的前向传播过程如上图。

说明:变量AL表示 A[L]=σ(Z[L])=σ(W[L]A[L1]+b[L])A^{[L]} = \sigma(Z^{[L]}) = \sigma(W^{[L]} A^{[L-1]} + b^{[L]}),或者称为y^\hat y,预测值。

注意:

  • 使用前面已经实现的函数linear_activation_forward()
  • 循环 [LINEAR->RELU] (L-1) 次
  • 使用list.append(c)在cache中追加一个值cc

实现代码如下

# GRADED FUNCTION: L_model_forward

def L_model_forward(X, parameters):
    """
    实现[LINEAR-> RELU] *(L-1) - > LINEAR-> SIGMOID计算前向传播,也就是L层网络的前向传播,
    Implement forward propagation for the [LINEAR->RELU]*(L-1)->LINEAR->SIGMOID computation
    
    Arguments:
    数据,numpy数组,维度为(输入节点数量,示例数)
    X -- data, numpy array of shape (input size, number of examples)
    parameters -- output of initialize_parameters_deep()
    
    Returns:
    AL -- last post-activation value 最后的激活值(预测值)
    caches -- list of caches containing:
                every cache of linear_relu_forward() (there are L-1 of them, indexed from 0 to L-2)
                the cache of linear_sigmoid_forward() (there is one, indexed L-1)
    包含以下内容的缓存列表:
                 linear_relu_forward()的每个cache(有L-1个,索引为从0到L-2)
                 linear_sigmoid_forward()的cache(只有一个,索引为L-1)
    """

    caches = []
    A = X
    L = len(parameters) // 2                  # number of layers in the neural network
    
    # Implement [LINEAR -> RELU]*(L-1). Add "cache" to the "caches" list.
    for l in range(1, L):
        A_prev = A 
        ### START CODE HERE ### (≈ 2 lines of code)
        A, cache = linear_activation_forward(A_prev, 
                                             parameters['W' + str(l)], 
                                             parameters['b' + str(l)], 
                                             activation='relu')
        caches.append(cache)
        
        ### END CODE HERE ###
    
    # Implement LINEAR -> SIGMOID. Add "cache" to the "caches" list.
    ### START CODE HERE ### (≈ 2 lines of code)
    AL, cache = linear_activation_forward(A, 
                                          parameters['W' + str(L)], 
                                          parameters['b' + str(L)], 
                                          activation='sigmoid')
    caches.append(cache)
    
    ### END CODE HERE ###
    
    assert(AL.shape == (1, X.shape[1]))
            
    return AL, caches

测试一下

X, parameters = L_model_forward_test_case()
AL, caches = L_model_forward(X, parameters)
print("AL = " + str(AL))
print("Length of caches list = " + str(len(caches)))

运行结果如下

AL = [[ 0.17007265  0.2524272 ]]
Length of caches list = 2

5.损失函数

现在你将要实现前向和反向传播过程。你需要计算损失,因为你要知道你的模型是不是真的学习的够好。

使用以下公式计算交叉熵损失J。
1mi=1m(y(i)log(a[L](i))+(1y(i))log(1a[L](i)))(7)-\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)) \tag{7}

该函数在Planar data classification with one hidden layer中已经介绍过,代码如下

# GRADED FUNCTION: compute_cost

def compute_cost(AL, Y):
    """
    Implement the cost function defined by equation (7).

    Arguments:
    与标签预测相对应的概率向量,维度为(1,样本数量)
    AL -- probability vector corresponding to your label predictions, shape (1, number of examples)
    标签向量(例如:如果不是猫,则为0,如果是猫则为1),维度为(1,数量)
    Y -- true "label" vector (for example: containing 0 if non-cat, 1 if cat), shape (1, number of examples)

    Returns:
    cost -- cross-entropy cost 交叉熵成本
    """
    
    m = Y.shape[1]

    # Compute loss from aL and y.
    ### START CODE HERE ### (≈ 1 lines of code)
    cost = (-1 / m) * np.sum(np.multiply(Y, np.log(AL)) + np.multiply(1 - Y, np.log(1 - AL)))
    ### END CODE HERE ###
    
    cost = np.squeeze(cost)      # To make sure your cost's shape is what we expect (e.g. this turns [[17]] into 17).
    assert(cost.shape == ())
    
    return cost

测试一下

Y, AL = compute_cost_test_case()

print("cost = " + str(compute_cost(AL, Y)))

运行结果如下

cost = 0.414931599615

6.反向传播模块

和前向传播一样,你需要为了反向传播构建辅助函数。

反向传播用于计算损失函数相对于各个参数的梯度。
在这里插入图片描述

和前向传播类似,你将按照以下3步构建反向传播

  • 线性反向
  • LINEAR -> ACTIVATION反向,ACTIVATION计算ReLU或者sigmoid激活函数的梯度
  • [LINEAR -> RELU] ×\times (L-1) -> LINEAR -> SIGMOID反向过程,全模块

6.1线性反向

对于ll层,线性部分是Z[l]=W[l]A[l1]+b[l]Z^{[l]} = W^{[l]} A^{[l-1]} + b^{[l]}

假设你已经计算了导数dZ[l]=LZ[l]dZ^{[l]} = \frac{\partial \mathcal{L} }{\partial Z^{[l]}},现在你想获得(dW[l],db[l]dA[l1])(dW^{[l]}, db^{[l]} dA^{[l-1]})
在这里插入图片描述
3个输出 (dW[l],db[l],dA[l])(dW^{[l]}, db^{[l]}, dA^{[l]})都要使用dZ[l]dZ^{[l]}来计算,公式如下
dW[l]=LW[l]=1mdZ[l]A[l1]T dW^{[l]} = \frac{\partial \mathcal{L} }{\partial W^{[l]}} = \frac{1}{m} dZ^{[l]} A^{[l-1] T} db[l]=Lb[l]=1mi=1mdZ[l](i) db^{[l]} = \frac{\partial \mathcal{L} }{\partial b^{[l]}} = \frac{1}{m} \sum_{i = 1}^{m} dZ^{[l](i)} dA[l1]=LA[l1]=W[l]TdZ[l] dA^{[l-1]} = \frac{\partial \mathcal{L} }{\partial A^{[l-1]}} = W^{[l] T} dZ^{[l]}

利用上面3个公式实现函数linear_backward(),代码如下。
注意,这里和原文有2处不一样。如果使用原文,LL层返现函数测试时候会报错assert (isinstance(db, float)) AssertionError

# GRADED FUNCTION: linear_backward

def linear_backward(dZ, cache):
    """
    为单层实现反向传播的线性部分(第L层)
    Implement the linear portion of backward propagation for a single layer (layer l)

    Arguments:
    相对于(当前第l层的)线性输出的成本梯度
    dZ -- Gradient of the cost with respect to the linear output (of current layer l)
    来自当前层前向传播的值的元组(A_prev,W,b)
    cache -- tuple of values (A_prev, W, b) coming from the forward propagation in the current layer

    Returns:
    相对于激活(前一层l-1)的成本梯度,与A_prev维度相同
    dA_prev -- Gradient of the cost with respect to the activation (of the previous layer l-1), same shape as A_prev
    相对于W(当前层l)的成本梯度,与W的维度相同
    dW -- Gradient of the cost with respect to W (current layer l), same shape as W
    相对于b(当前层l)的成本梯度,与b维度相同
    db -- Gradient of the cost with respect to b (current layer l), same shape as b
    """
    A_prev, W, b = cache
    m = A_prev.shape[1]

    ### START CODE HERE ### (≈ 3 lines of code)
    dW = np.dot(dZ, cache[0].T) / m
    #db = np.squeeze(np.sum(dZ, axis=1, keepdims=True)) / m
    db = np.sum(dZ,axis = 1, keepdims=True)/m
    dA_prev = np.dot(cache[1].T, dZ)
    ### END CODE HERE ###
    
    assert (dA_prev.shape == A_prev.shape)
    assert (dW.shape == W.shape)
    assert (db.shape == b.shape)
    #assert (isinstance(db, float))
    
    return dA_prev, dW, db

测试一下

# Set up some test inputs
dZ, linear_cache = linear_backward_test_case()

dA_prev, dW, db = linear_backward(dZ, linear_cache)
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db))

运行结果

dA_prev = [[ 0.51822968 -0.19517421]
 [-0.40506361  0.15255393]
 [ 2.37496825 -0.89445391]]
dW = [[-0.10076895  1.40685096  1.64992505]]
db = 0.506294475007

6.2激活反向

下面你要构建一个函数linear_activation_backward,把线性部分和激活部分的反向合并在一起

为了帮助你构建函数linear_activation_backward,提供了2个已经实现好的反向函数给你

  • sigmoid_backward: SIGMOID单元的反向传播,调用方式如下:
dZ = sigmoid_backward(dA, activation_cache)
  • relu_backward: RELU单元的反向传播,调用方式如下:
dZ = relu_backward(dA, activation_cache)

sigmoid_backward和relu_backward计算如下,g(.)g(.)表示激活函数
dZ[l]=dA[l]g(Z[l])(11)dZ^{[l]} = dA^{[l]} * g'(Z^{[l]}) \tag{11}.

LINEAR->ACTIVATION层反向传播实现代码如下

# GRADED FUNCTION: linear_activation_backward

def linear_activation_backward(dA, cache, activation):
    """
    实现LINEAR-> ACTIVATION层的后向传播。
    Implement the backward propagation for the LINEAR->ACTIVATION layer.
    
    Arguments:
    当前层l的激活后的梯度值
    dA -- post-activation gradient for current layer l 
    存储的用于有效计算反向传播的值的元组(值为linear_cache,activation_cache)
    cache -- tuple of values (linear_cache, activation_cache) we store for computing backward propagation efficiently
    要在此层中使用的激活函数名,字符串类型,【"sigmoid" | "relu"】
    activation -- the activation to be used in this layer, stored as a text string: "sigmoid" or "relu"
    
    Returns:
    相对于激活(前一层l-1)的成本梯度值,与A_prev维度相同
    dA_prev -- Gradient of the cost with respect to the activation (of the previous layer l-1), same shape as A_prev
    相对于W(当前层l)的成本梯度值,与W的维度相同
    dW -- Gradient of the cost with respect to W (current layer l), same shape as W
    相对于b(当前层l)的成本梯度值,与b的维度相同
    db -- Gradient of the cost with respect to b (current layer l), same shape as b
    """
    linear_cache, activation_cache = cache
    
    if activation == "relu":
        ### START CODE HERE ### (≈ 2 lines of code)
        dZ = relu_backward(dA, activation_cache)
        ### END CODE HERE ###
        
    elif activation == "sigmoid":
        ### START CODE HERE ### (≈ 2 lines of code)
        dZ = sigmoid_backward(dA, activation_cache)
        ### END CODE HERE ###
    
    # Shorten the code
    dA_prev, dW, db = linear_backward(dZ, linear_cache)
    
    return dA_prev, dW, db

测试一下

AL, linear_activation_cache = linear_activation_backward_test_case()

dA_prev, dW, db = linear_activation_backward(AL, linear_activation_cache, activation = "sigmoid")
print ("sigmoid:")
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db) + "\n")

dA_prev, dW, db = linear_activation_backward(AL, linear_activation_cache, activation = "relu")
print ("relu:")
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db))

运行结果

sigmoid:
dA_prev = [[ 0.11017994  0.01105339]
 [ 0.09466817  0.00949723]
 [-0.05743092 -0.00576154]]
dW = [[ 0.10266786  0.09778551 -0.01968084]]
db = -0.0572962221763

relu:
dA_prev = [[ 0.44090989 -0.        ]
 [ 0.37883606 -0.        ]
 [-0.2298228   0.        ]]
dW = [[ 0.44513824  0.37371418 -0.10478989]]
db = -0.208378923703

6.3LL层反向

现在你可以实现整个NN的反向部分函数。

前面已经说过,L_model_forward函数在每一次迭代中,需要把一些变量值保存在cache中,包括X,W,b, z。在反向传播模块中,你会用到这些变量来计算梯度。

在L_model_backward函数中,你要从LL层开始反向遍历所有的隐藏层。每一步都要取出对应cache值计算当前层梯度。下图显示了反向传播流程。

在这里插入图片描述

反向传播初始化
对于反向传播,我们知道NN的输出是A[L]=σ(Z[L])A^{[L]} = \sigma(Z^{[L]}),所以我们需要计算dAL =LA[L]= \frac{\partial \mathcal{L}}{\partial A^{[L]}}。实现代码如下

dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL)) # derivative of cost with respect to AL

计算完之后,我们可以用这个激活后的梯度 dAL 继续进行反向计算。正如上面图中所示,你可以把dAL送入 LINEAR->SIGMOID反向过程函数L_model_forward,然后再使用for循环遍历计算所有剩余层的LINEAR->RELU反向过程函数。在此过程中,你可以把dA, dW, db保存在字典grads中,代码如下


grads["dW"+str(l)]=dW[l]

例如: 对于l=3l=3层,dW[l]dW^{[l]} 保存形式为 grads[“dW3”]

实现[LINEAR->RELU] ×\times (L-1) -> LINEAR -> SIGMOID 模型代码如下
注意:代码和原文有3处改变。

def L_model_backward(AL, Y, caches):
    """
    对[LINEAR-> RELU] *(L-1) - > LINEAR - > SIGMOID组执行反向传播
    Implement the backward propagation for the [LINEAR->RELU] * (L-1) -> LINEAR -> SIGMOID group
    
    Arguments:
    概率向量,正向传播的输出(L_model_forward())
    AL -- probability vector, output of the forward propagation (L_model_forward())
    标签向量(例如:如果不是猫,则为0,如果是猫则为1),维度为(1,数量)
    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])
    包含以下内容的cache列表:
                 linear_activation_forward("relu")的cache,不包含输出层
                 linear_activation_forward("sigmoid")的cache    

    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
    
    # Initializing the backpropagation
    ### START CODE HERE ### (1 line of code)
    dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
    ### END CODE HERE ###
    
    # Lth layer (SIGMOID -> LINEAR) gradients. Inputs: "AL, Y, caches". Outputs: "grads["dAL"], grads["dWL"], grads["dbL"]
    ### START CODE HERE ### (approx. 2 lines)
    #current_cache = caches[-1]
    current_cache = caches[L-1]
    #grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_backward(sigmoid_backward(dAL, 
    #                                                                                                    current_cache[1]), 
    #                                                                                   current_cache[0])
    grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(dAL, current_cache, activation = "sigmoid")
    ### END CODE HERE ###
    
    for l in reversed(range(L-1)):
        # lth layer: (RELU -> LINEAR) gradients.
        # Inputs: "grads["dA" + str(l + 2)], caches". Outputs: "grads["dA" + str(l + 1)] , grads["dW" + str(l + 1)] , grads["db" + str(l + 1)] 
        ### START CODE HERE ### (approx. 5 lines)
        current_cache = caches[l]
        #dA_prev_temp, dW_temp, db_temp = linear_backward(sigmoid_backward(dAL, current_cache[1]), current_cache[0])
        dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads["dA" + str(l + 2)], current_cache, activation = "relu")
        grads["dA" + str(l + 1)] = dA_prev_temp
        grads["dW" + str(l + 1)] = dW_temp
        grads["db" + str(l + 1)] = db_temp
        ### END CODE HERE ###

    return grads

测试一下。注意:这里和原文不一样。原文的测试案例估计和我的不一样。

print("==============测试L_model_backward==============")
AL, Y_assess, caches = L_model_backward_test_case1()
grads = L_model_backward(AL, Y_assess, caches)
print ("dW1 = "+ str(grads["dW1"]))
print ("db1 = "+ str(grads["db1"]))
print ("dA1 = "+ str(grads["dA1"]))

结果如下

==============测试L_model_backward==============
dW1 = [[ 0.41010002  0.07807203  0.13798444  0.10502167]
 [ 0.          0.          0.          0.        ]
 [ 0.05283652  0.01005865  0.01777766  0.0135308 ]]
db1 = [[-0.22007063]
 [ 0.        ]
 [-0.02835349]]
dA1 = [[ 0.          0.52257901]
 [ 0.         -0.3269206 ]
 [ 0.         -0.32070404]
 [ 0.         -0.74079187]]

6.4更新参数

使用梯度下降更新模型参数,构建函数update_parameters()。
W[l]=W[l]α dW[l] W^{[l]} = W^{[l]} - \alpha \text{ } dW^{[l]}b[l]=b[l]α db[l] b^{[l]} = b^{[l]} - \alpha \text{ } db^{[l]}

α\alpha表示学习率。更新后,保存到参数字典。

代码如下

# GRADED FUNCTION: update_parameters

def update_parameters(parameters, grads, learning_rate):
    """
    使用梯度下降更新参数
    Update parameters using gradient descent
    
    Arguments:
    parameters -- python dictionary containing your parameters  包含你的参数的字典
    包含梯度值的字典,是L_model_backward的输出
    grads -- python dictionary containing your gradients, output of L_model_backward
    
    Returns:
    parameters -- python dictionary containing your updated parameters 包含更新参数的字典
                  parameters["W" + str(l)] = ... 
                  parameters["b" + str(l)] = ...
    """
    
    L = len(parameters) // 2 # number of layers in the neural network

    # Update rule for each parameter. Use a for loop.
    ### START CODE HERE ### (≈ 3 lines of code)
    for l in range(L):
        parameters["W" + str(l + 1)] = parameters["W" + str(l + 1)] - learning_rate * grads["dW" + str(l + 1)]
        parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * grads["db" + str(l + 1)]
    ### END CODE HERE ###
        
    return parameters

测试一下。注意:这里和原文不一样,没有W3和b3。

print("==============测试update_parameters==============")
parameters, grads = update_parameters_test_case()
parameters = update_parameters(parameters, grads, 0.1)

print ("W1 = " + str(parameters["W1"]))
print ("b1 = " + str(parameters["b1"]))
print ("W2 = " + str(parameters["W2"]))
print ("b2 = " + str(parameters["b2"]))

运行结果

==============测试update_parameters==============
W1 = [[-0.59562069 -0.09991781 -2.14584584  1.82662008]
 [-1.76569676 -0.80627147  0.51115557 -1.18258802]
 [-1.0535704  -0.86128581  0.68284052  2.20374577]]
b1 = [[-0.04659241]
 [-1.28888275]
 [ 0.53405496]]
W2 = [[-0.55569196  0.0354055   1.32964895]]
b2 = [[-0.84610769]]

7.总结

到这里,我们的搭建深度NN网络所需要的函数都已经完成了。

后面的编程练习,我们会利用它们来构筑2个模型

  • 2层NN
  • LL层NN

你可以利用这2个模型来实现猫图片的识别。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章