【神经网络】卷积神经网络(二)卷积层以及池化层的实现

上篇文章【神经网络】卷积神经网络(一)总览以及卷积层、池化层我们已经介绍了卷积神经网络中关于卷积层和池化层的运作原理,本文我们将详细说明卷积层和池化层的具体实现方式。

一.im2col函数

由于CNN中处理的是多维数据,如果老老实实地一步一步实现卷积运算,估计要重复好几层的for语句,而且NumPy中存在使用for语句后处理变慢的缺点,所以我们使用im2col这个函数来进行数据预处理。

im2col(Image to Column)是一个常用于神经网络以及计算机视觉领域的一种数据重组方法,主要用于将卷积运算转化为矩阵乘法来加速计算。以下通过一个简单的例子来说明im2col函数的用法,假设我们有如下条件:

  • **‌输入图像‌:**假设是一张单通道的2D图片(3像素高 × 3像素宽)。

\[1, 2, 3,

4, 5, 6,

7, 8, 9\]

  • **滤波器‌:**大小为 2×2。

\[W1, W2,

W3, W4\]

  • **步幅‌:**设为 1。
  • **填充‌:**设为 0。

首先是滑动滤波器操作,2×2的滤波器在3×3的图片上以步幅1滑动。它会像盖章一样,从左到右、从上到下扫描,一共能停靠 4 个位置。

我们分别用‌颜色‌标记这四个位置的区域:

位置1(左上角)‌:数字 1, 2, 4, 5

位置2(右上角)‌:数字 2, 3, 5, 6

位置3(左下角)‌:数字 4, 5, 7, 8

位置4(右下角)‌:数字 5, 6, 8, 9

紧接着**将每个3D方块"横向展开为1列",**把每一个位置抠出来的 2×2 小块拉直,变成一列。列的长度是 2×2=4。

位置1的区域 \[1,2,4,5] 拉直后变成‌列1‌:

[1,

2,

4,

5]

位置2的区域 \[2,3,5,6] 拉直后变成‌列2‌:

[2,

3,

5,

6]

位置3的区域 \[4,5,7,8] 拉直后变成‌列3‌:

[4,

5,

7,

8]

位置4的区域 \[5,6,8,9] 拉直后变成‌列4‌:

[5,

6,

8,

9]

把所有拉直后的列横向拼接起来,就得到了 im2col 的最终输出矩阵:

\[1, 2, 4, 5, <- 来自位置1

2, 3, 5, 6, <- 来自位置2

4, 5, 7, 8, <- 来自位置3

5, 6, 8, 9] <- 来自位置4

最后,我们把滤波器的权重也拉直成一行向量 W1, W2, W3, W4。用这个向量去乘以刚刚生成的 im2col 矩阵:

第1列的结果 = W1×1 + W2×2 + W3×4 + W4×5 (对应位置1的卷积运算结果)

第2列的结果 = W1×2 + W2×3 + W3×5 + W4×6 (对应位置2)

第3列的结果 = W1×4 + W2×5 + W3×7 + W4×8 (对应位置3)

第4列的结果 = W1×5 + W2×6 + W3×8 + W4×9 (对应位置4)

以上4个计算结果完全等于滤波器在原始图片上依次滑动做乘加得到的结果,但过程变成了一次简单的矩阵乘法。这就是im2col如何将应用滤波器的每一个3维区域变成1列,并最终拼成矩阵的原理。

im2col函数的传参如下:

cpp 复制代码
im2col (input_data, filter_h, filter_w, stride=1, pad=0)
• input_data―由(数据量,通道,高,长)的4维数组构成的输入数据
• filter_h―滤波器的高
• filter_w―滤波器的长
• stride―步幅
• pad―填充

二.卷积层实现

现在使用im2col来实现卷积层。这里我们将卷积层实现为名为Convolution的类:

