卷积神经网络(CNN)理解

1、引言(卷积概念)

在介绍CNN中卷积概念之前,先介绍一个数字图像中"边缘检测edge detection"案例,以加深对卷积的认识。图中为大小8X8的灰度图片,图片中数值表示该像素的灰度值。像素值越大,颜色越亮,所以为了示意,我们把右边小像素的地方画成深色,图的中间两个颜色的分界线就是我们要检测的边界。

该怎么检测边界呢?我们可以设计这样的一个 滤波器(filter,也称为kernel),大小3×3:

然后,我们用这个filter,往我们的图片上"盖",覆盖一块跟filter一样大的区域之后,对应元素相乘,然后求和。计算一个区域之后,就向其他区域挪动,接着计算,直到把原图片的每一个角落都覆盖到了为止。这个过程就是 "卷积"。 (我们不用管卷积在数学上到底是指什么运算,我们只用知道在CNN中是怎么计算的。) 这里的"挪动",就涉及到一个步长,假如我们的步长是1,那么覆盖了一个地方之后,就挪一格,容易知道,总共可以覆盖6×6个不同的区域。

通过上述操作,发现边界被准确探测出。

卷积神经网络(CNN)中的卷积,就是通过一个个的filter,不断地提取特征,从局部的特征到总体的特征,从而进行图像识别等等功能。

那么问题来了,我们怎么可能去设计这么多各种各样的filter?首先,我们都不一定清楚对于一大堆图片,我们需要识别哪些特征,其次,就算知道了有哪些特征,想真的去设计出对应的filter,恐怕也并非易事,要知道,特征的数量可能是成千上万的。

其实学过神经网络之后,我们就知道,这些filter,根本就不用我们去设计,每个filter中的各个数字,不就是参数吗,我们可以通过大量的数据,来让机器自己去"学习"这些参数嘛。这就是CNN的原理。

2、CNN基本概念

在理解CNN中卷积概念后,有一些基本概念需要理解,包括padding填白、stride步长、pooling池化、对多通道图片卷积。

2.1 padding填白

从上面的引子中,我们可以知道,原图像在经过filter卷积之后,变小了,从(8,8)变成了(6,6)。假设我们再卷一次,那大小就变成了(4,4)了。

这样有啥问题呢? 主要有两个问题: 每次卷积,图像都缩小,这样卷不了几次就没了; 相比于图片中间的点,图片边缘的点在卷积中被计算的次数很少。这样的话,边缘的信息就易于丢失。

为了解决这个问题,我们可以采用padding的方法。我们每次卷积前,先给图片周围都补一圈空白,让卷积之后图片跟原来一样大,同时,原来的边缘也被计算了更多次。

比如,我们把(8,8)的图片给补成(10,10),那么经过(3,3)的filter之后,就是(8,8),没有变。

我们把上面这种"让卷积之后的大小不变"的padding方式,称为 "Same"方式, 把不经过任何填白的,称为 "Valid"方式。这个是我们在使用一些框架的时候,需要设置的超参数。

2.2 stride步长

前面我们所介绍的卷积,都是默认步长是1,但实际上,我们可以设置步长为其他的值。 比如,对于(8,8)的输入,我们用(3,3)的filter, 如果stride=1,则输出为(6,6); 如果stride=2,则输出为(3,3)。

举栗子:

  • 栗子1:stride=1,padding=0(遍历采样,无填充:padding='valid')
  • 栗子2:stride=1,padding=1(遍历采样,有填充:padding='same')
  • 栗子3:stride=2,padding=0(降采样,无填充:尺寸缩小二点五分之一)
  • 栗子4:stride=2,padding=1(降采样,有填充;尺寸缩小二分之一)

Stride的作用:是成倍缩小尺寸,而这个参数的值就是缩小的具体倍数,比如步幅为2,输出就是输入的1/2;步幅为3,输出就是输入的1/3。

python 复制代码
栗子1:  一个特征图尺寸为4*4的输入,使用3*3的卷积核,步幅=1,填充=0
        输出的尺寸=(4 - 3)/1 + 1 = 2
python 复制代码
栗子2:  一个特征图尺寸为5*5的输入,使用3*3的卷积核,步幅=1,填充=1
        输出的尺寸=(5 + 2*1 - 3)/1 + 1 = 5

python 复制代码
栗子3:  一个特征图尺寸为5*5的输入, 使用3*3的卷积核,步幅=2,填充=0
         输出的尺寸=(5-3)/2 + 1 = 2

python 复制代码
栗子4:  一个特征图尺寸为6*6的输入, 使用3*3的卷积核,步幅=2,填充=1
         输出的尺寸=(6 + 2*1 - 3)/2 + 1 = 2.5 + 1 = 3.5 向下取整=3
         (降采样:边长减少1/2)

在PyTorch中,卷积层的步幅(stride)也是一个一维张量,通常包含两个或四个元素,具体取决于卷积层的类型(2D或3D卷积)。对于2D卷积层,步幅通常是一个包含两个元素的张量,格式为 [stride_height, stride_width]。对于3D卷积或其他更复杂的层,步幅可能包含四个元素,格式为 [stride_batch, stride_height, stride_width, stride_channel],但这种情况较少见,因为通常不会在批处理维度或通道维度上应用步幅。

python 复制代码
import torch.nn as nn

# 定义一个2D卷积层,步幅为(2, 2)
conv_layer = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=(2, 2), padding=1)

在这个例子中,stride=(2, 2) 表示卷积核在高度和宽度方向上每次移动两个像素。这种设置通常用于减少特征图的尺寸,实现下采样。

2.3 pooling池化

这个pooling,是为了提取一定区域的主要特征,并减少参数数量,防止模型过拟合。 比如下面的MaxPooling,采用了一个2×2的窗口,并取stride=2:

