python numpy 从零实现卷积神经网络

1.卷积层

这里,我们要实现一个拥有卷积层(CONV)和池化层(POOL)的网络,它包含了前向和反向传播。

1.1 边界扩充

首先实现两个辅助函数:一个用于零填充,另一个用于计算卷积。 边界填充将会在图像边界周围添加值为0的像素点,如下图

在这里插入图片描述

使用0填充边界有以下好处

(1)卷积了上一层之后的CONV层,没有缩小高度和宽度。 这对于建立更深的网络非常重要,否则在更深层时,高度/宽度会缩小。 一个重要的例子是“same”卷积,其中高度/宽度在卷积完一层之后会被完全保留。

(2)它可以帮助我们在图像边界保留更多信息。在没有填充的情况下,卷积过程中图像边缘的极少数值会受到过滤器的影响从而导致信息丢失。

def zero_pad(X,pad):
    
    X_paded = np.pad(X,(
                        (0,0),       #样本数,不填充
                        (pad,pad),   #图像高度,你可以视为上面填充x个,下面填充y个(x,y)
                        (pad,pad),   #图像宽度,你可以视为左边填充x个,右边填充y个(x,y)
                        (0,0)),      #通道数,不填充
                        'constant', constant_values=0)      #连续一样的值填充
    
    return X_paded

1.2 卷积窗口

卷积窗口如下图所示:

在这里插入图片描述

def conv_single_step(a_slice_prev, W, b):
    s = np.multiply(a_slice_prev,W) + b
    Z = np.sum(s)
    
    return Z

1.3 前向传播

在前向传播的过程中,我们将使用多种过滤器对输入的数据进行卷积操作,每个过滤器会产生一个2D的矩阵,我们可以把它们堆叠起来,于是这些2D的卷积矩阵就变成了高维的矩阵。

卷积公式
n H = ⌊ n H p r e v − f + 2 × p a d s t r i d e ⌋ + 1 n W = ⌊ n W p r e v − f + 2 × p a d s t r i d e ⌋ + 1 n C = number of filters used in the convolution n_H = \lfloor \frac{n_{H_{prev}} - f + 2 \times pad}{stride} \rfloor +1\\n_W = \lfloor \frac{n_{W_{prev}} - f + 2 \times pad}{stride} \rfloor +1 \\n_C = \text{number of filters used in the convolution} nH=stridenHprevf+2×pad+1nW=stridenWprevf+2×pad+1nC=number of filters used in the convolution

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
    
    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)
    
    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

1.4 反向传播

计算公式
d A + = ∑ h = 0 n H ∑ w = 0 n W W c × d Z h w d W c + = ∑ h = 0 n H ∑ w = 0 n W a s l i c e × d Z h w d b = ∑ h ∑ w d Z h w dA += \sum _{h=0} ^{n_H} \sum_{w=0} ^{n_W} W_c \times dZ_{hw}\\dW_c += \sum _{h=0} ^{n_H} \sum_{w=0} ^ {n_W} a_{slice} \times dZ_{hw} \\db = \sum_h \sum_w dZ_{hw}\\ dA+=h=0nHw=0nWWc×dZhwdWc+=h=0nHw=0nWaslice×dZhwdb=hwdZhw
函数实现

def conv_backward(dZ,cache):
    
    (A_prev, W, b, hparameters) = cache
   
    (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
   
    (m,n_H,n_W,n_C) = dZ.shape
    
    (f, f, n_C_prev, n_C) = W.shape
   
    pad = hparameters["pad"]
    stride = hparameters["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))
    
    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_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)

2.池化层

池化层会减少输入的宽度和高度,这样它会较少计算量的同时也使特征检测器对其在输入中的位置更加稳定。下面介绍两种类型的池化层:

最大值池化层:在输入矩阵中滑动一个大小为fxf的窗口,选取窗口里的值中的最大值,然后作为输出的一部分。

均值池化层:在输入矩阵中滑动一个大小为fxf的窗口,计算窗口里的值中的平均值,然后这个均值作为输出的一部分。
在这里插入图片描述
在这里插入图片描述

2.1 前向传播

池化层计算公式与卷积层一致:
n H = ⌊ n H p r e v − f + 2 × p a d s t r i d e ⌋ + 1 n W = ⌊ n W p r e v − f + 2 × p a d s t r i d e ⌋ + 1 n C = number of filters used in the convolution n_H = \lfloor \frac{n_{H_{prev}} - f + 2 \times pad}{stride} \rfloor +1\\n_W = \lfloor \frac{n_{W_{prev}} - f + 2 \times pad}{stride} \rfloor +1 \\n_C = \text{number of filters used in the convolution} nH=stridenHprevf+2×pad+1nW=stridenWprevf+2×pad+1nC=number of filters used in the convolution

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(1 + (n_H_prev - f) / stride)
    n_W = int(1 + (n_W_prev - f) / stride)
    n_C = n_C_prev
    

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

    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_prev_slice = 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)

    cache = (A_prev, hparameters)
    
    assert(A.shape == (m, n_H, n_W, n_C))
    
    return A, cache

