撰写网站建设策划书范文,网站面包屑怎么做,百度手机助手下载免费安装,官网做的好看的网站有哪些专栏#xff1a;神经网络复现目录
卷积神经网络 本章介绍的卷积神经网络#xff08;convolutional neural network#xff0c;CNN#xff09;是一类强大的、为处理图像数据而设计的神经网络。 基于卷积神经网络架构的模型在计算机视觉领域中已经占主导地位#xff0c;当今…专栏神经网络复现目录
卷积神经网络 本章介绍的卷积神经网络convolutional neural networkCNN是一类强大的、为处理图像数据而设计的神经网络。 基于卷积神经网络架构的模型在计算机视觉领域中已经占主导地位当今几乎所有的图像识别、目标检测或语义分割相关的学术竞赛和商业应用都以这种方法为基础。
文章部分文字和代码来自《动手学深度学习》 文章目录卷积神经网络从全连接层到卷积层两个性质1. 平移不变性2. 局部性卷积的数理逻辑使用平移不变性推导使用局部性推导通道图像卷积互相关运算定义代码实现卷积层图像中目标的边缘检测构造图像构造卷积核进行卷积运算卷积核的获取特征映射和感受野填充和步幅填充步幅多输入多输出通道多输入通道多通道输出1 ×\times× 1 卷积层汇聚层/池化层最大汇聚层和平均汇聚层pytorch中的使用多个通道卷积神经网络(LeNet)定义实现模型训练测试从全连接层到卷积层
首先思考一个问题为什么不使用全连接层我们在上一节明明取得了很好的效果。
答 全连接层有其巨大的缺陷参数过多 由于计算机性能的限制过多的参数使其并不能拥有较多的层数故没有办法达到很好的效果。
举个例子
假设我们有一个足够充分的照片数据集数据集中是拥有标注的照片每张照片具有百万级像素这意味着网络的每次输入都有一百万个维度。 即使将隐藏层维度降低到1000这个全连接层也将有个参数。 想要训练这个模型将不可实现因为需要有大量的GPU、分布式优化训练的经验和超乎常人的耐心。
下面我们开始学习如何简化网络。
两个性质
首先我们学习计算机视觉网络框架中比较重要的两个性质
1. 平移不变性
平移不变性Translation Invariance是指一个模型对于输入数据的平移操作不敏感。也就是说如果对于输入数据进行平移操作后模型输出不变则模型具有平移不变性。
通俗的说不管检测对象出现在图像中的哪个位置神经网络的前面几层应该对相同的图像区域具有相似的反应即为“平移不变性”。
在计算机视觉领域图像是由像素点组成的每个像素点都具有一个坐标。由于图像中物体的位置、大小、旋转等都可能发生变化如果模型不能处理平移操作那么在图像中同一物体的不同位置上都要训练一个独立的模型这将导致需要训练的模型数目大大增加。
一个例子是对于一个图像分类任务如果对输入图像进行平移操作比如将图像整体向左平移一个像素那么图像的内容和类别并没有发生变化但是像素矩阵的数值却发生了变化。如果我们的模型没有考虑到这种平移操作那么对于同一张图像的不同平移版本模型可能会做出不同的预测结果。因此为了提高模型的泛化能力我们需要考虑平移不变性使得模型对于图像平移的变化具有鲁棒性。
2. 局部性
局部性是指在一个数据集中相邻的数据往往具有相关性或者相似性。这种相关性或相似性可能是空间上的也可能是时间上的或者是其他方面的。例如对于一组包含了大量图片的数据集其中相邻的图片很可能是相似的即它们有相似的纹理、颜色、形状等等特征。在图像处理任务中通常会利用局部性来设计卷积神经网络以利用相邻像素之间的相关性。
卷积神经网络中是指神经网络的前面几层应该只探索输入图像中的局部区域而不过度在意图像中相隔较远区域的关系这就是“局部性”原则。最终可以聚合这些局部特征以在整个图像级别进行预测。
一个计算机视觉的例子是图像识别其中局部性是指每个图像中的像素点只与其周围像素相关而不受图像其他部分的影响。这是因为在图像中每个像素点的意义仅仅取决于其在图像中的位置而与整个图像的大小和形状无关。例如如果我们想要识别一张包含猫的图片我们只需要观察每个像素点的灰度值以及每个像素点周围的一些像素而不需要考虑图像的整体形状和大小。因此计算机视觉中的许多算法都利用了这种局部性来实现高效的图像处理和分析。
卷积的数理逻辑
首先多层感知机的输入是二维图像XXX其隐藏表示HHH在数学上是一个矩阵在代码中表示为二维张量。 其中XXX和HHH具有相同的形状。 为了方便理解我们可以认为无论是输入还是隐藏表示都拥有空间结构。
使用[X]i,j[X]_{i,j}[X]i,j和[H]i,j[H]_{i,j}[H]i,j分别表示输入图像和隐藏表示中位置i,j处的像素。 为了使每个隐藏神经元都能接收到每个输入像素的信息我们将参数从权重矩阵如同我们先前在多层感知机中所做的那样替换为四阶权重张量。假设包含偏置参数我们可以将全连接层形式化地表示为: [H]i,j[U]i,j[W]i,j[X][U]i,j∑k∑l[W]i,j,k,l[X]k,l[U]i,j∑a∑b[V]i,j,a,b[X]ia,jb.[ H ] _ { i , j }[ U ] _ { i , j } [ W ] _ { i , j } [ X ] \\ [ U ] _ { i , j } \sum _ { k } \sum _ { l } [ W ] _ { i , j , k , l } [ X ] _ { k , l }\\ [ U ] _ { i , j } \sum _ { a } \sum _ { b } [ V ] _ { i , j , a , b } [ X ] _ { i a , j b } .[H]i,j[U]i,j[W]i,j[X][U]i,jk∑l∑[W]i,j,k,l[X]k,l[U]i,ja∑b∑[V]i,j,a,b[X]ia,jb.
其中从W到V的转换只是形式上的转换因为在这两个四阶张量的元素之间存在一一对应的关系。 我们只需重新索引下标(k,l)使kialjb由此可得[V]i,j,a,b[W]i,j,ia,jb[ V ] _ { i , j ,a,b}[ W ] _ { i , j,ia,jb }[V]i,j,a,b[W]i,j,ia,jb 。索引a和b通过在正偏移和负偏移之间移动覆盖了整个图像。
使用平移不变性推导
现在引用上述的第一个原则平移不变性。 这意味着检测对象在输入XXX中的平移应该仅导致隐藏表示HHH中的平移,不改变值。
也就是说VVV和UUU实际上不依赖于的(iii,jjj)值即 [V]i,j,a,b[V]a,b[ V ] _ { i , j ,a,b}[ V ] _ { a,b}[V]i,j,a,b[V]a,b 还记得刚才我们说什么吗 不管检测对象出现在图像中的哪个位置神经网络的前面几层应该对相同的图像区域具有相似的反应 上述公式就是这个意思不依赖对象出现的位置位置坐标并不是参数于是有了简化。 并且U是一个常数比如u。因此我们可以简化定义为 [H]i,ju∑a∑b[V]a,b[X]ia,jb[ H ] _ { i , j } u \sum _ { a } \sum _ { b } [V ] _ { a,b} [ X ] _ {i a , j b } [H]i,jua∑b∑[V]a,b[X]ia,jb
使用局部性推导
现在引用上述的第二个原则局部性。如上所述为了收集用来训练参数[H]i,j[ H ] _ { i , j }[H]i,j的相关信息我们不应偏离到距(i,j)很远的地方。这意味着在|a|△或|b|△的范围之外我们可以设置 [V]a,b0[V ] _ { a,b}0[V]a,b0因此公式可简化为 [H]i,ju∑a−△△∑b−△△[V]a,b[X]ia,jb[ H ] _ { i , j } u \sum _ { a -△}^△ \sum _ {b -△}^△ [V ] _ { a,b} [ X ] _ {i a , j b } [H]i,jua−△∑△b−△∑△[V]a,b[X]ia,jb
这样我们得出了卷积的公式
通道
然而这种方法有一个问题我们忽略了图像一般包含三个通道/三种原色红色、绿色和蓝色。 实际上图像不是二维张量而是一个由高度、宽度和颜色组成的三维张量比如包含1024 ∗*∗ 1024 ∗*∗ 3个像素。 前两个轴与像素的空间位置有关而第三个轴可以看作每个像素的多维表示。 因此我们将X索引为[X]i,j,k[ X] _ { i , j,k }[X]i,j,k,由此卷积相应地调整为[V]a,b,c[V] _ { a,b,c }[V]a,b,c
此外由于输入图像是三维的我们的隐藏表示HHH也最好采用三维张量。 换句话说对于每一个空间位置我们想要采用一组而不是一个隐藏表示。这样一组隐藏表示可以想象成一些互相堆叠的二维网格。 因此我们可以把隐藏表示想象为一系列具有二维张量的通道channel。 这些通道有时也被称为特征映射feature maps因为每个通道都向后续层提供一组空间化的学习特征。 直观上可以想象在靠近输入的底层一些通道专门识别边缘而一些通道专门识别纹理。
为了支持输入X和隐藏表示H中的多个通道我们可以在中添加第四个坐标即d。综上所述 [H]i,j,dud∑a−△△∑b−△△∑c[V]a,b,c,d[X]ia,jb,c[ H ] _ { i , j ,d} u_d \sum _ { a -△}^△ \sum _ {b -△}^△\sum _ {c } [V ] _ { a,b,c,d} [ X ] _ {i a , j b,c } [H]i,j,duda−△∑△b−△∑△c∑[V]a,b,c,d[X]ia,jb,c
其中隐藏表示HHH中的索引ddd表示输出通道而随后的输出将继续以三维张量HHH作为输入进入下一个卷积层。
图像卷积
互相关运算
定义
互相关运算cross-correlation operation是卷积运算的一种形式两者在计算方式上类似但是卷积运算通常会在核函数上进行翻转也称为旋转180度而互相关运算则不进行翻转。
具体来说给定一个输入张量和一个卷积核张量它们在输入的每个位置上做卷积操作可以将卷积核看作一个模板在输入上滑动这个模板对于模板覆盖的区域进行元素乘积的累加将其作为输出的对应位置的值。互相关运算与卷积运算的区别在于卷积运算在计算中会先将卷积核进行翻转或旋转180度再进行与输入的元素乘积和求和而互相关运算则不翻转卷积核直接进行元素乘积和求和。
数学公式表示
对于二维输入张量 XXX 和卷积核张量 KKK它们的互相关运算可以表示为
Yi,j∑a1h∑b1wXia−1,jb−1Ka,bY_{i,j}\sum_{a1}^{h}\sum_{b1}^{w}X_{ia-1,jb-1}K_{a,b}Yi,j∑a1h∑b1wXia−1,jb−1Ka,b
其中hhh 和 www 分别是卷积核的高度和宽度iii 和 jjj 分别是输出张量 YYY 中的高度和宽度的索引。这个公式可以理解为对于输出张量 YYY 中的每一个位置 (i,j)(i,j)(i,j)用输入张量 XXX 中以 (i,j)(i,j)(i,j) 为左上角的 h×wh\times wh×w 的矩形区域与卷积核 KKK 进行元素乘积和求和得到输出张量 YYY 中位置 (i,j)(i,j)(i,j) 的值。
图示
输出矩阵的大小与 输入矩阵大小和核函数大小有关公式为
代码实现
import torch
from torch import nn
from d2l import torch as d2ldef corr2d(X, K): #save计算二维互相关运算h, w K.shapeY torch.zeros((X.shape[0] - h 1, X.shape[1] - w 1))for i in range(Y.shape[0]):for j in range(Y.shape[1]):Y[i, j] (X[i:i h, j:j w] * K).sum()return Y卷积层
class Conv2D(nn.Module):def __init__(self, kernel_size):super().__init__()self.weight nn.Parameter(torch.rand(kernel_size))self.bias nn.Parameter(torch.zeros(1))def forward(self, x):return corr2d(x, self.weight) self.bias这是一个使用 PyTorch 实现的二维卷积层的简单代码。其中kernel_size 参数表示卷积核的大小。
在初始化方法 init 中我们使用 nn.Parameter 创建了权重张量 self.weight 和偏置项 self.bias它们都是可训练的模型参数。
在前向传播方法 forward 中我们调用了之前定义的互相关函数 corr2d将输入 x 和权重张量 self.weight 作为输入进行卷积运算并加上偏置项 self.bias。
这个简单的实现只是展示了卷积层的基本实现思路实际的卷积层还有很多优化和扩展例如不同的卷积操作、不同的卷积核初始化方法、多通道卷积等。
图像中目标的边缘检测
学到这里可以进行一个卷积层的简单实验通过像素变化的位置来检测图像中不同颜色的边缘。
构造图像
X torch.ones((6, 8))
X[:, 2:6] 0结果 [[1., 1., 0., 0., 0., 0., 1., 1.], [1., 1., 0., 0., 0., 0., 1., 1.], [1., 1., 0., 0., 0., 0., 1., 1.], [1., 1., 0., 0., 0., 0., 1., 1.], [1., 1., 0., 0., 0., 0., 1., 1.], [1., 1., 0., 0., 0., 0., 1., 1.]]
构造卷积核
K torch.tensor([[1.0, -1.0]])这个卷积核在进行互关连运算时如果水平相邻的元素一致则输出0否则为其他值在这里为1
进行卷积运算
Y corr2d(X, K)
Y结果为 [[ 0., 1., 0., 0., 0., -1., 0.], [ 0., 1., 0., 0., 0., -1., 0.], [ 0., 1., 0., 0., 0., -1., 0.], [ 0., 1., 0., 0., 0., -1., 0.], [ 0., 1., 0., 0., 0., -1., 0.], [ 0., 1., 0., 0., 0., -1., 0.]]
这样我们就找到了水平边缘但是如果图像转置一下得出的结果为全0说明没有垂直边缘 猜想 我们在进行了水平边缘检测后可以对原图像转置此时得出的结果为垂直边缘监测二者的叠加基本可以确定图像的边缘 卷积核的获取
如果我们只需寻找黑白边缘那么以上[1, -1]的边缘检测器足以。然而当有了更复杂数值的卷积核或者连续的卷积层时我们不可能手动设计滤波器。那么我们是否可以学习由X生成Y的卷积核呢
# 构造一个二维卷积层它具有1个输出通道和形状为12的卷积核
conv2d nn.Conv2d(1,1, kernel_size(1, 2), biasFalse)# 这个二维卷积层使用四维输入和输出格式批量大小、通道、高度、宽度
# 其中批量大小和通道数都为1
X X.reshape((1, 1, 6, 8))
Y Y.reshape((1, 1, 6, 7))
lr 3e-2 # 学习率for i in range(10):Y_hat conv2d(X)l (Y_hat - Y) ** 2conv2d.zero_grad()l.sum().backward()# 迭代卷积核conv2d.weight.data[:] - lr * conv2d.weight.gradif (i 1) % 2 0:print(fepoch {i1}, loss {l.sum():.3f})这里的XY都已知
以上代码实现了一个简单的二维卷积神经网络其具体实现过程如下
首先定义了一个 nn.Conv2d 的卷积层它有 1 个输入通道1 个输出通道卷积核大小为 (1, 2)且不使用偏置项。
定义输入数据 X 和标签 Y并且对数据进行了 reshape使其变为四维格式即 (批量大小, 通道数, 图像高度, 图像宽度)在这里批量大小和通道数都为 1。
设定学习率为 3e-2。
进行训练共迭代 10 次。每次迭代首先通过 conv2d(X) 得到网络的输出 Y_hat接着计算网络的损失这里采用均方误差 (MSE) 作为损失函数。然后使用 conv2d.zero_grad() 将卷积核的梯度清零再计算网络参数即卷积核的梯度并执行一次梯度下降更新卷积核。最后每迭代 2 次输出一次损失值。
训练之后我们观察得到的权重
conv2d.weight.data.reshape((1, 2))结果为 接近我们刚才设置的卷积核。
特征映射和感受野
特征映射Feature Map是指卷积神经网络在每一层卷积计算时由卷积核对输入数据做卷积得到的输出结果。具体来说卷积操作通过将卷积核在输入数据上滑动每次计算卷积核与输入数据对应位置的内积然后将结果填充到输出特征映射的对应位置最终形成了输出特征映射。
在卷积神经网络中每一层都会通过多个卷积核生成多个特征映射每个特征映射对应着输入数据中不同的特征。例如在图像识别任务中一层特征映射可能对应着图像的边缘特征而另一层特征映射则对应着纹理特征。
感受野receptive field是指卷积神经网络中输出特征图上的每一个像素点在输入图像上映射的区域大小。具体来说对于输出特征图上的一个像素点它对应输入图像上的一个区域这个区域就是它的感受野。
在卷积神经网络中每层特征图的感受野大小会随着网络的层数不断增加而增加。对于一个卷积层它的感受野大小取决于它的卷积核大小、步幅以及填充大小。具体来说假设输入特征图的大小为 Hin×WinH_{in}\times W_{in}Hin×Win输出特征图的大小为 Hout×WoutH_{out}\times W_{out}Hout×Wout卷积核大小为 k×kk\times kk×k步幅为 sss填充大小为 ppp那么输出特征图上的一个像素点在输入特征图上的感受野大小就是 (k−1)×dp(k-1)\times dp(k−1)×dp其中 d⌊(Hin−1)/s⌋1d\lfloor (H_{in}-1)/s\rfloor 1d⌊(Hin−1)/s⌋1 是输入特征图在这个维度上的大小。对于具有多个通道的输入和输出特征图它们的感受野大小是在每个通道上独立计算的。
填充和步幅
假设以下情景 有时在应用了连续的卷积之后我们最终得到的输出远小于输入大小。这是由于卷积核的宽度和高度通常大于所导致的。比如一个240 ∗*∗ 240像素的图像经过10层的5 ∗*∗ 5卷积后将减少到像素。如此一来原始图像的边界丢失了许多有用信息。而填充是解决此问题最有效的方法 有时我们可能希望大幅降低图像的宽度和高度。例如如果我们发现原始的输入分辨率十分冗余。步幅则可以在这类情况下提供帮助。
填充
如上所述在应用多层卷积时我们常常丢失边缘像素。 由于我们通常使用小卷积核因此对于任何单个卷积我们可能只会丢失几个像素。 但随着我们应用许多连续卷积层累积丢失的像素数就多了。 解决这个问题的简单方法即为填充padding在输入图像的边界填充元素通常填充元素是0。在下图中我们将 3 ∗*∗ 3 输入填充到 5 ∗*∗ 5输出也增加到4 ∗*∗ 4 卷积神经网络中卷积核的高度和宽度通常为奇数例如1、3、5或7。 选择奇数的好处是保持空间维度的同时我们可以在顶部和底部填充相同数量的行在左侧和右侧填充相同数量的列。
此外使用奇数的核大小和填充大小也提供了书写上的便利。对于任何二维张量X当满足 1. 卷积核的大小是奇数 2. 所有边的填充行数和列数相同 3. 输出与输入具有相同高度和宽度 则可以得出输出Y[i, j]是通过以输入X[i, j]为中心与卷积核进行互相关计算得到的。
比如在下面的例子中我们创建一个高度和宽度为3的二维卷积层并在所有侧边填充1个像素。给定高度和宽度为8的输入则输出的高度和宽度也是8。
import torch
from torch import nn
# 为了方便起见我们定义了一个计算卷积层的函数。
# 此函数初始化卷积层权重并对输入和输出提高和缩减相应的维数
def comp_conv2d(conv2d, X):# 这里的11表示批量大小和通道数都是1X X.reshape((1, 1) X.shape)Y conv2d(X)# 省略前两个维度批量大小和通道return Y.reshape(Y.shape[2:])# 请注意这里每边都填充了1行或1列因此总共添加了2行或2列
conv2d nn.Conv2d(1, 1, kernel_size3, padding1)
X torch.rand(size(8, 8))
comp_conv2d(conv2d, X).shape当卷积核的高度和宽度不同时我们可以填充不同的高度和宽度使输出和输入具有相同的高度和宽度。在如下示例中我们使用高度为5宽度为3的卷积核高度和宽度两边的填充分别为2和1。
conv2d nn.Conv2d(1, 1, kernel_size(5, 3), padding(2, 1))
comp_conv2d(conv2d, X).shape步幅
在计算互相关时卷积窗口从输入张量的左上角开始向下、向右滑动。 在前面的例子中我们默认每次滑动一个元素。 但是有时候为了高效计算或是缩减采样次数卷积窗口可以跳过中间位置每次滑动多个元素。
在卷积神经网络中步幅stride是指卷积核在对输入进行卷积操作时每次滑动的距离也就是卷积核每次移动的像素数。与填充padding一样步幅是卷积操作的一项超参数通常由设计者根据网络的结构和输入数据的特点来设置。当步幅为1时卷积核每次只移动一个像素此时输出特征图的大小和输入特征图的大小相同。当步幅大于1时输出特征图的大小会减小卷积操作的感受野也会变大即每个输出像素所对应的输入像素的范围会变大。较大的步幅可以减小计算量和内存占用但可能会导致信息丢失。
下面我们将高度和宽度的步幅设置为2从而将输入的高度和宽度减半。
conv2d nn.Conv2d(1, 1, kernel_size3, padding1, stride2)
comp_conv2d(conv2d, X).shape多输入多输出通道
多输入通道
在卷积神经网络中输入通常不是单个二维矩阵而是由多个二维矩阵组成的三维数组例如RGB图像就是由三个二维矩阵组成的三维数组。同样的卷积核也可以具有多个输入通道。在这种情况下我们需要对每个输入通道分别计算互相关然后将它们相加得到输出通道。这是因为不同的输入通道在不同的特征上可能有不同的响应因此我们需要分别对它们进行卷积操作并将它们的输出相加以得到最终的输出通道。
在数学上假设输入具有cic_ici个通道卷积核具有coc_oco个输出通道卷积核的高度为khk_hkh宽度为kwk_wkw输入的高度和宽度分别为XhX_hXh和XwX_wXw输出的高度和宽度分别为YhY_hYh和YwY_wYw。那么我们可以将卷积操作写成以下形式
Yo,x,y∑i1ci∑p1kh∑q1kwWo,i,p,qXi,xp−1,yq−1boY_{o,x,y} \sum_{i1}^{c_i}\sum_{p1}^{k_h}\sum_{q1}^{k_w}W_{o,i,p,q}X_{i,xp-1,yq-1}b_oYo,x,yi1∑cip1∑khq1∑kwWo,i,p,qXi,xp−1,yq−1bo
其中Wo,i,p,qW_{o,i,p,q}Wo,i,p,q表示卷积核在输出通道ooo和输入通道iii之间的参数bob_obo是偏置参数Xi,xp−1,yq−1X_{i,xp-1,yq-1}Xi,xp−1,yq−1表示输入中通道iii和位置(xp−1,yq−1)(xp-1,yq-1)(xp−1,yq−1)处的像素值Yo,x,yY_{o,x,y}Yo,x,y表示输出中通道ooo和位置(x,y)(x,y)(x,y)处的像素值。注意这里我们假设输入和卷积核的高度和宽度相同因此我们可以将它们的索引分别合并为xp−1xp-1xp−1和yq−1yq-1yq−1。
需要注意的是如果输入数据有cic_ici个通道那么每个卷积核就需要有cic_ici个相应的二维权重矩阵。这些矩阵的形状为kh×kwk_h\times k_wkh×kw。我们将它们合并成一个四维张量形状为co×ci×kh×kwc_o\times c_i\times k_h\times k_wco×ci×kh×kw。
示例 代码实现
import torch
from d2l import torch as d2ldef corr2d_multi_in(X, K):# 先遍历“X”和“K”的第0个维度通道维度再把它们加在一起return sum(d2l.corr2d(x, k) for x, k in zip(X, K))X torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])corr2d_multi_in(X, K)结果为tensor([[ 56., 72.],[104., 120.]])
多通道输出
多输出通道是卷积神经网络中的一种常见设计。在单通道卷积中我们使用一个固定的卷积核对输入进行卷积运算得到一个输出通道的结果。而在多输出通道中我们使用多个卷积核对输入进行卷积运算每个卷积核对应一个输出通道最终得到多个通道的结果。这些输出通道之间可以看作是不同的特征提取器每个特征提取器可以识别输入中不同的特征进一步增加了模型的表达能力。在卷积神经网络中多输出通道通常在卷积层后接上池化层或下一层的卷积层以进一步提取高级特征。
def corr2d_multi_in_out(X, K):# 迭代“K”的第0个维度每次都对输入“X”执行互相关运算。# 最后将所有结果都叠加在一起return torch.stack([corr2d_multi_in(X, k) for k in K], 0)举个例子 假设我们有一个3x3的输入图像其中包含2个通道即深度为2。我们想要使用一个形状为2233的卷积核在这个图像上执行卷积操作其中输出通道数为2。这意味着我们将使用2个形状为233的卷积核分别将每个通道的输入转换为2个输出通道。以下是代码示例
import torch
import torch.nn as nn# 输入图像大小为 3x3深度为 2
input_data torch.randn(1, 2, 3, 3)# 定义一个深度为 2输出通道为 2卷积核大小为 3x3 的卷积层
conv nn.Conv2d(in_channels2, out_channels2, kernel_size3)# 打印卷积核的权重矩阵
print(conv.weight)# 对输入数据进行卷积操作
output_data conv(input_data)# 输出数据大小为 1x2x1x1
print(output_data.size())
在这个例子中我们定义了一个深度为2输出通道数为2卷积核大小为3x3的卷积层。输入数据包含一个批量大小为1深度为2大小为3x3的图像。输出数据的大小为1x2x1x1表示有一个批量深度为2大小为1x1的输出图像。卷积核权重矩阵的大小为2x2x3x3其中2x2表示输出通道数和输入通道数3x3表示卷积核的大小。
pytorch实现
import torch
from torch import nn# 输入通道数3, 输出通道数2, 卷积核大小3x3
conv2d nn.Conv2d(3, 2, kernel_size(3, 3), biasFalse)
X torch.rand(1, 3, 5, 5)
Y conv2d(X)
print(Y.shape)
1 ×\times× 1 卷积层
1 ×\times× 1 卷积层看起来并没有什么意义 毕竟卷积的本质是有效提取相邻像素间的相关特征而1 ×\times× 1卷积显然没有此作用。尽管如此1 ×\times× 1 卷积层仍然十分流行经常包含在复杂深层网络的设计中。下面让我们详细地解读一下它的实际作用。
在深度学习中1 x 1 卷积层经常被用来控制网络的通道数即通过增加或者减少通道数来影响网络的复杂度。
在卷积操作中输入数据和卷积核都是多维数组。对于 1×11 \times 11×1 的卷积操作相当于在每个通道上对每个像素的数值进行加权求和得到一个标量输出。因此1×11 \times 11×1 卷积层本质上是一个全连接层但是它又具备了卷积层的一些特性例如平移不变性等。
一个常见的应用场景是使用 1×11 \times 11×1 卷积层来增加或者减少卷积层的通道数。例如我们可以使用 1×11 \times 11×1 卷积层来代替全连接层从而在卷积神经网络中加入全局平均池化Global Average Pooling层或者将高维特征映射投影到低维空间中以达到降低计算复杂度的目的。 汇聚层/池化层
通常当我们处理图像时我们希望逐渐降低隐藏表示的空间分辨率、聚集信息这样随着我们在神经网络中层叠的上升每个神经元对其敏感的感受野输入就越大。
而我们的机器学习任务通常会跟全局图像的问题有关例如“图像是否包含一只猫呢”所以我们最后一层的神经元应该对整个输入的全局敏感。通过逐渐聚合信息生成越来越粗糙的映射最终实现学习全局表示的目标同时将卷积图层的所有优势保留在中间层。
最大汇聚层和平均汇聚层
与卷积层类似汇聚层运算符由一个固定形状的窗口组成该窗口根据其步幅大小在输入的所有区域上滑动为固定形状窗口有时称为汇聚窗口遍历的每个位置计算一个输出。 然而不同于卷积层中的输入与卷积核之间的互相关计算汇聚层不包含参数。 相反池运算是确定性的我们通常计算汇聚窗口中所有元素的最大值或平均值。这些操作分别称为最大汇聚层maximum pooling和平均汇聚层average pooling。
在这两种情况下与互相关运算符一样汇聚窗口从输入张量的左上角开始从左往右、从上往下的在输入张量内滑动。在汇聚窗口到达的每个位置它计算该窗口中输入子张量的最大值或平均值。计算最大值或平均值是取决于使用了最大汇聚层还是平均汇聚层。 代码实现
import torch
from torch import nn
from d2l import torch as d2ldef pool2d(X, pool_size, modemax):p_h, p_w pool_sizeY torch.zeros((X.shape[0] - p_h 1, X.shape[1] - p_w 1))for i in range(Y.shape[0]):for j in range(Y.shape[1]):if mode max:Y[i, j] X[i: i p_h, j: j p_w].max()elif mode avg:Y[i, j] X[i: i p_h, j: j p_w].mean()return Y这段代码定义了一个名为pool2d的函数用于实现二维池化操作。其中输入X是一个二维张量pool_size是一个元组表示池化窗口的大小。函数通过遍历池化窗口内的元素来实现池化操作并返回池化后的结果。
具体地函数首先获取池化窗口的高度和宽度然后根据池化窗口的大小和输入张量的形状计算输出张量的形状。在遍历输入张量时函数根据mode参数指定的池化模式分别计算池化窗口内的最大值或平均值并将其赋值给输出张量的相应位置。最后函数返回输出张量。
pytorch中的使用
这是一个输入图像
X torch.arange(16, dtypetorch.float32).reshape((1, 1, 4, 4))默认情况下深度学习框架中的步幅与汇聚窗口的大小相同。 因此如果我们使用形状为(3, 3)的汇聚窗口那么默认情况下我们得到的步幅形状为(3, 3)。
pool2d nn.MaxPool2d(3)
pool2d(X)结果为tensor([[[[10.]]]])
填充和步幅可以手动设定。
下面为填充1步幅为2的池化层。
pool2d nn.MaxPool2d(3, padding1, stride2)
pool2d(X)结果为 tensor([[[[ 5., 7.],[13., 15.]]]])
当然我们可以设定一个任意大小的矩形汇聚窗口并分别设定填充和步幅的高度和宽度。
pool2d nn.MaxPool2d((2, 3), stride(2, 3), padding(0, 1))
pool2d(X)其中步幅为(2,3)表示在水平方向上移动两个位置在垂直方向上移动三个位置。
多个通道
在处理多通道输入数据时汇聚层在每个输入通道上单独运算而不是像卷积层一样在通道上对输入进行汇总。 这意味着汇聚层的输出通道数与输入通道数相同。 下面我们将在通道维度上连结张量X和X 1以构建具有2个通道的输入。
X torch.cat((X, X 1), 1)如下所示汇聚后输出通道的数量仍然是2。
pool2d nn.MaxPool2d(3, padding1, stride2)
pool2d(X)卷积神经网络(LeNet)
定义
LeNet是深度学习领域的一个经典卷积神经网络模型由Yann LeCun等人于1998年提出被广泛应用于手写数字识别和其他图像识别任务。LeNet的网络结构相对简单包含两个卷积层和三个全连接层是卷积神经网络的基础。LeNet对于现代的图像识别任务来说可能过于简单但其对于深度学习的发展有着重要的贡献是深度学习领域不可或缺的一部分。
总体来看LeNetLeNet-5由两个部分组成
卷积编码器由两个卷积层组成;
全连接层密集块由三个全连接层组成。
输入层接收输入的图像数据一般为灰度图或者RGB彩色图像。
第一个卷积层对输入进行卷积操作使用的卷积核的大小为5x5输出通道数为6不使用填充步幅为1。
第一个池化层对第一个卷积层的输出进行下采样使用的窗口大小为2x2步幅为2池化方式为平均池化或最大池化。
第二个卷积层对第一个池化层的输出进行卷积操作使用的卷积核的大小为5x5输出通道数为16不使用填充步幅为1。
第二个池化层对第二个卷积层的输出进行下采样使用的窗口大小为2x2步幅为2池化方式为平均池化或最大池化。
全连接层1将第二个池化层的输出展开成向量并经过一个全连接层输出大小为120。
全连接层2对第一层全连接层的输出进行处理输出大小为84。
输出层对第二层全连接层的输出进行处理输出大小为分类任务的类别数目。
手写数字识别的架构如图所示。 在此图示中输入图片的尺寸为32*32图示有误 C1层卷积层628×28 该层使用了6个卷积核每个卷积核的大小为5×5这样就得到了6个feature map特征图。 每个卷积核5×5与原始的输入图像32×32进行卷积这样得到的feature map特征图大小为32-51×32-51 28×28 S2层下采样层也称池化层614×14 这一层主要是做池化或者特征映射特征降维池化单元为2×2因此6个特征图的大小经池化后即变为14×14。回顾本文刚开始讲到的池化操作池化单元之间没有重叠在池化区域内进行聚合统计后得到新的特征值因此经2×2池化后每两行两列重新算出一个特征值出来相当于图像大小减半因此卷积后的28×28图像经2×2池化后就变为14×14。 C3层卷积层1610×10 C3层有16个卷积核卷积模板大小为5×5。每个卷积核都与S2的614*14进行互关系运算得到16个通道 与C1层的分析类似C3层的特征图大小为14-51×14-51 10×10 S4下采样层也称池化层165×5 与S2的分析类似池化单元大小为2×2因此该层与C3一样共有16个特征图每个特征图的大小为5×5。 C5层卷积层/全连接层120 该层有120个卷积核每个卷积核的大小仍为5×5因此有120个特征图。由于S4层的大小为5×5而该层的卷积核大小也是5×5因此特征图大小为5-51×5-51 1×1。这样该层就刚好变成了全连接这只是巧合如果原始输入的图像比较大则该层就不是全连接了。 F6层全连接层84 F6层有84个单元之所以选这个数字的原因是来自于输出层的设计对应于一个7×12的比特图如下图所示 OUTPUT层输出层10 Output层也是全连接层共有10个节点分别代表数字0到9。如果第i个节点的值为0则表示网络识别的结果是数字i。
实现
模型
import torch
from torch import nn
from d2l import torch as d2lnet nn.Sequential(nn.Conv2d(1, 6, kernel_size5, padding2), nn.Sigmoid(),nn.AvgPool2d(kernel_size2, stride2),nn.Conv2d(6, 16, kernel_size5), nn.Sigmoid(),nn.AvgPool2d(kernel_size2, stride2),nn.Flatten(),nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),nn.Linear(120, 84), nn.Sigmoid(),nn.Linear(84, 10))或者是
import torch.nn as nn
import torch.nn.functional as Fclass LeNet(nn.Module):def __init__(self):super(LeNet, self).__init__()self.conv1 nn.Conv2d(3, 16, 5) # in_channels3 out_channels16 kernel5self.pool1 nn.MaxPool2d(2, 2)self.conv2 nn.Conv2d(16, 32, 5)self.pool2 nn.MaxPool2d(2, 2)self.fc1 nn.Linear(32*5*5, 120)self.fc2 nn.Linear(120, 84)self.fc3 nn.Linear(84, 10)# 调用上面定义的函数def forward(self, x):x F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28)x self.pool1(x) # output(16, 14, 14)x F.relu(self.conv2(x)) # output(32, 10, 10)x self.pool2(x) # output(32, 5, 5)x x.view(-1, 32*5*5) # output(32*5*5)x F.relu(self.fc1(x)) # output(120)x F.relu(self.fc2(x)) # output(84)x self.fc3(x) # output(10)return x
训练
import torch
import torchvision
import torch.nn as nnfrom model import LeNet
import torch.optim as optim
import torchvision.transforms as transforms
from torch.utils.data import DataLoaderdef main():transform transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])# 50000张训练图片# 第一次使用时要将download设置为True才会自动去下载数据集train_set torchvision.datasets.CIFAR10(root./data, trainTrue,downloadFalse, transformtransform)train_loader DataLoader(train_set, batch_size36,shuffleTrue, num_workers0)# 10000张验证图片# 第一次使用时要将download设置为True才会自动去下载数据集val_set torchvision.datasets.CIFAR10(root./data, trainFalse,downloadFalse, transformtransform)val_loader DataLoader(val_set, batch_size5000,shuffleFalse, num_workers0)val_data_iter iter(val_loader)val_image, val_label val_data_iter.next()# classes (plane, car, bird, cat,# deer, dog, frog, horse, ship, truck)# 定义模型和损失函数net LeNet()loss_function nn.CrossEntropyLoss()# 定义优化器optimizer optim.Adam(net.parameters(),lr0.001)# 对训练集迭代5次epochs 5for epoch in range(epochs):running_loss 0for step,data in enumerate(train_loader,start0): # step从start开始inputs,labels data# 梯度清零optimizer.zero_grad()outputs net(inputs)# 计算损失函数loss loss_function(outputs,labels)# 反向传播并更新参数loss.backward()optimizer.step()running_loss loss.item()if step%500 499:with torch.no_grad():outputs net(val_image)predict_y torch.max(outputs,dim1)[1]accuracy (predict_yval_label).sum().item() / val_label.size(0)print([%d %3d] train_loss: %.3f test_accuracy: %.3f %(epoch1,step1,running_loss/500,accuracy))running_loss 0print(Finished Training)save_path ./Lenet.pthtorch.save(net.state_dict(),save_path)
if __name__ __main__:main()这段代码是一个基于LeNet网络结构进行CIFAR10分类的训练代码主要步骤如下
导入必要的Python包包括PyTorch中的torch、torchvision、torch.nn等模块以及用于数据加载的DataLoader。 定义数据预处理包括将图像转换为Tensor格式、标准化处理等操作。 加载训练集和验证集使用DataLoader将数据分批次读取。 定义LeNet网络结构和损失函数此处使用交叉熵损失函数。 定义Adam优化器。 进行训练共进行5个epoch每个epoch对训练集进行一次完整的遍历每次遍历对数据分批次读取并进行网络的前向计算、反向传播、参数更新等操作。 每500个batch输出一次训练损失和测试精度。 保存训练好的模型参数。
测试
import torch
import torchvision.transforms as transforms
from PIL import Imagefrom model import LeNetdef main():transform transforms.Compose([transforms.Resize((32, 32)), # 对输入的图片尺寸进行调整transforms.ToTensor(), # Convert a PIL Image or numpy.ndarray to tensor. (H x W x C)-(C x H x W)transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])classes (plane, car, bird, cat,deer, dog, frog, horse, ship, truck)net LeNet()net.load_state_dict(torch.load(Lenet.pth)) # 加载模型im Image.open(data/plane.jpg)im transform(im) # [C, H, W]# 增加一个维度batchim torch.unsqueeze(im, dim0) # [N, C, H, W] 增加一个batch维度with torch.no_grad():outputs net(im)predict torch.max(outputs, dim1)[1].numpy()print(classes[int(predict)])if __name__ __main__:main()