深度学习三 —— 手撕卷积层

手撕卷积层(numpy)

卷积层的操作和定义

基础运算

卷积层的核心就是卷积运算,其实就是矩阵运算,大概过程如下:

在这里插入图片描述

( 4 , 4 ) (4, 4) (4,4) ( 3 , 3 ) (3, 3) (3,3),最后输出结果为 ( 2 , 2 ) (2, 2) (2,2)

偏置 bias

但是在Pytorch的CNN中,除了基础卷积操作以外,还存在偏置,如下所示:

在这里插入图片描述

填充 padding

CNN中,在进行卷积处理之前,有时需要对周围元素填入数据,称之为填充(padding),如下所示

在这里插入图片描述

  • p a d d i n g = 1 padding = 1 padding=1: 图像大小经过卷积后不变

步幅 Stride

卷积核的移动步长称之为 步幅(Stride),上述例子均为 S t r i d e = 1 Stride=1 Stride=1

关系计算公式

​ 假设输入大小为 ( H , W ) (H, W) (H,W),卷积核大小为 ( F H , F W ) (FH, FW) (FH,FW),输出大小为 ( O H , O W (OH, OW (OH,OW p a d d i n g = P padding=P padding=P S t r i d e = S Stride=S Stride=S。此时输出大小的计算公式为:

O H = H + 2 P − F H S + 1 OH=\frac{H+2P-FH}{S}+1 OH=SH+2PFH+1
O W = W + 2 P − F W S + 1 OW=\frac{W+2P-FW}{S}+1 OW=SW+2PFW+1

卷积层的实现

初始数据生成

x = np.random.rand(10, 1, 28, 28)
x.shape
# (10, 1, 28, 28)

生成四维数组,访问每一个数据,用x[0]或者x[1]即可;访问第一个数据第一个通道的二维数据可以用x[0,0]或者是x[0][0]

im2col & col2im

针对多层的卷积,全部使用for循环会变慢,numpy有这个特点,所以这里使用im2col(image to column)这个函数来实现:

对3维或者4维数据应用,应用im2col后,变成二维矩阵,以适应滤波器。将应用滤波器的区域(3维方块)横向展开为1列。im2col会在所有应用滤波器的地方进行这个展开处理。

在这里插入图片描述

步幅比较小的时候,展开后的个数会多于原方块的元素个数,会消耗更多的内存

在这里插入图片描述


    def im2col(self, input_data, conv_h, conv_w, stride=1, padding=0):
        """

        :param input_data: N x C x H x W
        :param conv_h: 卷积核的高
        :param conv_w: 卷积核的长
        :param stride: 步幅
        :param padding: 填充
        :return: col 2维数据
        """
        N, C, H, W = input_data.shape
        out_h = (H + 2 * padding - conv_h) // stride + 1
        out_w = (W + 2 * padding - conv_w) // stride + 1

        img = np.pad(input_data, [(0, 0), (0, 0), (padding, padding), (padding, padding)], "constant")
        col = np.zeros(N, C, conv_h, conv_w, out_h, out_w)
        for y in range(conv_h):
            y_max = y + stride * out_h
            for x in range(conv_w):
                x_max = x + stride * out_w
                col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
        col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N * out_h * out_w, -1)
        return col

    def col2im(self, col, input_shape, conv_h, conv_w, stride=1, padding=0):
        """

        :param col:
        :param input_shape: 输入数据形式 例如:(10, 1, 28, 28)
        :param conv_h:
        :param conv_w:
        :param stride:
        :param padding:
        :return:
        """
        N, C, H, W = input_shape
        out_h = (H + 2 * padding - conv_h) // stride + 1
        out_w = (W + 2 * padding - conv_w) // stride + 1

        col = np.zeros(N, out_h, out_w, C, conv_h, conv_w).transpose(0, 3, 4, 5, 1, 2)

        img = np.zeros((N, C, H + 2 * padding + stride - 1, W + 2 * padding + stride - 1))
        for y in range(conv_h):
            y_max = y + stride * out_h
            for x in range(conv_w):
                x_max = x + stride * out_h
                img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]

        return img[:, :, padding:H + padding, padding:W + padding]


基础卷积层实现


class Convolution:
    def __init__(self, W, b, stride=1, padding=0):
        """

        :param W: 权重
        :param b: 偏置
        :param stride: 步幅
        :param padding: 填充
        """
        self.W = W
        self.b = b
        self.stride = stride
        self.padding = padding


    def forward(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = int(1 + (H + 2 * self.padding - FH) / self.stride)
        out_w = int(1 + (W + 2 * self.padding - FW) / self.stride)

        col = self.im2col(x, FH, FW, self.stride, self.padding)
        col_W = self.W.reshape(FN, -1).T  # conv2d 展开
        out = np.dot(col, col_W) + self.b
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
        return out

reshape将各个滤波器的方块纵向展开为1列。这里通过reshape(FN,-1)将参数指定为-1,这是reshape的一个便利的功能。通过在reshape时指定为-1,reshape函数会自动计算-1维度上的元素个数,以使多维数组的元素个数前后一致

(10, 3, 5, 5)形状的数组的元素个数共有750个,指定reshape(10,-1)后,就 会转换成(10, 75)形状的数组。

Forward中最后将输出的大小转换成合适的形状,使用了numpy中的transpose函数。transpose会更改多维数组的轴的顺序。如图7-20 所示,通过指定从0开始的索引(编号)序列,就可以更改轴的顺序。
在这里插入图片描述

进行卷积层的反向传播时,要进行逆处理,比如说使用col2im函数

最终卷积层实现

import os, sys
import collections
import numpy as np


class Convolution:
    def __init__(self, W, b, stride=1, padding=0):
        """

        :param W: 权重
        :param b: 偏置
        :param stride: 步幅
        :param padding: 填充
        """
        self.W = W
        self.b = b
        self.stride = stride
        self.padding = padding

        # 中间数据,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
        N, C, H, W = x.shape
        out_h = int(1 + (H + 2 * self.padding - FH) / self.stride)
        out_w = int(1 + (W + 2 * self.padding - FW) / self.stride)

        col = self.im2col(x, FH, FW, self.stride, self.padding)
        col_W = self.W.reshape(FN, -1).T  # conv2d 展开
        out = np.dot(col, col_W) + self.b
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
        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 = self.col2im(dcol, self.x.shape, FH, FW, self.stride, self.padding)
        return dx

    def im2col(self, input_data, conv_h, conv_w, stride=1, padding=0):
        """

        :param input_data: N x C x H x W
        :param conv_h: 卷积核的高
        :param conv_w: 卷积核的长
        :param stride: 步幅
        :param padding: 填充
        :return: col 2维数据
        """
        N, C, H, W = input_data.shape
        out_h = (H + 2 * padding - conv_h) // stride + 1
        out_w = (W + 2 * padding - conv_w) // stride + 1

        img = np.pad(input_data, [(0, 0), (0, 0), (padding, padding), (padding, padding)], "constant")
        col = np.zeros(N, C, conv_h, conv_w, out_h, out_w)
        for y in range(conv_h):
            y_max = y + stride * out_h
            for x in range(conv_w):
                x_max = x + stride * out_w
                col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
        col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N * out_h * out_w, -1)
        return col

    def col2im(self, col, input_shape, conv_h, conv_w, stride=1, padding=0):
        """

        :param col:
        :param input_shape: 输入数据形式 例如:(10, 1, 28, 28)
        :param conv_h:
        :param conv_w:
        :param stride:
        :param padding:
        :return:
        """
        N, C, H, W = input_shape
        out_h = (H + 2 * padding - conv_h) // stride + 1
        out_w = (W + 2 * padding - conv_w) // stride + 1

        col = np.zeros(N, out_h, out_w, C, conv_h, conv_w).transpose(0, 3, 4, 5, 1, 2)

        img = np.zeros((N, C, H + 2 * padding + stride - 1, W + 2 * padding + stride - 1))
        for y in range(conv_h):
            y_max = y + stride * out_h
            for x in range(conv_w):
                x_max = x + stride * out_h
                img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]

        return img[:, :, padding:H + padding, padding:W + padding]

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
经典卷积神经网络是指使用numpy纯写的卷积神经网络代码,该代码可以帮助理解卷积神经网络的原理。它不使用任何神经网络框架,适合那些有意愿深入理解卷积神经网络底层的人群。这个的代码相对简单,但是通过研究它,可以充分理解卷积神经网络的工作原理。 卷积神经网络(CNN)是一种常用于图像处理和识别的深度学习模型。它通过卷积层、池化层和全连接层等组成,实现了对图像特征的提取和分类。在卷积神经网络中,卷积层通过滤波器(卷积核)对输入图像进行卷积操作,以提取图像的局部特征。池化层则通过降采样的方式,减少特征图的尺寸,同时保留重要的特征信息。全连接层将特征图转化为一维向量,并通过神经网络的计算得出最终的分类结果。 通过经典卷积神经网络的代码,我们可以更加深入地了解卷积神经网络的计算过程。该代码中的全连接层实际上就是指上述提到的全连接神经网络,它将最后一次卷积操作的输出作为输入,并通过神经网络的计算产生最终的输出结果。 总之,经典卷积神经网络可以帮助我们更好地理解卷积神经网络的原理和计算过程。通过研究这个代码,我们可以深入了解卷积操作、池化操作和全连接操作在卷积神经网络中的应用,从而更好地应用和设计卷积神经网络模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

L☆★

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值