深度學習之step by step搭建神經網路

故y發表於2022-11-19

宣告

本文參考Deep-Learning-Specialization-Coursera/Convolution_model_Step_by_Step_v1.ipynb at main · abdur75648/Deep-Learning-Specialization-Coursera · GitHub,力求理解。


資料下載

連結:https://pan.baidu.com/s/1LoMe9bS_ig0wB7ubR9m39Q
提取碼:afhc,請在開始之前下載好所需資料。


【博主使用的python版本:3.9.12】,當然也使用tensorflow2.


1. 神經網路的底層搭建

  這裡,我們要實現一個擁有卷積層(CONV)和池化層(POOL)的網路,它包含了前向和反向傳播。

nH,nW,nc,是指分別表示給定層的影像的高度、寬度和通道數。如果你想特指某一層,那麼可以這樣寫:nH[L],nW[L],nc[L]

1 - Packages

我們先要引入一些庫:

import numpy as np
import h5py
import matplotlib.pyplot as plt
from public_tests import *

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

np.random.seed(1)

2 - Outline of the Assignment

我們將實現一個卷積神經網路的一些模組,下面我們將列舉我們要實現的模組的函式功能:

  1. 卷積模組,包含了以下函式:
  • 使用0擴充邊界
  • 卷積視窗
  • 前向卷積
  • 反向卷積(可選)

    2.池化模組,包含了以下函式:

  • 前向池化
  • 建立掩碼
  • 值分配
  • 反向池化(可選)
  • 我們將在這裡從底層搭建一個完整的模組,之後我們會用TensorFlow實現。模型結構如下:

  需要注意的是我們在前向傳播的過程中,我們會儲存一些值,以便在反向傳播的過程中計算梯度值。

3 - Convolutional Neural Networks

儘管程式設計框架使卷積容易使用,但它們仍然是深度學習中最難理解的概念之一。卷積層將輸入轉換成不同維度的輸出,如下所示。

 我們將一步步構建卷積層,我們將首先實現兩個輔助函式:一個用於零填充,另一個用於計算卷積。

3.1 - Zero-Padding

邊界填充將會在影像邊界周圍新增值為0的畫素點,如下圖所示:

使用0填充邊界有以下好處:

卷積了上一層之後的CONV層,沒有縮小高度和寬度。 這對於建立更深的網路非常重要,否則在更深層時,高度/寬度會縮小。 一個重要的例子是“same”卷積,其中高度/寬度在卷積完一層之後會被完全保留。

它可以幫助我們在影像邊界保留更多資訊。在沒有填充的情況下,卷積過程中影像邊緣的極少數值會受到過濾器的影響從而導致資訊丟失。

  我們將實現一個邊界填充函式,它會把所有的樣本影像X XX都使用0進行填充。我們可以使用np.pad來快速填充。需要注意的是如果你想使用pad = 1填充陣列**a**.shape = ( 5 , 5 , 5 , 5 , 5 )的第二維,使用pad = 3填充第4維,使用pad = 0來填充剩下的部分,我們可以這麼做:

 Average Pooling - Backward Pass

在最大值池化層中,對於每個輸入視窗,輸出的所有值都來自輸入中的最大值,但是在均值池化層中,因為是計算均值,所以輸入視窗的每個元素對輸出有一樣的影響,我們來看看如何反向傳播吧~

def distribute_value(dz,shape):
    """
    給定一個值,為按矩陣大小平均分配到每一個矩陣位置中。
    
    引數:
        dz - 輸入的實數
        shape - 元組,兩個值,分別為n_H , n_W
        
    返回:
        a - 已經分配好了值的矩陣,裡面的值全部一樣。
    
    """
    #獲取矩陣的大小
    (n_H , n_W) = shape
    
    #計算平均值
    average = dz / (n_H * n_W)
    
    #填充入矩陣
    a = np.ones(shape) * average
    
    return a

測試一下:

a = distribute_value(2, (2, 2))
print('distributed value =', a)