python 复制代码
class Convolution:
    def __init__(self, W, b, stride=1, pad=0):  //滤波器(权重)、偏置、步幅、填充作为参数接收
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad
        
        # 中间数据(backward时使用)
        self.x = None   
        self.col = None
        self.col_W = None
        
        # 权重和偏置参数的梯度
        self.dW = None
        self.db = None

    def forward(self, x):
        FN, C, FH, FW = self.W.shape  //滤波器是(FN, C, FH, FW) 的4 维形状
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)

        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T //将各个滤波器的方块纵向展开为1 列,在reshape时指定为-1,reshape函数会自动计算-1维度上的元素个数,以使多维数组的元素个数前后一致

        out = np.dot(col, col_W) + self.b 
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2) 

        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def backward(self, dout):
        FN, C, FH, FW = self.W.shape
        dout = dout.transpose(0,2,3,1).reshape(-1, FN)

        self.db = np.sum(dout, axis=0)
        self.dW = np.dot(self.col.T, dout)
        self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)

        dcol = np.dot(dout, self.col_W.T)
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx

forward的实现中,最后会将输出大小转换为合适的形状,转换时使用了NumPy的transpose函数,transpose会更改多维数组的轴的顺序:

在进行卷积层的反向传播时,则使用im2col的逆处理,也就是col2im函数,除了使用col2im这一点,卷积层的反向传播和Affi ne 层的实现方式都一样。

三.池化层实现

池化层的实现和卷积层相同,都使用im2col展开输入数据。不同的是在池化的情况下,通道方向上是独立的:

像这样展开之后,只需对展开的矩阵求各行的最大值,并转换为合适的形状即可:

代码如下:

python 复制代码
class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
        self.x = None
        self.arg_max = None

    def forward(self, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)

        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)

        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        return out

    def backward(self, dout):
        dout = dout.transpose(0, 2, 3, 1)
        
        pool_size = self.pool_h * self.pool_w
        dmax = np.zeros((dout.size, pool_size))
        dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
        dmax = dmax.reshape(dout.shape + (pool_size,)) 
        
        dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
        dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)
        
        return dx

池化层的实现按下面3 个阶段进行:

    1. 展开输入数据。
    1. 求各行的最大值。
    1. 转换为合适的输出大小。
相关推荐
AI人工智能+1 小时前
营业执照识别技术,通过深度学习、图像处理与NLP技术的深度融合,实现了对营业执照信息的快速、精准提取与智能解析
深度学习·自然语言处理·ocr·营业执照识别
老鱼说AI1 小时前
统计学习方法第七章:支持向量机精讲(超硬核长文深入预警!)
人工智能·深度学习·神经网络·算法·机器学习·支持向量机·学习方法
动物园猫1 小时前
停车场空车位检测数据集分享(适用于YOLO系列深度学习检测任务)
人工智能·深度学习·yolo
山科智能信息处理实验室1 小时前
(AAAI-2026)KnowLP:GraphRAG 诱导双知识结构图,实现个性化学习路径推荐
人工智能·深度学习·大语言模型
weixin_468466851 小时前
DeepLab 语义分割模型新手部署与实战指南
人工智能·深度学习·机器学习·语义分割·机器视觉·deeplab
钓了猫的鱼儿1 小时前
基于深度学习+AI的葡萄目标检测与预警系统(Python源码+数据集+UI可视化界面+YOLOv11训练结果)
人工智能·深度学习·目标检测
AndrewHZ1 小时前
【LLM技术全景】Transformer架构深度解析:Encoder-Decoder全理解
人工智能·深度学习·语言模型·大模型·llm·transformer·编解码技术
allein_STR1 小时前
【Transformer拆解】-4. 残差连接(Residual Connection)与层归一化(LayerNorm)
人工智能·深度学习·transformer
大模型最新论文速读2 小时前
06-02 · LLM 最新论文速览
论文阅读·人工智能·深度学习·机器学习·自然语言处理