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個模型來實現貓圖片的識別。

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