经典的卷积神经网络
在本次笔记中主要介绍一些经典的卷积神经网络模型,主要包含以下:
- LeNet:最早发布的卷积神经网络之一,目的是识别图像中的手写数字;
- AlexNet: 是第一个在大规模视觉竞赛中击败传统计算机视觉模型的大型神经网络;
- 使用重复块的网络(VGG):利用许多重复的神经网络块;
- 网络中的网络(NiN):重复使用由卷积层和1×1卷积层(用来代替全连接层)来构建深层网络;
- 含并行连结的网络(GoogLeNet):使用并行连结的网络,通过不同窗口大小的卷积层和最大汇聚层来并行抽取信息;
- 残差网络(ResNet):它通过残差块构建跨层的数据通道,是计算机视觉中最流行的体系架构;
- 稠密连接网络(DenseNet):它的计算成本很高,但能带来了更好的效果。
- 轻量化CNN(mobileNet):旨在优化移动和嵌入式设备上的性能
LeNet
介绍
最早发布的卷积神经网络之一,因其在计算机视觉任务中的高效性能而受到广泛关注。 该模型是由AT&T贝尔实验室的研究员Yann LeCun在1989年提出的(并以其命名),目的是识别图像中的手写数字(应用与美国邮政服务)。
LeNet(LeNet-5)由两个部分组成:
- 卷积编码器:由两个卷积层组成;
- 全连接层密集块:由三个全连接层组成。
在卷积层块中,每个卷积层都使用5 × 5 的窗口,并在输出上使用sigmoid激活函数。第一个卷积层输出通道数为6,第二个卷积层输出通道数则增加到16。
在图中网络结构中汇聚层就是应用最大池化完成的。其中,最大池化的窗口大小为2 × 2,且步幅为2。由于池化窗口与步幅形状相同,池化窗口在输入上每次滑动所覆盖的区域互不重叠。
该网络使用数据集:MNIST数据集
包含50000个训练数据、10000个测试数据;样本均为灰度图像:28*28;输出:10类(0-9)
总结
- LeNet是早期成功的神经网络
- 先使用卷积层来学习图片空间信息
- 然后使用全连接层来转换到类别空间
代码实现
# lenet模型的实现
import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
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)
# 模型训练,上面已经实现了LeNet,接下来让我们看看LeNet在Fashion-MNIST数据集上的表现
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):
if isinstance(net, nn.Module):
net.eval() # 设置为评估模式
# 如果 device 参数未提供,则通过 next(iter(net.parameters())).device 获取模型第一个参数所在的设备,将其作为后续计算的设备。
if not device:
device = next(iter(net.parameters())).device
# 正常预测的数量,总预测的数量
metric = d2l.Accumulator(2)
# 在评估阶段,不需要计算梯度
with torch.no_grad():
for X, y in data_iter:
# 如果输入数据 X 是列表类型(通常在 BERT 微调等场景中会遇到),将列表中的每个张量迁移到指定设备上。
if isinstance(X, list):
# BERT 微调所需的(之后讲)
X = [x.to(device) for x in X]
else:
# 否则直接将输入数据 X 迁移到指定设备上
X = X.to(device)
# 标签 y 迁移到指定设备上
y = y.to(device)
# 使用 d2l 库中的 accuracy 函数计算当前批次的预测准确率,即正确预测的样本数量。
metric.add(d2l.accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
# 训练函数
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
"""用GPU训练模型"""
# 1. 初始化模型的权重,使用 Xavier 均匀分布初始化线性层和卷积层的权重。
def init_weight(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weight)
print('training on', device)
# 2. 将模型移动到指定的设备(通常是 GPU)上进行训练。
net.to(device)
# 3.定义优化器(随机梯度下降 SGD)和损失函数(交叉熵损失)。
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
# 4. 创建一个动画绘制器 Animator 用于实时可视化训练过程中的损失和准确率变化。
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer, num_batches = d2l.Timer(), len(train_iter)
# 5. 开始多个轮次的训练,在每个轮次中遍历训练数据集,计算损失、反向传播并更新模型参数。
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))
# 6.每个轮次结束后,评估模型在测试数据集上的准确率。
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
# 7.最后输出训练结束后的训练损失、训练准确率、测试准确率以及训练速度。
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())
得到结果:
AlexNet
先说点其他的
-
背景
当时关于特征提取有两种思想:- 观察图像特征的提取方法:2012年 前,图像特征都是机械地计算出来的。
- 认为特征本身应该被学习
-
AlexNet 诞生的关键因素
- 数据层面:深度模型需大量有标签数据才能优于传统方法。早期受存储和预算限制,研究多基于小数据集。2009 年 ImageNet 数据集发布并举办挑战赛,提供百万样本、千种类别,推动了研究发展。
- 硬件层面:深度学习计算需求高,早期优化凸目标的简单算法因计算资源限制受青睐。GPU 最初用于图形处理,其运算与卷积层计算相似,英伟达和 ATI 将其优化用于通用计算。GPU 由众多小型处理单元组成,虽单个核心性能弱,但数量庞大,在浮点性能、功耗和内存带宽方面优于 CPU。2012 年,Alex Krizhevsky 和 Ilya Sutskever 利用 GPU 实现快速卷积运算,推动了深度学习发展。
介绍
-
网络结构
使用了8层卷积神经网络,前5层是卷积层,剩下的3层是全连接层,具体如下所示。
(由当时GPU内存的限制引起的,作者使用两块GPU进行计算,因此分为了上下两部分) -
与LeNet结构的差异:
-
具体细节:
- 卷积层C1:使用96个核对224×224×3的输入图像进行滤波,卷积核大小为11×11×3,步长为4。将一对55×55×48的特征图分别放入ReLU激活函数,生成激活图。激活后的图像进行最大池化,size为3×3,stride为2,池化后的特征图size为27×27×48(一对)。池化后进行LRN处理。
- 卷积层C2:使用卷积层C1的输出(响应归一化和池化)作为输入,并使用256个卷积核进行滤波,核大小为5 × 5 × 48。
- 卷积层C3:有384个核,核大小为3 × 3 × 256,与卷积层C2的输出(归一化的,池化的)相连。
- 卷积层C4:有384个核,核大小为3 × 3 × 192。
- 卷积层C5:有256个核,核大小为3 × 3 × 192。卷积层C5与C3、C4层相比多了个池化,池化核size同样为3×3,stride为2。
- 全连接F6:此层的全连接实际上是通过卷积进行的,输入6×6×256,4096个6×6×256的卷积核,扩充边缘padding = 0, 步长stride = 1, 因此其FeatureMap大小为(6-6+0×2+1)/1 = 1,即1×1×4096;
-
创新特点:
-
更深的神经网络结构
- AlexNet 是首个真正意义上的深度卷积神经网络,它的深度达到了当时先前神经网络的数倍。通过增加网络深度,AlexNet 能够更好地学习数据集的特征,从而提高了图像分类的精度。
-
ReLU激活函数的使用
- AlexNet 首次使用了修正线性单元ReLU这一非线性激活函数。相比于传统的 sigmoid 和 tanh 函数,ReLU 能够在保持计算速度的同时,有效地解决了梯度消失问题,从而使得训练更加高效。
-
ReLU激活函数的使用
- 对于每个特征图上的每个位置,计算该位置周围的像素的平方和,然后将当前位置的像素值除以这个和。
- LRN是在卷积层和池化层之间添加的一种归一化操作。在卷积层中,每个卷积核都对应一个特征图(feature map),LRN就是对这些特征图进行归一化。具体来说,对于每个特征图上的每个位置,计算该位置周围的像素的平方和,然后将当前位置的像素值除以这个和。
- 对于每个特征图上的每个位置,计算该位置周围的像素的平方和,然后将当前位置的像素值除以这个和。
-
数据增强和Dropout
- 数据增强:通过随机裁剪、水平翻转图像以及对 RGB 通道强度进行 PCA 变换来扩大数据集,减少过拟合,降低错误率。
- Dropout:在全连接层使用 Dropout 技术,以 0.5 概率随机失活神经元,减少神经元间复杂的协同适应,防止过拟合,测试时将神经元输出乘以 0.5。
-
大规模分布式训练
- AlexNet在使用GPU进行训练时,可将卷积层和全连接层分别放到不同的GPU上进行并行计算,从而大大加快了训练速度。像这种大规模 GPU 集群进行分布式训练的方法在后来的深度学习中也得到了广泛的应用。
-
-
分布式GPU的使用方式
-
将网络分布在两个 GTX 580 GPU 上进行训练。两个 GPU 之间能够直接读写彼此内存,无需通过主机内存,这为跨 GPU 并行化提供了便利。
在分布式训练时,各层在两个 GPU 上有不同的分工,如下图:
-
对模型压缩的启发:
-
模型分割与并行化
-
减少冗余与参数共享
如:C4、C5卷积层仅与同GPU的特征图相连。(参数共享、剪枝) -
优化训练策略(调整学习率和优化器参数)
-
利用硬件特性(使用低精度计算、设计轻量级网络结构)
-
-
总结
-
AlexNet的架构与LeNet相似,但使用了更多的卷积层和更多的参数来拟合大规模的ImageNet数据集。
-
现在,AlexN