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。
栗子1: 一个特征图尺寸为4*4的输入,使用3*3的卷积核,步幅=1,填充=0
输出的尺寸=(4 - 3)/1 + 1 = 2
栗子2: 一个特征图尺寸为5*5的输入,使用3*3的卷积核,步幅=1,填充=1
输出的尺寸=(5 + 2*1 - 3)/1 + 1 = 5
栗子3: 一个特征图尺寸为5*5的输入, 使用3*3的卷积核,步幅=2,填充=0
输出的尺寸=(5-3)/2 + 1 = 2
栗子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],但这种情况较少见,因为通常不会在批处理维度或通道维度上应用步幅。
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);
所以,在前面的图中,我加一个激活函数,给对应的部分标上符号,就是这样的:
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博客