CNN网络主要有三部分构成:卷积层、池化层和全连接层构成,其中卷积层负责提取图像中的局部特征;池化层用来大幅降低参数量级(降维);全连接层类似神经网络的部分,用来输出想要的结果。
卷积思想
卷积Convolution,输入信息与卷积核(滤波器)的乘积。
卷积核是卷积运算过程中必不可少的一个“工具”,在卷积神经网络中,卷积核是非常重要的,它们被用来提取图像中的特征。
卷积核其实是一个小矩阵,在定义时需要考虑以下几方面的内容:
-
卷积核的个数:卷积核(过滤器)的个数决定了其输出特征矩阵的通道数。
-
卷积核的值:卷积核的值是自定义的,根据想要提取的特征来进行设置的,后续进行更新,就像中 的 一样,只是乘积运算变成卷积运算。
-
卷积核的大小:常见的卷积核有1×1、3×3、5×5等,注意一般都是奇数x奇数。
卷积计算
卷积计算过程
卷积的过程是将卷积核在图像上进行滑动计算,每次滑动到一个新的位置时,卷积核和图像进行点对点的计算,并将其求和得到一个新的值,然后将这个新的值加入到特征图中,最终得到一个新的特征图。
代码实现
用nn.Conv2d 类来创建卷积层,并应用于一张图像上。分别演示单个卷积核和多个卷积核的效果。
彩色.png 图像如图
import torch# 张量
import torch.nn as nn# 神经网络
import matplotlib.pyplot as plt# 画图
import os# 文件操作def showimg(img):plt.imshow(img)plt.axis("off")plt.show()
#显示图像并关闭图像上的坐标轴刻度# 单卷积核
def test001():dir = os.path.dirname(__file__)img = plt.imread(os.path.join(dir, "彩色.png"))# 创建卷积核# in_channels:输入数据的通道数# out_channels:输出特征图数,和filter数一直conv = nn.Conv2d(in_channels=4, out_channels=1, kernel_size=3, stride=1, padding=1)# 注意:卷积层对输入的数据有形状要求 [batch, channel, height, width]# 需要进行形状转换 H, W, C -> C, H, Wimg = torch.tensor(img, dtype=torch.float).permute(2, 0, 1)print(img.shape)# 接着变形:CHW -> NCHWnewimg = img.unsqueeze(0)print(newimg.shape)# 送入卷积核运算一下newimg = conv(newimg)print(newimg.shape)# 蒋BCHW->HWCnewimg = newimg.squeeze(0).permute(1, 2, 0)showimg(newimg.detach().numpy())# 多卷积核
def test002():dir = os.path.dirname(__file__)# 获取当前文件所在目录img = plt.imread(os.path.join(dir, "彩色.png"))# 读取图片conv = nn.Conv2d(in_channels=4, out_channels=3, kernel_size=3, stride=1, padding=1)# 创建卷积核img = torch.tensor(img).permute(2, 0, 1).unsqueeze(0)# 把图片变形# 使用卷积核对图片进行卷积计算# 输出卷积后的张量形状,此时应该是 [batch, channels, height, width]outimg = conv(img)print(outimg.shape)# 把图形形状转换回来以方便显示outimg = outimg.squeeze(0).permute(1, 2, 0)print(outimg.shape)# showimg(outimg)# 显示这些特征图for idx in range(outimg.shape[2]):showimg(outimg[:, :, idx].squeeze(-1).detach())if __name__ == "__main__":test002()"""
torch.Size([1, 3, 501, 500])
torch.Size([501, 500, 3])
"""
边缘填充Padding
持经过卷积后的图像大小不变, 可以在原图周围添加 padding 来实现。更重要的,边缘填充还更好的保护了图像边缘数据的特征。
步长Stride
要选择合适的步长
stride太小:重复计算较多,计算量大,训练效率降低;
stride太大:会造成信息遗漏,无法有效提炼数据背后的特征;
多通道卷积计算
计算方法如下:
1. 当输入有多个通道(Channel), 例如 RGB 三个通道, 此时要求卷积核需要拥有相同的通道数数.
2. 每个卷积核通道与对应的输入图像的各个通道进行卷积.
3. 将每个通道的卷积结果按位相加得到最终的特征图.
卷积层实验
结论:
-
输入特征的通道数决定了卷积核的通道数(卷积核通道个数=输入特征通道个数)。
-
卷积核的个数决定了输出特征矩阵的通道数与偏置矩阵的通道数(卷积核个数=输出特征通道数=偏置矩阵通道数)。
相关题例
-
什么是多通道卷积?在处理RGB图像时,如何体现多通道卷积的特性?
多通道卷积是指在卷积神经网络(CNN)中处理具有多个输入通道的数据的过程。在图像处理领域,输入通道通常指的是图像的颜色通道。例如,灰度图像是单通道的,而RGB图像则是三通道的,因为它们分别对应红、绿、蓝三种颜色。
在处理RGB图像时的多通道卷积特性
-
输入层:对于一个典型的RGB图像,输入层会接受一个三维数组(高度×宽度×颜色通道)。对于一个分辨率为 H \times WH×W 的RGB图像,其形状将是 (H, W, 3)(H,W,3),其中3代表三个颜色通道。
-
卷积层:当数据传递到卷积层时,该层会应用一组卷积核(也称为滤波器或特征检测器)来提取图像的不同特征。每个卷积核都是一个小的三维数组,其深度与输入图像的通道数相同,这意味着每个卷积核都会同时在所有三个颜色通道上滑动并进行计算。
-
卷积运算:卷积运算是通过将卷积核与输入图像的一部分(称为感受野)对应位置上的元素相乘,然后求和。对于RGB图像,这意味着卷积核中的每个值都会与图像中相应位置的三个颜色通道的值相乘,然后这三个乘积会被加在一起,产生一个标量输出。
-
输出特征图:经过卷积运算后,每个卷积核都会生成一个新的二维数组,称为特征图。如果使用了多个卷积核,则会得到多个特征图作为输出。每个特征图都代表了输入图像的一种特定特征,如边缘、纹理或其他模式。
-
深层网络:在深层卷积网络中,每一层的输出特征图将成为下一层的输入。因此,随着网络层次的加深,可以捕捉到越来越复杂的图像特征。
-
假设你有一个3通道(如RGB)的输入图像,其尺寸为64x64,应用一个大小为3x3、深度也为3的卷积核进行卷积操作,请描述这个过程。
import torch
import torch.nn as nn
import torch.nn.functional as F# 设置随机种子以便结果可复现
torch.manual_seed(42)# 创建一个形状为 (1, 3, 64, 64) 的随机输入图像
input_image = torch.randn(1, 3, 64, 64)# 定义一个卷积层
class ConvNet(nn.Module):def __init__(self):super(ConvNet, self).__init__()# 卷积层,输入通道为3,输出通道为1,卷积核大小为3x3self.conv1 = nn.Conv2d(in_channels=3, out_channels=1, kernel_size=3)def forward(self, x):# 应用卷积层x = self.conv1(x)return x# 实例化网络
model = ConvNet()# 应用模型到输入图像
output_feature_map = model(input_image)# 输出特征图的形状
print("Output shape:", output_feature_map.shape)
#Output shape: torch.Size([1, 1, 62, 62])
-
请计算一个大小为5x5、深度为3的卷积核应用于具有10个通道的输入时,需要多少个可学习参数。如果有偏置项,总参数量是多少?
计算卷积核参数数量
-
卷积核参数:
- 每个输入通道上的卷积核大小为5x5,所以每个输入通道上有 5 \times 5 = 255×5=25 个参数。
- 如果输入有10个通道,那么总的卷积核参数数量为 10 \times 25 = 25010×25=250。
-
输出通道数:
- 如果卷积核输出一个特征图(即输出通道数为1),那么每个输入通道都需要与卷积核进行卷积操作,但最终它们的输出将被汇总成一个输出通道。
- 因此,总的可学习参数数量仍然是250个,因为这些参数将被用来计算单个输出通道。
计算带有偏置项的总参数量
如果还包括偏置项(bias term),那么每个输出通道都会有一个偏置项。在这种情况下:
- 偏置项数量:对于一个输出通道,只需要1个偏置项。
- 总参数量:卷积核参数(250)加上偏置项(1),总共为 250 + 1 = 251250+1=251 个参数。
总结
- 仅卷积核参数:250个。
- 包括偏置项的总参数量:251个。
这个计算假设输出通道数为1。如果输出通道数大于1,那么每个额外的输出通道都将有自己的250个卷积核参数和一个偏置项。例如,如果有2个输出通道,那么总的参数数量将是 2 \times (250 + 1) = 5022×(250+1)=502。
-
解释卷积层中的偏置项是什么,并讨论在神经网络中引入偏置项的好处。
卷积层中的偏置项
在卷积神经网络(CNN)中,偏置项(bias)是一个附加到卷积操作结果的标量值。在数学上,偏置项可以被视为一个常数向量,它在卷积运算之后被加到每个输出特征图的每个位置上。
偏置项的作用
-
移位输出:偏置项可以使得卷积层的输出特征图在数值上发生平移。这意味着即使输入图像的像素值全为零,卷积层也能产生非零的输出。这是因为偏置项的存在允许模型在没有任何输入的情况下也能输出某些值。
-
提高灵活性:通过引入偏置项,模型变得更加灵活,因为它可以在不改变权重的情况下改变输出。这有助于模型适应更多的模式和变化。
-
避免激活函数的局限性:某些激活函数(如ReLU)会在输入为负数时输出0。如果没有偏置项,那么在某些情况下,即使卷积核与输入图像的某个区域非常匹配,只要这个匹配的结果不足以克服激活函数的阈值,输出也会为0。偏置项可以帮助克服这种局限性,使得即使是较小的权重变化也能导致有效的输出。
引入偏置项的好处
-
增强表达能力:偏置项可以增强神经网络的表达能力,使得模型能够在训练过程中更好地拟合训练数据。它提供了额外的自由度,使得模型可以更准确地学习输入数据中的模式。
-
加速收敛:偏置项可以帮助优化过程更快地收敛。如果没有偏置项,模型可能需要更多的时间来调整权重以达到最佳拟合状态。有了偏置项,模型可以更快地找到合适的权重组合。
-
简化初始化:在模型初始化阶段,如果没有偏置项,所有节点的初始输出可能会非常相似,这会导致梯度更新的方向和幅度相似,从而影响训练效果。引入偏置项可以避免这种情况,使得初始化时的输出更加多样化。
池化层Pooling
池化层 (Pooling) 降低维度, 缩减模型大小,提高计算速度. 即: 主要对卷积层学习到的特征图进行下采样(SubSampling)处理.
池化层的类型
-
最大池化(Max Pooling):
- 每个池化窗口内选取最大的值作为输出。这种方法保留了窗口内的最大值,通常被认为是更具有鉴别性的。
-
平均池化(Average Pooling):
- 每个池化窗口内计算所有值的平均值作为输出。这种方法平滑了特征图,有助于减少噪声的影响。
PyTorch 池化 API使用
import torch
import torch.nn as nn# 1. API 基本使用
def test01():inputs = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]]).float()inputs = inputs.unsqueeze(0).unsqueeze(0)#增加通道维度# 1. 最大池化# 输入形状: (N, C, H, W)polling = nn.MaxPool2d(kernel_size=2, stride=1, padding=0)"""
创建最大池化层:定义一个最大池化层 polling,参数说明如下:
kernel_size=2:池化窗口的大小为 2x2。
stride=1:池化窗口在输入数据上移动的步长为1。
padding=0:不使用填充。
"""output = polling(inputs)print(output)# 2. 平均池化polling = nn.AvgPool2d(kernel_size=2, stride=1, padding=0)output = polling(inputs)print(output)# 2. stride 步长
def test02():inputs = torch.tensor([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]]).float()inputs = inputs.unsqueeze(0).unsqueeze(0)# 1. 最大池化polling = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)output = polling(inputs)print(output)# 2. 平均池化polling = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)output = polling(inputs)print(output)# 3. padding 填充
def test03():inputs = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]]).float()inputs = inputs.unsqueeze(0).unsqueeze(0)# 1. 最大池化polling = nn.MaxPool2d(kernel_size=2, stride=1, padding=1)output = polling(inputs)print(output)# 2. 平均池化polling = nn.AvgPool2d(kernel_size=2, stride=1, padding=1)output = polling(inputs)print(output)# 4. 多通道池化
def test04():inputs = torch.tensor([[[0, 1, 2], [3, 4, 5], [6, 7, 8]],[[10, 20, 30], [40, 50, 60], [70, 80, 90]],[[11, 22, 33], [44, 55, 66], [77, 88, 99]]]).float()inputs = inputs.unsqueeze(0)# 最大池化polling = nn.MaxPool2d(kernel_size=2, stride=1, padding=0)output = polling(inputs)print(output)if __name__ == '__main__':test04()"""
tensor([[[[ 4., 5.],[ 7., 8.]],[[50., 60.],[80., 90.]],[[55., 66.],[88., 99.]]]])
"""
-
简述在卷积神经网络中池化层的作用,并解释其为何能帮助提高模型性能。
在卷积神经网络(CNN)中,池化层(Pooling Layer)是一种常用的下采样(Downsampling)技术,其主要作用是减少数据的空间维度(高度和宽度),从而降低后续网络层的计算复杂度,并且帮助模型学习更具鲁棒性的特征表示。
池化层的作用
-
降低维度:
池化层通过将输入数据划分为几个不重叠的小区域(通常称为“窗口”),并对每个区域执行某种形式的操作(如最大值或平均值),来降低输入数据的维度。这有效地减少了特征图的尺寸,降低了计算量和内存消耗。 -
平移不变性:
池化操作有助于提高模型对输入数据中特征位置变化的鲁棒性,即平移不变性。即使输入特征的位置发生了一定的变化,池化后的特征仍然能够捕获这些特征的大致信息。 -
抑制过拟合:
通过减少特征图的尺寸,池化层可以减少网络的参数数量,从而减轻过拟合的风险。较少的参数意味着更少的学习自由度,这有助于模型更好地泛化到未见过的数据。
如何提高模型性能
-
减少计算复杂度:
池化层通过减少特征图的尺寸,可以显著减少后续层的计算负担。这对于处理大规模输入数据集特别有用,因为较大的输入会导致计算成本急剧上升。 -
增强鲁棒性:
池化操作可以使得模型对输入数据中的小变化更加鲁棒。例如,在图像识别任务中,即使物体的位置发生了一些偏移,池化后的特征仍然能够提供关于物体存在的信息。 -
减少过拟合:
通过减少特征图的尺寸,池化层可以减少模型的参数数量,从而减轻过拟合的问题。更少的参数意味着模型需要学习的模式更简单,这有助于提高模型在新数据上的泛化能力。 -
加速训练过程:
由于减少了计算量,池化层还可以加速训练过程。这使得在有限的计算资源下训练更深或更复杂的模型成为可能。
-
描述最大池化和平均池化的具体计算步骤,包括如何进行窗口滑动、取最大值或平均值的操作。
最大池化(Max Pooling)
-
定义窗口大小:
通常,窗口大小是一个二维的矩形区域,例如 k \times kk×k,其中 kk 是窗口的边长。常见的窗口大小有 2 \times 22×2,3 \times 33×3 等。 -
确定步长(Stride):
步长决定了窗口在每次移动时跨越的像素数。例如,步长为1意味着窗口每次移动一个像素,步长为2意味着每次移动两个像素。 -
窗口滑动:
- 从输入特征图的左上角开始,将窗口放置在输入特征图上。
- 然后根据步长移动窗口,直到覆盖整个输入特征图。
-
取最大值:
- 对于窗口覆盖的每个区域,找出该区域内的最大值。
- 这个最大值将作为输出特征图中的一个像素值。
-
生成输出特征图:
- 重复上述步骤,直到窗口覆盖了整个输入特征图。
- 最终得到的输出特征图的尺寸将取决于输入特征图的尺寸、窗口大小以及步长。
平均池化(Average Pooling)
-
定义窗口大小:
同样地,窗口大小也是一个二维的矩形区域,例如 k \times kk×k。 -
确定步长(Stride):
步长的定义与最大池化相同。 -
窗口滑动:
窗口从输入特征图的左上角开始,并根据步长滑动。 -
计算平均值:
- 对于窗口覆盖的每个区域,计算该区域内所有像素值的平均值。
- 这个平均值将作为输出特征图中的一个像素值。
-
生成输出特征图:
- 重复上述步骤,直到窗口覆盖了整个输入特征图。
- 最终得到的输出特征图的尺寸同样取决于输入特征图的尺寸、窗口大小以及步长。
- 分析池化层如何实现下采样(downsampling),并讨论这种降维操作如何有助于防止过拟合。
池化层如何实现下采样
-
定义池化窗口:
池化操作通过定义一个固定大小的窗口(例如 k \times kk×k)来实现。这个窗口会在输入特征图上滑动,并对每个窗口内的数据执行某种聚合操作(如最大值或平均值)。 -
窗口滑动:
窗口从输入特征图的左上角开始滑动,每次滑动的距离由步长(stride)决定。步长大于1时,会跳过某些像素,从而实现下采样。 -
聚合操作:
在每个窗口内执行聚合操作。例如,在最大池化中,每个窗口内的最大值被选出作为输出特征图中的一个像素值;而在平均池化中,则是计算窗口内所有值的平均值。 -
生成输出特征图:
经过上述操作后,生成的输出特征图具有比输入特征图更小的尺寸,实现了数据的下采样。
下采样如何防止过拟合
-
减少参数数量:
通过下采样,可以显著减少特征图的尺寸,从而减少后续层的输入维度。这意味着后续层的参数数量也会减少。较少的参数数量可以减少模型的复杂度,从而减少过拟合的风险。 -
提高特征鲁棒性:
池化操作(特别是最大池化)可以帮助模型学习到更加鲁棒的特征。最大池化保留了每个窗口内的最大值,这意味着即使输入数据中的某些部分发生了微小变化,池化后的特征仍然能够反映这些变化。这种鲁棒性有助于模型在面对不同的输入时表现更加一致。 -
平移不变性:
池化操作有助于提高模型对输入数据中特征位置变化的鲁棒性。即使输入特征的位置发生了一定的变化,池化后的特征仍然能够提供关于物体存在的信息。这种平移不变性使得模型对输入数据的位置变化不太敏感,从而有助于提高泛化能力。 -
加速训练过程:
减少数据的维度可以加速训练过程,因为每次迭代需要处理的数据量减少了。这使得训练更大或更复杂的模型成为可能,同时也能在较短的时间内完成训练。
感受野
字面意思是感受的视野范围
堆叠小的卷积核所需的参数更少一些,并且卷积过程越多,特征提取 也会越细致,加入的非线性变换也随着增多,还不会增大权重参数个数,这就是感受野的基本出发点,用小的卷积核来完成体特征提取操作。