assert type(a) == np.ndarray, "Output must be a np.ndarray"
assert a.shape == (2, 2), f"Wrong shape {a.shape} != (2, 2)"
assert np.sum(a) == 2, "Values must sum to 2"

a = distribute_value(100, (10, 10))
assert type(a) == np.ndarray, "Output must be a np.ndarray"
assert a.shape == (10, 10), f"Wrong shape {a.shape} != (10, 10)"
assert np.sum(a) == 100, "Values must sum to 100"

print("\033[92m All tests passed.")
distributed value = [[0.5 0.5]
 [0.5 0.5]]
 All tests passed.

 Putting it Together: Pooling Backward

def pool_backward(dA,cache,mode = "max"):
    """
    實現池化層的反向傳播
    
    引數:
        dA - 池化層的輸出的梯度,和池化層的輸出的維度一樣
        cache - 池化層前向傳播時所儲存的引數。
        mode - 模式選擇,【"max" | "average"】
        
    返回:
        dA_prev - 池化層的輸入的梯度,和A_prev的維度相同
    
    """
    #獲取cache中的值
    (A_prev , hparaeters) = cache
    
    #獲取hparaeters的值
    f = hparaeters["f"]
    stride = hparaeters["stride"]
    
    #獲取A_prev和dA的基本資訊
    (m , n_H_prev , n_W_prev , n_C_prev) = A_prev.shape
    (m , n_H , n_W , n_C) = dA.shape
    
    #初始化輸出的結構
    dA_prev = np.zeros_like(A_prev)
    
    #開始處理資料
    for i in range(m):
        a_prev = A_prev[i]      
        for h in range(n_H):
            for w in range(n_W):
                for c in range(n_C):
                    #定位切片位置
                    vert_start = h
                    vert_end = vert_start + f
                    horiz_start = w
                    horiz_end = horiz_start + f
                    
                    #選擇反向傳播的計算方式
                    if mode == "max":
                        #開始切片
                        a_prev_slice = a_prev[vert_start:vert_end,horiz_start:horiz_end,c]
                        #建立掩碼
                        mask = create_mask_from_window(a_prev_slice)
                        #計算dA_prev
                        dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c] += np.multiply(mask,dA[i,h,w,c])
    
                    elif mode == "average":
                        #獲取dA的值
                        da = dA[i,h,w,c]
                        #定義過濾器大小
                        shape = (f,f)
                        #平均分配
                        dA_prev[i,vert_start:vert_end, horiz_start:horiz_end ,c] += distribute_value(da,shape)
    #資料處理完畢,開始驗證格式
    assert(dA_prev.shape == A_prev.shape)
    
    return dA_prev

測試一下:

np.random.seed(1)
A_prev = np.random.randn(5, 5, 3, 2)
hparameters = {"stride" : 1, "f": 2}
A, cache = pool_forward(A_prev, hparameters)
print(A.shape)
print(cache[0].shape)
dA = np.random.randn(5, 4, 2, 2)

dA_prev1 = pool_backward(dA, cache, mode = "max")
print("mode = max")
print('mean of dA = ', np.mean(dA))
print('dA_prev1[1,1] = ', dA_prev1[1, 1])  
print()
dA_prev2 = pool_backward(dA, cache, mode = "average")
print("mode = average")
print('mean of dA = ', np.mean(dA))
print('dA_prev2[1,1] = ', dA_prev2[1, 1]) 
print("\033[92m All tests passed.")
(5, 4, 2, 2)
(5, 5, 3, 2)
mode = max
mean of dA =  0.14571390272918056
dA_prev1[1,1] =  [[ 0.          0.        ]
 [ 5.05844394 -1.68282702]
 [ 0.          0.        ]]

mode = average
mean of dA =  0.14571390272918056
dA_prev2[1,1] =  [[ 0.08485462  0.2787552 ]
 [ 1.26461098 -0.25749373]
 [ 1.17975636 -0.53624893]]
 All tests passed.

到此就結束了,下面我們進行應用。

 

相關文章