文章目录
卷积神经网络(LeNet)学习笔记
介绍
当时,LeNet取得了与支持向量机(support vector machines)性能相媲美的成果,成为监督学习的主流方法。
LeNet被广泛用于自动取款机(ATM)机中,帮助识别处理支票的数字。
总体来看,LeNet(LeNet-5)由两个部分组成:
- 卷积编码器:由两个卷积层组成;(先使用卷积层来学习图片空间信息)
- 全连接层密集块:由三个全连接层组成。(然后使用全连接层来转换到类别空间)
每个卷积块中的基本单元是一个卷积层、一个sigmoid激活函数和平均汇聚层。请注意,虽然ReLU和最大汇聚层更有效,但它们在20世纪90年代还没有出现。每个卷积层使用5×5卷积核和一个sigmoid激活函数。这些层将输入映射到多个二维特征输出,通常同时增加通道的数量。第一卷积层有6个输出通道,而第二个卷积层有16个输出通道。每个2×2池操作(步幅2)通过空间下采样将维数减少4倍。卷积的输出形状由批量大小、通道数、高度、宽度决定。
为了将卷积块的输出传递给稠密块,我们必须在小批量中展平(flatten
)每个样本。换言之,我们将这个四维输入转换成全连接层所期望的二维输入。这里的二维表示的第一个维度索引小批量中的样本,第二个维度给出每个样本的平面向量表示。LeNet的稠密块有三个全连接层,分别有120、84和10个输出。
实现
我们只需要实例化一个Sequential
块并将需要的层连接在一起。
import torch
from torch import nn
from d2l import torch as d2l
class Reshape(nn.Module): # 调整输入数据大小
def forward(self, x):
return x.view(-1, 1, 28, 28) # -1代表自适应,由张量中元素个数和其它维度自动计算得来
net = nn.Sequential(
Reshape(),
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(), # Conv2d(输入通道, 输出通道数, 窗口大小,填充)
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10))
对原始模型做了一点小改动,去掉了最后一层的高斯激活。除此之外,这个网络与最初的LeNet-5一致。
通过在每一层打印输出的形状,我们可以检查模型,以确保其操作与我们期望的一致:
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)
请注意,在整个卷积块中,与上一层相比,每一层特征的高度和宽度都减小了。 第一个卷积层使用2个像素的填充,来补偿5×5卷积核导致的特征减少。 相反,第二个卷积层没有填充,因此高度和宽度都减少了4个像素。 随着层叠的上升,通道的数量从输入时的1个,增加到第一个卷积层之后的6个,再到第二个卷积层之后的16个。 同时,每个汇聚层的高度和宽度都减半。最后,每个全连接层减少维数,最终输出一个维数与结果分类数相匹配的输出。
模型训练
由于完整的数据集位于内存中,因此在模型使用GPU计算数据集之前,我们需要将其复制到显存中。
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
"""使用GPU计算模型在数据集上的精度"""
if isinstance(net, nn.Module): # isinstance判断一个对象的变量类型
# 在使用PyTorch进行训练和测试时一定要记得把实例化的model指定train/eval。
net.eval() # 设置为评估模式,net.eval()开启验证模式,不用计算梯度和更新梯度
if not device:
device = next(iter(net.parameters())).device
# 正确预测的数量,总预测的数量
metric = d2l.Accumulator(2)
with torch.no_grad(): # 强制之后的内容不进行梯度计算。
for X, y in data_iter:
if isinstance(X, list):
# BERT微调所需的(之后将介绍)
X = [x.to(device) for x in X]
else:
X = X.to(device)
y = y.to(device)
metric.add(d2l.accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
为了使用GPU,我们还需要一点小改动。 与 3.6节中定义的train_epoch_ch3
不同,在进行正向和反向传播之前,我们需要将每一小批量数据移动到我们指定的设备(例如GPU)上。
我们使用在中介绍的Xavier随机初始化模型参数。 与全连接层一样,我们使用交叉熵损失函数和小批量随机梯度下降。
#@save
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
"""用GPU训练模型(在第六章定义)"""
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
# 动图显示
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
# 计时器
timer, num_batches = d2l.Timer(), len(train_iter)
for epoch in range(num_epochs):
# 训练损失之和,训练准确率之和,样本数
metric = d2l.Accumulator(3)
net.train() # 设置为训练模式,
for i, (X, y) in enumerate(train_iter):
timer.start()
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
optimizer.step()
with torch.no_grad():
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
timer.stop()
train_l = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(train_l, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(device)}')
训练和评估LeNet-5模型。
lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
【注】pytorch中net.eval() 和net.train()的使用
神经网络模块存在两种模式,train模式(net.train())和eval模式(net.eval())。一般的神经网络中,这两种模式是一样的,只有当模型中存在dropout和batchnorm的时候才有区别。一旦我们用测试集进行结果测试的时候,一定要使用net.eval()把dropout关掉,因为这里我们的目的是测试训练好的网络,而不是在训练网络,没有必要再dropout和再计算BN的方差和均值(BN使用训练的历史值)。
小结
- 卷积神经网络(CNN)是一类使用卷积层的网络。
- 在卷积神经网络中,我们组合使用卷积层、非线性激活函数和汇聚层。
- 为了构造高性能的卷积神经网络,我们通常对卷积层进行排列,逐渐降低其表示的空间分辨率,同时增加通道数。
- 在传统的卷积神经网络中,卷积块编码得到的表征在输出之前需由一个或多个全连接层进行处理。
- LeNet是最早发布的卷积神经网络之一。
colab的搭建使用(需要使用GPU的部分)
卷积层里的多输入多输出通道
多个输入通道
每一个通道都有对应的卷积核
每个通道的输入和对应通道的卷积核做卷积,然后将得到的各个通道上的输出进行叠加(对应位置上的元素相加)得到最终的结果
输出是单通道的,不管输入有多少个通道,输出是他们输出结果的叠加,所以始终是单通道
多个输出通道
为什么要有多个输出通道?因为不管有多少个输入通道只会得到单输出通道的话是不够的
如果对每一个输出通道有一个三维的卷积核,这个卷积核会输出自己的通道(就相当于在三维的基础上又加了一维 i ,这一维表示输出的通道数)
这里输入和输出通道是没有相关性的
多个输入和输出特征
对于一个深度的神经网络来说,下面的一些层的不同通道用来识别一些不同的局部的底层信息(边、纹理),越往上,上层会将局部的纹理组合起来,变成更加高级,较之前更加整体性的模式(特征,如耳朵、胡须等),最上面将所有识别的模式组合起来就形成了所要识别的类别(猫)
1*1的卷积层
-
卷积核的高和宽都等于1,意味着它不会识别空间信息,因为他每次只看一个空间像素所以不会去识别通道中的空间信息
-
输出的值等价于将对应的输入位置上的不同通道上的值做加权和
-
1*1
卷积核的作用就是去融合不同通道的信息可以认为是不做空间的匹配,只是在输入层直接做输入通道和输出通道的融合,等价于将整个输入拉成一个向量,通道数等于feature的数量,卷积核相当于一个co*ci
的全部连接层 -
1*1的卷积层就等价于一个全连接层,不做任何的控制信息,因为全连接层不考虑空间信息它只考虑在特征维度(也就是输入通道维数)的融合
-
它是一个特殊的卷积层
-
1*1卷积核的作用就是去融合不同通道的信息可以认为是不做空间的匹配,只是在输入层直接做输入通道和输出通道的融合,等价于将整个输入拉成一个向量,通道数等于feature的数量,卷积核相当于一个co*ci的全部连接层
-
1*1的卷积层就等价于一个全连接层,不做任何的控制信息,因为全连接层不考虑空间信息它只考虑在特征维度(也就是输入通道维数)的融合
-
它是一个特殊的卷积层