卷积层的向前传播

def conv_forward(A_prev,W,b,hparameters):
    (m,n_H_prev,n_W_prev,n_C_prev)=A_prev.shape
    (f,f,n_C_prev,n_C)=W.shape

向前传播,参与运算的有A_prev,W,b,还有两个超参数,步长和填充大小。
先来看A_prev的结构,第一维表示的是样本,所以一会向前传播第一层循环就是

for i in range(m):
    a_prev=A_prev[i]

将四维降维成为三维,A_prev后面的三维分别是,当前的高度,当前的宽度,当前的通道数量。然后就是过滤器的结构,分析一下,过滤器的前三维表示的是过滤器的长和宽,然后第三维对应的就是A_prev的通道的数,前三维是针对一个过滤器来说的。第四维对应的就是这一层的过滤器的数量。所以对于一个单样本的A_prev来说,我们要让他经历每一个当前层的过滤器,所以由有了一个循环

for i in range(n_C):

分析完了重要参数的结构,然后就是正向传播的内容了

    stride=hparameters["stride"]
    pad=hparameters["pad"]

传入了超参数,一个是步长,一个是填充的边界长度,分别获取就可以了

    n_H=int((n_H_prev-f+2*pad)/stride)+1
    n_W=int((n_W_prev-f+2*pad)/stride)+1
    Z=np.zeros((m,n_H,n_W,n_C))

    A_prev_pad=zero_pad(A_prev,pad)

在开始计算卷积之前,我们首先要了解一下当前层计算完毕之后的结构,首先是通道数,通道数就是过滤器的数量,也就是过滤器的那个参数的第四维(?,?,?,当前层过滤器的数量),然后第一维就是样本的数量,(样本的数量,?,?,当前层过滤器的数量),那么第二三维呢?,这个就需要了解我们的卷积计算过程了,产生的长宽高和步长以及填充的边有关系,通过计算可以知道,当前层的长和宽

    n_H=int((n_H_prev-f+2*pad)/stride)+1
    n_W=int((n_W_prev-f+2*pad)/stride)+1

然后就是创建当前层计算结果的结构

   Z=np.zeros((m,n_H,n_W,n_C))

然后在就是填充传入的数据

A_prev_pad=zero_pad(A_prev,pad)

接下来就是正式的开始卷积的计算了

    for i in range(m):#样本
        #四维变成三维
        a_prev_pad=A_prev_pad[i]
        for h in range(n_H):
            for w in range(n_W):#前两维就是长和宽
                for c in range(n_C):# 每一个小块块都要经过当前层的所有的过滤器
                    vert_start=h*stride
                    vert_end=vert_start+f
                    horiz_start=w*stride
                    horiz_end=horiz_start+f
                    #每一个小快都要经历当前层的所有的过滤器 
                    a_slice_prev=a_prev_pad[vert_start:vert_end,horiz_start:horiz_end,:]
                    Z[i,h,w,c]=conv_single_step(a_slice_prev,W[:,:,:,c],b[0,0,0,c])
    assert(Z.shape==(m,n_H,n_W,n_C))
    cache=(A_prev,W,b,hparameters)

    return (Z,cache)

然后就是断言Z的结构是不是对的,还有就是向后面传播cache

池化层的向前传播

def pool_forward(A_prev,hparameters,mode="max"):

    (m , n_H_prev , n_W_prev , n_C_prev) = A_prev.shape
  f = hparameters["f"]
    stride = hparameters["stride"]  

和卷积层的结构是一样的

    n_H=int((n_H_prev-f)/stride)+1
    n_W=int((n_W_prev-f)/stride)+1
    n_C=n_C_prev

    A = np.zeros((m , n_H , n_W , n_C))

实际上公式是一样的,只不过这里没有使用到padding,然后在池化计算前后通道数是相同的,所以池化前后通道数没有发生变化,所以前后发生变化的只有第二维和第三维,和步长以及f相关

    for i in range(m):
        for h in range(n_H):
            for w in range(n_W):
                for c in range(n_C):
                    vert_start=h*stride
                    vert_end=vert_start+f
                    horiz_start=w*stride
                    horiz_end=horiz_start+f

                    a_slice_prev=A_prev[i,vert_start:vert_end,horiz_start:horiz_end,c]

                    if mode=="max":
                        A[i,h,w,c]=np.max(a_slice_prev)
                    elif mode=="average":
                        A[i,h,w,c]=np.mean(a_slice_prev)
    assert(A.shape==(m,n_H,n_W,n_C)) 
    cache=(A_prev,hparameters)
    return A,cache

卷积层和池化层的区别

卷积计算之后厚度会发生变化,而池化层的厚度不会发生变化
卷积层的过滤器的值是要和上一层的结果进行运算的,而池化层的f完全就是用来计算当前层的长和宽的

卷基层的反向传播

def conv_backward(dZ,cache):
    (A_prev,W,b,hparameters)=cache

反向传播的推导一定需要一些正向传播时用到的参数,就像神经网络在反向传播中,在推导dA_prev时需要用到W等等。在这里我们需要获取的和神经网络是一样的,我们需要获取到dA_prev,dW,db,我们需要获取到A_prev,W,b

    (m,n_H_prev,n_W_prev,n_C_prev)=A_prev.shape

