理解CNN卷积层与池化层计算

概述

深度学习中CNN网络是核心,对CNN网络来说卷积层与池化层的计算至关重要,不同的步长、填充方式、卷积核大小、池化层策略等都会对最终输出模型与参数、计算复杂度产生重要影响,本文将从卷积层与池化层计算这些相关参数出发,演示一下不同步长、填充方式、卷积核大小计算结果差异。

一:卷积层

卷积神经网络(CNN)第一次提出是在1997年,杨乐春(LeNet)大神的一篇关于数字OCR识别的论文,在2012年的ImageNet竞赛中CNN网络成功击败其它非DNN模型算法,从此获得学术界的关注与工业界的兴趣。毫无疑问学习深度学习必须要学习CNN网络,学习CNN就必须明白卷积层,池化层等这些基础各层,以及它们的参数意义,从本质上来说,图像卷积都是离散卷积,图像数据一般都是多维度数据(至少两维),离散卷积本质上是线性变换、具有稀疏与参数重用特征即相同参数可以应用输入图像的不同小分块,假设有3x3离散卷积核如下:
这里写图片描述

假设有
- 5x5的图像输入块
- 步长为1(strides=1)
- 填充方式为VALID(Padding=VALID)
- 卷积核大小filter size=3x3

则它们的计算过程与输出如下
这里写图片描述

假设这个时候我们修改步长为2、填充方式为SAME,卷积核大小不变(strides=2 Padding=SAME filter size=3x3),则计算过程与输出变为如下:
这里写图片描述

最终输出得到的结果我们可以称为featuremap,CNN的深度多数时候是指featuremap的个数,对多维度的输入图像计算多个卷积核,得到多个featuremap输出叠加,显示如下:
这里写图片描述

上述输入为5x5x2,使用卷积核3x3,输出3x3x3,填充方式为VALID,计算如果填充方式改为SAME则输出为5x5x3。可以看出填充方式对输出结果的影响。

二:小卷积核VS大卷积核

在AlexNet中有有11x11的卷积核与5x5的卷积核,但是在VGG网络中因为层数增加,卷积核都变成3x3与1x1的大小啦,这样的好处是可以减少训练时候的计算量,有利于降低总的参数数目。关于如何把大卷积核替换为小卷积核,本质上有两种方法。

1.将二维卷积差分为两个连续一维卷积

二维卷积都可以拆分为两个一维的卷积,这个是有数学依据的,所以11x11的卷积可以转换为1x11与11x1两个连续的卷积核计算,总的运算次数:

  • 11x11 = 121次
  • 1x11+ 11x1 = 22次
2.将大二维卷积用多个连续小二维卷积替代

可见把大的二维卷积核在计算环节改成两个连续的小卷积核可以极大降低计算次数、减少计算复杂度。同样大的二维卷积核还可以通过几个小的二维卷积核替代得到。比如:5x5的卷积,我们可以通过两个连续的3x3的卷积替代,比较计算次数

  • 5x5= 25次
  • 3x3+ 3x3=18次

三:池化层

在CNN网络中卷积池之后会跟上一个池化层,池化层的作用是提取局部均值与最大值,根据计算出来的值不一样就分为均值池化层与最大值池化层,一般常见的多为最大值池化层。池化的时候同样需要提供filter的大小、步长、下面就是3x3步长为1的filter在5x5的输入图像上均值池化计算过程与输出结果
这里写图片描述

改用最大值做池化的过程与结果如下:
这里写图片描述

**天下难事,
必作于易;
天下大事,
必作于细!**


关注微信公众号【OpenCV学堂】,获取更多有用知识!
这里写图片描述

  • 14
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
好的,我可以帮你回答这个问题。首先,手写数字识别的卷神经网络(CNN)通常由卷层和池化层组成。以下是用C++实现卷层和池化层的基本步骤: 卷层: 1. 定义卷核的大小和数量 2. 定义输入和输出张量的大小 3. 对于每个卷核,遍历输入张量,按照卷核大小计算每个位置的输出 4. 将输出张量传递给下一层网络 池化层: 1. 定义池化的类型(最大池化或平均池化)和池化核大小 2. 定义输入和输出张量的大小 3. 对于每个池化核,遍历输入张量,按照池化核大小计算每个位置的输出 4. 将输出张量传递给下一层网络 下面是一个卷层和一个最大池化层的C++代码实现示例: ```c++ //卷层实现 void conv_layer(float* input, float* output, float* weight, float* bias, int in_h, int in_w, int in_c, int out_h, int out_w, int out_c, int ksize, int stride, int padding) { int in_size = in_h * in_w * in_c; int out_size = out_h * out_w * out_c; int kernel_size = ksize * ksize; //遍历每个输出通道 for (int oc = 0; oc < out_c; oc++) { int out_idx = oc * out_h * out_w; for (int oh = 0; oh < out_h; oh++) { for (int ow = 0; ow < out_w; ow++) { int in_idx = 0; //遍历每个输入通道 for (int ic = 0; ic < in_c; ic++) { int kernel_idx = oc * in_c * kernel_size + ic * kernel_size; //遍历卷核大小 for (int kh = 0; kh < ksize; kh++) { for (int kw = 0; kw < ksize; kw++) { int ih = oh * stride + kh - padding; int iw = ow * stride + kw - padding; if (ih >= 0 && ih < in_h && iw >= 0 && iw < in_w) { int in_pix_idx = ic * in_h * in_w + ih * in_w + iw; int kernel_pix_idx = kh * ksize + kw; in_idx += input[in_pix_idx] * weight[kernel_idx + kernel_pix_idx]; } } } } //加上偏置项 output[out_idx++] = in_idx + bias[oc]; } } } } //最大池化层实现 void maxpool_layer(float* input, float* output, int in_h, int in_w, int in_c, int out_h, int out_w, int ksize, int stride) { int in_size = in_h * in_w * in_c; int out_size = out_h * out_w * in_c; //遍历每个输入通道 for (int ic = 0; ic < in_c; ic++) { int out_idx = ic * out_h * out_w; for (int oh = 0; oh < out_h; oh++) { for (int ow = 0; ow < out_w; ow++) { int in_idx = ic * in_h * in_w + oh * stride * in_w + ow * stride; float max_val = input[in_idx]; //遍历池化核大小 for (int kh = 0; kh < ksize; kh++) { for (int kw = 0; kw < ksize; kw++) { int cur_idx = in_idx + kh * in_w + kw; if (cur_idx < in_size && input[cur_idx] > max_val) { max_val = input[cur_idx]; } } } output[out_idx++] = max_val; } } } } ``` 希望这个示例可以帮助你理解如何用C++实现卷层和池化层

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

gloomyfish

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

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

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

打赏作者

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

抵扣说明:

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

余额充值