目录
一:回顾
二:卷积神经网络
举个例子
三: 卷积
互相关运算
卷积层
学习卷积核
四:填充
五:步幅
六: 多输入多输出通道
七:1×1 卷积层
为什么需要1*1的卷积核?
八: 最大汇聚层和平均汇聚层
九:Lenet介绍
十:总结
所有项目代码+UI界面
上一篇介绍了深度学习中的层和块以及参数管理,深度学习模型由层和块组成。层是神经网络的基本组成部分,它接受输入并将其转换为输出。块由多层组成,通常用于表示更复杂的操作,例如卷积神经网络(cnn)和循环神经网络(rnn)。块可以堆叠在一起,形成整个模型。
我们之前讨论的多层感知机十分适合处理表格数据,其中行对应样本,列对应特征。 对于表格数据,我们寻找的模式可能涉及特征之间的交互,但是我们不能预先假设任何与特征交互相关的先验结构。 此时,多层感知机可能是最好的选择,然而对于高维感知数据,这种缺少结构的网络可能会变得不实用。
例如,在之前猫狗分类的例子中:假设我们有一个足够充分的照片数据集,数据集中是拥有标注的照片,每张照片具有百万级像素,这意味着网络的每次输入都有一百万个维度。 即使将隐藏层维度降低到1000,这个全连接层也将有106×103=109个参数。 想要训练这个模型将不可实现,因为需要有大量的GPU、分布式优化训练的经验和超乎常人的耐心。百万级像素指的是一张图片的像素点数量为100万个。这通常表示图片具有很高的分辨率和细节。例如,一张分辨率为1000x1000的图片就有100万个像素点。
我们可以从儿童游戏”沃尔多在哪里”中得到灵感: 在这个游戏中包含了许多充斥着活动的混乱场景,而沃尔多通常潜伏在一些不太可能的位置,读者的目标就是找出他。 尽管沃尔多的装扮很有特点,但是在眼花缭乱的场景中找到他也如大海捞针。 然而沃尔多的样子并不取决于他潜藏的地方,因此我们可以使用一个“沃尔多检测器”扫描图像。 该检测器将图像分割成多个区域,并为每个区域包含沃尔多的可能性打分。 卷积神经网络正是将空间不变性(spatial invariance)的这一概念系统化,从而基于这个模型使用较少的参数来学习有用的表示。
现在,我们将上述想法总结一下,从而帮助我们设计适合于计算机视觉的神经网络架构。
平移不变性(translation invariance):不管检测对象出现在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有相似的反应,即为“平移不变性”。(具体来说,卷积层的卷积操作实现了平移不变性,因为对于同一种特征,它可以在图像的不同位置上被识别出来。而池化层的最大池化操作可以保留图像的最重要的特征,同时将图像缩小,这有助于提高模型的鲁棒性和泛化能力)。
局部性(locality):原则是卷积神经网络中的一个重要原则。在传统的多层感知机神经网络中,每个神经元都和前一层的所有神经元相连,这种全连接的结构在输入图像较大时会导致参数数量巨大,计算量也会大大增加。卷积神经网络通过采用卷积层的方式,让每个神经元仅与输入图像的一个局部区域相连,这种方式能够大幅减少网络参数数量,提高计算效率。 在卷积神经网络中,每个卷积层会学习一些卷积核,每个卷积核都是一组权重,它通过在输入图像的不同位置上滑动,计算出一系列的特征图。这样,每个卷积核仅会关注输入图像的一个局部区域,这种局部特征的提取符合了“局部性”原则。在后续的池化层中,还可以进一步缩小特征图的尺寸,并提高特征的鲁棒性和平移不变性。
让我们看看这些原则是如何转化为数学表示的。
而卷积神经网络是包含卷积层的一类特殊的神经网络。 在深度学习研究社区中,w被称为卷积核(convolution kernel)或者滤波器(filter),亦或简单地称之为该卷积层的权重,通常该权重是可学习的参数。 当图像处理的局部区域很小时,卷积神经网络与多层感知机的训练差异可能是巨大的:以前,多层感知机可能需要数十亿个参数来表示网络中的一层,而现在卷积神经网络通常只需要几百个参数,而且不需要改变输入或隐藏表示的维数。 参数大幅减少的代价是,我们的特征现在是平移不变的,并且当确定每个隐藏活性值时,每一层只包含局部的信息。 以上所有的权重学习都将依赖于归纳偏置。当这种偏置与现实相符时,我们就能得到样本有效的模型,并且这些模型能很好地泛化到未知数据中。 但如果这偏置与现实不符时,比如当图像不满足平移不变时,我们的模型可能难以拟合我们的训练数据。
如果一个人的面部表情是因为他/她正在看电影而引起的,那么这个面部表情可能会受到画面中不同部分的影响。因此,当他/她在电影屏幕的左边看电影时,面部表情可能会与在电影屏幕的右边看电影时有所不同。但是,如果这个人正在面对着相机拍照,那么他/她的面部表情不应该随着位置的改变而改变。
相机的位置相对于被拍摄对象是不变的,因此被拍摄对象的面部表情也不应该因为位置的改变而改变。
格来说,卷积层是个错误的叫法,因为它所表达的运算其实是互相关运算(cross-correlation),而不是卷积运算。在卷积层中,输入张量和核张量通过互相关运算产生输出张量。
首先,我们暂时忽略通道(第三维)这一情况,看看如何处理二维图像数据和隐藏表示。
一般很少用偶数个卷积核的
图1
二维互相关运算。阴影部分是第一个输出元素,以及用于计算输出的输入张量元素和核张量元素:0×0+1×1+3×2+4×3=19.
卷积是对位置非常敏感的,你的第i行第j列的元素就是对应输入第i行第j列附近那一块东西
卷积层对输入和卷积核权重进行互相关运算,并在添加标量偏置之后产生输出。 所以,卷积层中的两个被训练的参数是卷积核权重和标量偏置。 就像我们之前随机初始化全连接层一样,在训练基于卷积层的模型时,我们也随机初始化卷积核权重。
基于上面定义的corr2d
函数实现二维卷积层。在__init__
构造函数中,将weight
和bias
声明为两个模型参数。前向传播函数调用corr2d
函数并添加偏置。
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
高度和宽度分别为ℎ和w的卷积核可以被称为ℎ×w卷积或ℎ×w卷积核。 我们也将带有ℎ×w卷积核的卷积层称为ℎ×w卷积层。
现在让我们看看是否可以通过仅查看“输入-输出”对来学习由X
生成Y
的卷积核。 我们先构造一个卷积层,并将其卷积核初始化为随机张量。接下来,在每次迭代中,我们比较Y
与卷积层输出的平方误差,然后计算梯度来更新卷积核。为了简单起见,我们在此使用内置的二维卷积层,并忽略偏置。
# 构造一个二维卷积层,它具有1个输出通道和形状为(1,2)的卷积核
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)
# 这个二维卷积层使用四维输入和输出格式(批量大小、通道、高度、宽度),
# 其中批量大小和通道数都为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) ** 2
conv2d.zero_grad()
l.sum().backward()
# 迭代卷积核
conv2d.weight.data[:] -= lr * conv2d.weight.grad
if (i + 1) % 2 == 0:
print(f'epoch {i+1}, loss {l.sum():.3f}')
在10次迭代之后,误差已经降到足够低。现在我们来看看我们所学的卷积核的权重张量。
conv2d.weight.data.reshape((1, 2))
输出:
tensor([[ 1.0486, -0.9313]])
图1中输出的卷积层有时被称为特征映射(feature map)
为它可以被视为一个输入映射到下一层的空间维度的转换器。 在卷积神经网络中,对于某一层的任意元素�,其感受野(receptive field)是指在前向传播期间可能影响�计算的所有元素(来自所有先前层)。
比如,一个240×240像素的图像,经过10层5×5的卷积后,将减少到200×200像素。如此一来,原始图像的边界丢失了许多有用信息。而填充是解决此问题最有效的方法; 有时,我们可能希望大幅降低图像的宽度和高度。例如,如果我们发现原始的输入分辨率十分冗余。步幅则可以在这类情况下提供帮助。
如上所述,在应用多层卷积时,我们常常丢失边缘像素。 由于我们通常使用小卷积核,因此对于任何单个卷积,我们可能只会丢失几个像素。 但随着我们应用许多连续卷积层,累积丢失的像素数就多了。 解决这个问题的简单方法即为填充(padding):在输入图像的边界填充元素(通常填充元素是0)。 例如,在图2中,我们将3×3输入填充到5×5,那么它的输出就增加为4×4。阴影部分是第一个输出元素以及用于输出计算的输入和核张量元素: 0×0+0×1+0×2+0×3=0。padding=1是将图像的上下左右都填充一行或者一列
图2
在计算互相关时,卷积窗口从输入张量的左上角开始,向下、向右滑动。 在前面的例子中,我们默认每次滑动一个元素。 但是,有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。
我们将每次滑动元素的数量称为步幅(stride)。到目前为止,我们只使用过高度或宽度为1的步幅,那么如何使用较大的步幅呢? 图3是垂直步幅为3,水平步幅为2的二维互相关运算。 着色部分是输出元素以及用于输出计算的输入和内核张量元素:0×0+0×1+1×2+2×3=8、0×0+6×1+0×2+0×3=6。
通常,当垂直步幅为sℎ、水平步幅为sw时,输出形状为
卷积的计算公式:
例如彩色图像具有标准的RGB通道来代表红、绿和蓝。 但是到目前为止,我们仅展示了单个输入和单个输出通道的简化例子。 这使得我们可以将输入、卷积核和输出看作二维张量。
当我们添加通道时,我们的输入和隐藏的表示都变成了三维张量。例如,每个RGB输入图像具有3×ℎ×w的形状。我们将这个大小为3的轴称为通道(channel)维度。本节将更深入地研究具有多输入和多输出通道的卷积核。
输出的具体通道是由卷积核的个数决定的
合理的情况:当一个原始图片的像素进来,下面的一些层都是识别一些局部的很底层的纹理,然后越往上层就会把你的纹理组合起来,变成比如说猫的胡须的纹理。耳朵的纹理,在往上层把纹理组合起来,最后站在上层的卷积层例如在这个通道识别到是一个猫头,这个通道识别到的是猫脚等等,在最上面就是所有组合起来就是一只猫。这就是为什么需要多个输入和多个输出通道的原因
1×1卷积,即kℎ=kw=1,看起来似乎没有多大意义。 毕竟,卷积的本质是有效提取相邻像素间的相关特征,而1×1卷积显然没有此作用。 尽管如此,1×1仍然十分流行,经常包含在复杂深层网络的设计中。下面,让我们详细地解读一下它的实际作用。
减小计算的开销,当不好更改我们卷积核的个数和形状的时候,那么如果对输入通道的个数可以做约简的话,也能实现后续对卷积层的参数的约简,
输出的通道数是由卷积的个数决定的,并且1*1的效果比3*3的好,参数量变少了
加上P,整个P是指偏置
现在我们的目的是:让参数量变得尽量不要这么大
相当于64*64*192通过一个个数为1全连接层,如何输出1个维度的同形状的。
图片里的右边部分,,显示用32*5*5的卷积运算,得到32*24*24的图片,其参数量是1.2亿!,而用选用1*1再用5*5得出的结果是1200万。
在这两种情况下,与互相关运算符一样,汇聚窗口从输入张量的左上角开始,从左往右、从上往下的在输入张量内滑动。在汇聚窗口到达的每个位置,它计算该窗口中输入子张量的最大值或平均值。计算最大值或平均值是取决于使用了最大汇聚层还是平均汇聚层。
最大汇聚层
X = torch.arange(16, dtype=torch.float32).reshape((1, 1, 4, 4))
X
默认情况下,深度学习框架中的步幅与汇聚窗口的大小相同。 因此,如果我们使用形状为(3, 3)
的汇聚窗口,那么默认情况下,我们得到的步幅形状为(3, 3)
。填充和步幅可以手动设定。
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
pool2d(X)
pool2d = nn.MaxPool2d((2, 3), stride=(2, 3), padding=(0, 1))
pool2d(X)
池化窗口大小为2x3,步幅为2x3,意味着池化窗口在水平方向上每次移动2个像素,在垂直方向上每次移动3个像素。填充为0x1表示在水平方向上不填充,垂直方向上在上方和下方各填充1个像素。
LeNet,它是最早发布的卷积神经网络之一,因其在计算机视觉任务中的高效性能而受到广泛关注。 这个模型是由AT&T贝尔实验室的研究员Yann LeCun在1989年提出的(并以其命名),
当时,LeNet取得了与支持向量机(support vector machines)性能相媲美的成果,成为监督学习的主流方法。 LeNet被广泛用于自动取款机(ATM)机中,帮助识别处理支票的数字。 时至今日,一些自动取款机仍在运行Yann LeCun和他的同事Leon Bottou在上世纪90年代写的代码呢!
总体来看,LeNet(LeNet-5)由两个部分组成:
卷积编码器:由两个卷积层组成;
全连接层密集块:由三个全连接层组成。
LeNet中的数据流。输入是手写数字,输出为10种可能结果的概率。
每个卷积块中的基本单元是一个卷积层、一个sigmoid激活函数和平均汇聚层。请注意,虽然ReLU和最大汇聚层更有效,但它们在20世纪90年代还没有出现。每个卷积层使用5×5卷积核和一个sigmoid激活函数。
下面,我们将一个大小为28×28的单通道(黑白)图像通过LeNet。通过在每一层打印输出的形状,我们可以检查模型,以确保其操作与我们期望的上图一致。
在 LeNet 中,从 16*5*5 的卷积层输出到全连接层时,将 16*5*5 的三维特征图展开成一维向量,即 16*5*5 = 400 的向量,作为全连接层的输入。因此全连接层的输入大小为 400,输出大小为 120。
X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape: \t',X.shape)
Lenet的具体动态演示可以参考这个TensorSpace Playground - LeNet每一步都非常的详细,界面也很友好。TensorSpace Playground - LeNet
可以将全连接层看作是多层感知机的隐藏层。全连接层的每个节点都连接着上一层的所有节点,因此它们可以捕捉到更高级别的特征,进一步提高分类的准确性。与其他层不同的是,全连接层的输出是一个固定长度的向量,可以被用作分类器的输入。
在全连接层中每个节点都连接着上一层的所有节点,这样会捕捉到上一层所有节点之间的关系。这种方式的优点是可以学习到更高级别的特征,因为每个节点可以通过权重的调整来聚合上一层节点的信息,进而提取更加抽象和高级别的特征。
最后,LeNet中的全连接层并不是使用softmax函数,而是作为一个简单的多层感知机来处理特征向量,最后的输出层才使用了softmax函数进行分类。
特别注意:在训练过程中,模型会学习到每个数字类别对应的权重,而不是针对某个特定数字的权重。当需要识别数字1时,模型会根据输入图片的特征计算出每个类别的概率,然后选取概率最高的节点,即为数字1。比如识别的是1,最后全连接层84映射到10个类别的时候,这个全连接层是1的特征,将1的特征映射到不同的每个数字类别对应的权重中去,得到的肯定是1的权重最大,也就是概率最大,其他不应该比1的大,这样就识别出来1了,
图像的平移不变性使我们以相同的方式处理局部图像,而不在乎它的位置。
局部性意味着计算相应的隐藏表示只需一小部分局部图像像素。
在图像处理中,卷积层通常比全连接层需要更少的参数,但依旧获得高效用的模型。
卷积神经网络(CNN)是一类特殊的神经网络,它可以包含多个卷积层。
多个输入和输出通道使模型在每个空间位置可以获取图像的多方面特征。
卷积神经网络(CNN)是一类使用卷积层的网络。
在卷积神经网络中,我们组合使用卷积层、非线性激活函数和汇聚层。
为了构造高性能的卷积神经网络,我们通常对卷积层进行排列,逐渐降低其表示的空间分辨率,同时增加通道数。
在传统的卷积神经网络中,卷积块编码得到的表征在输出之前需由一个或多个全连接层进行处理。
LeNet是最早发布的卷积神经网络之一。
视频,笔记和代码,以及注释都已经上传网盘,放在主页置顶文章