2.2 创建掩码

在开始池化层的反向传播之前,我们需要创建一个函数,这个函数创建了一个掩码矩阵,以保存最大值的位置,当为1的时候表示最大值的位置,其他的为0,这个是最大值池化层,均值池化层的向后传播也和这个差不多,但是使用的是不同的掩码。

最大值池化层的反向传播
X = [ 1 3 4 2 ] → M = [ 0 0 1 0 ] X = \begin{bmatrix}1 && 3 \\4 && 2\end{bmatrix} \quad \rightarrow \quad M =\begin{bmatrix}0 && 0 \\1 && 0\end{bmatrix} X=[1432]M=[0100]

def create_mask_from_window(x):
    mask = x == np.max(x)
    
    return mask

均值池化层的反向传播

在最大值池化层中,对于每个输入窗口,输出的所有值都来自输入中的最大值,但是在均值池化层中,因为是计算均值,所以输入窗口的每个元素对输出有一样的影响。
d Z = 1 → d Z = [ 1 / 4 1 / 4 1 / 4 1 / 4 ] dZ = 1 \quad \rightarrow \quad dZ =\begin{bmatrix}1/4 && 1/4 \\1/4 && 1/4\end{bmatrix} dZ=1dZ=[1/41/41/41/4]

2.3 反向传播

接下来,我们从最大值池化层开始实现池化层的反向传播。 即使池化层没有反向传播过程中要更新的参数,我们仍然需要通过池化层反向传播梯度,以便为在池化层之前的层(比如卷积层)计算梯度。

def pool_backward(dA,cache,mode = "max"):
    (A_prev , hparaeters) = cache
    
    f = hparaeters["f"]
    stride = hparaeters["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

卷积神经网络参数计算

Activation shapeActivation sizeh parametersparameters
Input(32,32,3)32* 32 *3=3072f=5,s=10
conv1(28,28,8)28* 28 *8=6272p=2,s=2(5 * 5+1)*8
pool1(14,14,8)14* 14 *8=1568f=5,s=10
conv2(10,10,16)10* 10 *16=1600p=2,s=2(5 * 5+1)*16
pool2(5,5,16)5* 5 *16=4000
fc3(120,1)120400*120+1
fc4(84,1)84120*84+1
softmax(10,1)1084*10+1

参考吴恩达DeepLearning.ai

  • 6
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以回答这个问题。下面是一个用Python实现卷积神经网络的示例代码: ```python import numpy as np class ConvolutionalNeuralNetwork: def __init__(self, input_shape, num_filters, filter_size, stride): self.input_shape = input_shape self.num_filters = num_filters self.filter_size = filter_size self.stride = stride # Initialize convolutional filters filter_height, filter_width = filter_size num_channels = input_shape[0] self.filters = np.random.randn(num_filters, num_channels, filter_height, filter_width) def forward_pass(self, X): num_filters, num_channels, filter_height, filter_width = self.filters.shape _, input_height, input_width = X.shape # Compute output dimensions output_height = (input_height - filter_height) // self.stride + 1 output_width = (input_width - filter_width) // self.stride + 1 # Initialize output tensor outputs = np.zeros((num_filters, output_height, output_width)) # Convolve input with filters for f in range(num_filters): for i in range(output_height): for j in range(output_width): # Compute the receptive field row_start = i * self.stride row_end = row_start + filter_height col_start = j * self.stride col_end = col_start + filter_width receptive_field = X[:, row_start:row_end, col_start:col_end] # Perform element-wise multiplication and sum over channels and spatial dimensions output = np.sum(receptive_field * self.filters[f], axis=(1,2,3)) # Write the output to the output tensor outputs[f, i, j] = output return outputs ``` 这个示例代码实现了一个简单的卷积神经网络(CNN)。在`__init__`方法中,输入参数包括输入张量的形状(即通道数、高度和宽度)、卷积核的数量和大小以及卷积核的步幅。卷积核是随机初始化的。 在`forward_pass`方法中,输入张量`X`被卷积卷积以生成输出张量。卷积的过程中,每个卷积核在输入张量上以步幅为步长滑动,每次滑动都覆盖一个区域,这个区域称为感受野。对于每个感受野,卷积核的所有元素都与输入张量的对应元素相乘,然后将所有乘积相加以生成一个输出值。因此,卷积操作本质上是一种特殊的加权求和操作,每个卷积核都可以学习将输入张量的某个特定特征映射到输出张量中。 希望这个示例代码可以帮助你了解卷积神经网络的基本原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值