在上一步中,我们已经获取到了上一层的结果,上一层的结果是四维的,如下(样本,上一层的高,上一层的宽,上一层的通道数)

    (m,n_H,n_W,n_C)=dZ.shape

就像神经网络中的起点一样,我们的卷积神经网络的反向传播的起点也是这样的,dZ是我们的起点。

    (f,f,n_C_prev,n_C)=W.shape

W的结构也是四维的,在正向传播中已经介绍了

    pad=hparameters["pad"]
    stride=hparameters["stride"]

我们需要用到pad(填充)和stride(步长)

    dA_prev=np.zeros((m,n_H_prev,n_W_prev,n_C_prev))
    dW=np.zeros((f,f,n_C_prev,n_C))
    db=np.zeros((1,1,1,n_C))

然后就是为了这一层的反向传播创建dA_prev,dW,db的空结构

    A_prev_pad=zero_pad(A_prev,pad)
    dA_prev_pad=zero_pad(dA_prev,pad)

这里需要我们进行填充

    for i in range(m):#第一维的循环,使得每一个处理都是针对单样本来说的
        a_prev_pad=A_prev_pad[i]
        da_prev_pad=dA_prev_pad[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
                    #我们获取到了一个三维中的一个小块
                    a_slice=a_prev_pad[vert_start:vert_end,horiz_start:horiz_end,:]
                   #一下代码的我们详细说明
                    da_prev_pad[vert_start:vert_end,horiz_start:horiz_start:horiz_end,:]+=W[:,:,:,c]*dZ[i,h,w,c]
                    dW[:,:,:,c]+=a_slice*dZ[i,h,w,c]
                    db[:,:,:,c]+=dZ[i,h,w,c]
        dA_prev[i,:,:,:]=da_prev_pad[pad:-pad,pad:-pad,:]
    assert(dA_prev.shape==(m,n_H_prev,n_W_prev,n_C_prev)) 

    return (dA_prev,dW,db)       

如下就是对反向传播的核心代码的详细分析

             da_prev_pad[vert_start:vert_end,horiz_start:horiz_start:horiz_end,:]+=W[:,:,:,c]*dZ[i,h,w,c]

在神经网络中我们通过dz和当前层的w推出da_prev(z=a_prevw+b),和卷积神经网络类似,我们通过dz和w推出
da_prev_pad。大致的思路定下来了,然后就是细节上的处理。看上面的循环,在第三维的循环是针对每一个不同的过滤器的,*
所以每一次的循环都是使用每一个不同的过滤器,所以一定就有一项W[:,:,:,c]。
然后dz,我们使用的dz是思维的唯一确定的一个点,所以四维都是固定的,也就是dZ[i,h,w,c]
W[:,:,:,c]*dZ[i,h,w,c]的运算相当于是一个点乘以一个矩阵,所以获取到的也是一个矩阵,也肯定是一个大矩阵中的小块

                    dW[:,:,:,c]+=a_slice*dZ[i,h,w,c]
                    db[:,:,:,c]+=dZ[i,h,w,c]

然后就是推dW和db.
dW[:,:,:,c]表示的当前这个过滤器的梯度,c表示的是不同过滤器的数量的。它是有上次层a和Z推出来的。在上一层的a有多少个切片,就对应着计算出来的W的梯度,然后相加。还是一个值乘以一个小矩阵的结构

左边dW[:,:,:,c]
右边a_slice*dZ[i,h,w,c] ------->
dW[:,:,:,c]+=a_slice*dZ[i,h,w,c]

推出了dW,然后也推出了db

db[:,:,:,c]+=d[i,h,w,c]

上面推出来的da_prev_pad是针对单样本来说的,我们现在要将它赋给多样本的单元

dA_prev[i,:,:,:]=da_prev_pad[pad:-pad,pad:-pad,:]

这个时候就要解除填充

池化层的反向传播

针对与最大池化和平均池化处理的方式是不同的,所以对于函数的参数有一项是mode看mode是最大还是平均
首先是两个预备的函数

def create_mask_from_window(x):
    mask=x==np.max(x)
    return mask
def distribute_value(dz,shape):
    (n_H,n_W)=shape
    average=dz/(n_H*n_W)
    a=np.ones(shape)*average
    return a
def pool_backward(dA,cache,mode="max"):
    (A_prev,hparameters)=cache

传入的参数不是dZ而是dA,说明的是池化层的过程,池化层的正向传播是A_prev转化为A

    f=hparameters["f"]
    stride=hparameters["stride"]
    (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[i,vert_start:vert_end,horiz_start:horiz_end,c]+=np.multiply(mask,dA[i,h,w,c])
                elif mode=="average":    
                    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

dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c]+=np.multiply(mask,dA[i,h,w,c])
dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c]=np.multiply(mask,dA[i,h,w,c])

以上两种写法都是可以的

dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c]=distribute_value(da,shape)
dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c]+=distribute_value(da,shape)
```