除了MaxPooling,还有AveragePooling,顾名思义就是取那个区域的平均值。

2.4 对多通道(channels)图片的卷积

这个需要单独提一下。彩色图像,一般都是RGB三个通道(channel)的,因此输入数据的维度一般有三个:(长,宽,通道)。 比如一个28×28的RGB图片,维度就是(28,28,3)。

前面的引子中,输入图片是2维的(8,8),filter是(3,3),输出也是2维的(6,6)。

如果输入图片是三维的呢(即增多了一个channels),比如是(8,8,3),这个时候,我们的filter的维度就要变成(3,3,3)了,它的最后一维要跟输入的channel维度一致。 这个时候的卷积,是三个channel的所有元素对应相乘后求和,也就是之前是9个乘积的和,现在是27个乘积的和。因此,输出的维度并不会变化。还是(6,6)。

但是,一般情况下,我们会 使用多个filters同时卷积,比如,如果我们同时使用4个filter的话,那么 输出的维度则会变为(6,6,4)。

图中的输入图像是(8,8,3),filter有4个,大小均为(3,3,3),得到的输出为(6,6,4)。

其实,如果套用我们前面学过的神经网络的符号来看待CNN的话,

  • 我们的输入图片就是X,shape=(8,8,3);
  • 4个filters其实就是第一层神金网络的参数W1,shape=(3,3,3,4),这个4是指有4个filters;
  • 我们的输出,就是Z1,shape=(6,6,4);
  • 后面其实还应该有一个激活函数,比如relu,经过激活后,Z1变为A1,shape=(6,6,4);

所以,在前面的图中,我加一个激活函数,给对应的部分标上符号,就是这样的:

在pytorch中,自定义卷积层代码如下:

python 复制代码
        self.conv2 = nn.Sequential(
            nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
  • 卷积层 (nn.Conv2d):

    • in_channels=16: 输入通道数,这里是16,意味着这个卷积层期望接收具有16个通道的输入数据。
    • out_channels=32: 输出通道数,设置为32,表示这个卷积层会产生32个特征图(filters/feature maps)。
    • kernel_size=3: 卷积核的大小是3x3像素。
    • stride=1: 步长为1,表示卷积核每次移动一个像素。
    • padding=1: 填充为1,表示在输入数据的周围添加一圈0值填充,使得卷积操作后输出的空间维度与输入相同。
  • 激活层 (nn.ReLU):

    • 应用ReLU(Rectified Linear Unit)激活函数,它将每个输入值转换为最大值0和输入值本身。这有助于引入非线性,使得网络能够学习更复杂的特征。
  • 池化层 (nn.MaxPool2d):

    • kernel_size=2: 池化窗口的大小是2x2像素。
    • stride=2: 步长为2,表示池化窗口每次移动两个像素。这通常会导致输出特征图的空间尺寸减半。

3、CNN的结构组成

上面我们已经知道了卷积(convolution)、池化(pooling)以及填白(padding)是怎么进行的,接下来我们就来看看CNN的整体结构,它包含了3种层(layer):

3.1 卷积层(Convolutional layer, conv)

由滤波器filters和激活函数构成。 一般要设置的超参数包括filters的数量、大小、步长,以及padding是"valid"还是"same"。当然,还包括选择什么激活函数。

3.2 Pooling layer(池化层-pool)

这里里面没有参数需要我们学习,因为这里里面的参数都是我们设置好了,要么是Maxpooling,要么是Averagepooling。 需要指定的超参数,包括是Max还是average,窗口大小以及步长。 通常,我们使用的比较多的是Maxpooling,而且一般取大小为(2,2)步长为2的filter,这样,经过pooling之后,输入的长宽都会缩小2倍,channels不变。

3.3 Fully Connected layer(全链接层-FC)

全连接层(Fully Connected Layer),也称为密集层(Dense Layer),是神经网络中的一层,其中每个神经元都与前一层的所有神经元相连接。全连接层通常用于神经网络的最后几层,将学到的特征映射到最终的输出,例如分类任务的类别数。

在全连接层中,每个神经元都有一组参数(权重和偏置),这些参数用于对输入数据进行线性变换,然后通过激活函数进行非线性变换。全连接层的输出是输入数据和权重矩阵的线性组合,再加上偏置向量。

参考博客:

【1】多通道图片的卷积_多通道卷积过程-CSDN博客

【2】CNN中stride(步幅)和padding(填充)的详细理解_cnn stride-CSDN博客

相关推荐
Captain823Jack1 小时前
nlp新词发现——浅析 TF·IDF
人工智能·python·深度学习·神经网络·算法·自然语言处理
Captain823Jack2 小时前
w04_nlp大模型训练·中文分词
人工智能·python·深度学习·神经网络·算法·自然语言处理·中文分词
itwangyang5202 小时前
AIDD - 从机器学习到深度学习:蛋白质-配体对接评分函数的进展
人工智能·深度学习·机器学习
mingo_敏3 小时前
深度学习中的并行策略概述:2 Data Parallelism
人工智能·深度学习
湫ccc5 小时前
《Opencv》基础操作详解(3)
人工智能·opencv·计算机视觉
Jack_pirate5 小时前
深度学习中的特征到底是什么?
人工智能·深度学习
微凉的衣柜5 小时前
微软在AI时代的战略布局和挑战
人工智能·深度学习·microsoft
哦哦~9216 小时前
深度学习驱动的油气开发技术与应用
大数据·人工智能·深度学习·学习
西西弗Sisyphus8 小时前
探索多模态大语言模型(MLLMs)的推理能力
人工智能·计算机视觉·语言模型·大模型
pk_xz12345610 小时前
OpenCV实现实时人脸检测和识别
人工智能·opencv·计算机视觉