在LeNet提出后的将近20年里,神经⽹络⼀度被其他机器学习⽅法超越,如支持向量机。虽然LeNet可以在早期的⼩数据集上取得好的成绩,但是在更大的真实数据集上的表现并不尽如人意。⼀⽅面,神经⽹络计算复杂。虽然20世纪90年代也有过⼀一些针对神经网络的加速硬件,但并没有像之后GPU那样⼤量普及。因此,训练⼀个多通道、多层和有大量参数的卷积神经网络在当年很难完成。另⼀方面,当年研究者还没有⼤量深⼊研究参数初始化和⾮凸优化算法等诸多领域,导致复杂的神经网络的训练通常较困难。
我们在上一节看到,神经网络可以直接基于图像的原始像素进行分类。这种称为端到端(end-to- end)的⽅法节省了很多中间步骤。然而,在很长⼀段时间里更流行的是研究者通过勤劳与智慧所设计并⽣成的⼿工特征(特征工程)。这类图像分类研究的主要流程是:
1)获取图像数据集
2)使用已有的特征提取函数生成图像的特征
3)使用机器学习模型对图像的特征分类
当时认为的机器学习部分仅限最后这一步。如果那时候跟机器学习研究者交谈,他们会认为机器学习既重要又优美。优雅的定理证明了许多分类器的性质。机器学习领域生机勃勃、严谨⽽且极其有用。然而,如果跟计算机视觉研究者交谈,则是另外⼀一幅景象。他们会告诉你图像识别⾥“不可告⼈”的现实是:计算机视觉流程中真正重要的是数据和特征。也就是说,使用较⼲净的数据集和较有效的特征甚⾄⽐机器学习模型的选择对图像分类结果的影响更大。
目录
1. 学习特征表示
既然特征如此重要,它该如何表示呢?
我们已经提到,在相当⻓的时间里,特征都是基于各式各样⼿⼯设计的函数从数据中提取的。事实上, 不少研究者通过提出新的特征提取函数不断改进图像分类结果。这⼀度为计算机视觉的发展做出了重要贡献。
然而,另⼀些研究者则持异议。他们认为特征本身也应该由学习得来。他们还相信,为了表征⾜够复杂的输入,特征本身应该分级表示。持这一想法的研究者相信,多层神经⽹络可能可以学得数据的多级表征,并逐级表示越来越抽象的概念或模式。以图像分类为例,并回忆(二维卷积层)中物体边缘检测的例子。在多层神经⽹络中,图像的第一级的表示可以是在特定的位置和⻆度是否出现边缘;而第⼆级的表示说不定能够将这些边缘组合出有趣的模式,如花纹;在第三级的表示中,也许上一级的花纹能进⼀步汇合成对应物体特定部位的模式。这样逐级表示下去,最终,模型能够较容易根据最后一级的表示完成分类任务。需要强调的是,输⼊的逐级表示由多层模型中的参数决定,⽽这些参数都是学出来的。
尽管⼀直有一群执着的研究者不断钻研,试图学习视觉数据的逐级表征,然⽽很长一段时间⾥这些野⼼都未能实现。这其中有诸多因素值得我们⼀一分析。
- 缺失要素一:数据
包含许多特征的深度模型需要⼤量的有标签数据才能表现得⽐其他经典方法更好。限于早期计算机有限的存储和90年代有限的研究预算,⼤部分研究只基于小的公开数据集。例如,不少研究论文基于加州大学欧⽂分校(UCI)提供的若⼲个公开数据集,其中许多数据集只有几百⾄⼏千张图像。这⼀状况在 2010年前后兴起的大数据浪潮中得到改善。特别是,2009年诞生的ImageNet数据集包含了1,000⼤类物体,每类有多达数千张不同的图像。这⼀规模是当时其他公开数据集无法与之相提并论的。 ImageNet数据集同时推动计算机视觉和机器学习研究进入新的阶段,使此前的传统方法不再有优势。
- 缺失要素二:硬件
深度学习对计算资源要求很高。早期的硬件计算能力有限,这使训练较复杂的神经网络变得很困难。然而,通⽤GPU的到来改变了这⼀格局。很久以来,GPU都是为图像处理和计算机游戏设计的,尤其是针对⼤吞吐量的矩阵和向量乘法从⽽服务于基本的图形变换。值得庆幸的是,这其中的数学表达与深度⽹络中的卷积层的表达类似。通⽤GPU这个概念在2001年开始兴起,涌现出诸如OpenCL和CUDA之类的编程框架。这使得GPU也在2010年前后开始被机器学习社区使⽤。
2. AlexNet
2012年,AlexNet横空出世。这个模型的名字来源于论⽂第一作者的姓名Alex Krizhevsky 。 AlexNet使⽤了8层卷积神经网络,并以很大的优势赢得了ImageNet 2012图像识别挑战赛。它⾸次证明了学习到的特征可以超越⼿⼯设计的特征,从⽽⼀举打破计算机视觉研究的前状。
AlexNet与LeNet的设计理念⾮常相似,但也有显著的区别。
第⼀,与相对较小的LeNet相比,AlexNet包含8层变换,其中有5层卷积和2层全连接隐藏层,以及1个全连接输出层。下⾯我们来详细描述这些层的设计。
AlexNet第一层中的卷积窗口形状是11*11.因为ImageNet中绝大多数图像的⾼和宽均比MNIST图像的⾼和宽大10倍以上,ImageNet图像的物体占⽤更多的像素,所以需要更大的卷积窗口来捕获物体。 第二层中的卷积窗⼝形状减⼩到5*5,之后全用3*3.此外,第⼀、第二和第五个卷积层之后都使用了窗⼝形状为3*3、步幅为2的最大池化层。⽽且,AlexNet使⽤的卷积通道数也⼤大于LeNet中的卷积通道数数十倍。
紧接着最后一个卷积层的是两个输出个数为4096的全连接层。这两个巨⼤的全连接层带来将近1GB的模型参数。由于早期显存的限制,最早的AlexNet使⽤双数据流的设计使⼀个GPU只需要处理⼀半模型。幸运的是,显存在过去⼏年得到了⻓足的发展,因此通常我们不再需要这样的特别设计了。
第⼆,AlexNet将sigmoid激活函数改成了更加简单的ReLU激活函数。⼀方面,ReLU激活函数的计算更简单,例如它并没有sigmoid激活函数中的求幂运算。另一方面,ReLU激活函数在不同的参数初始化⽅法下使模型更容易训练。这是由于当sigmoid激活函数输出极接近0或1时,这些区域的梯度⼏乎为0, 从而造成反向传播⽆法继续更新部分模型参数;而ReLU激活函数在正区间的梯度恒为1。因此,若模型参数初始化不当,sigmoid函数可能在正区间得到几乎为0的梯度,从⽽令模型无法得到有效训练。
第三,AlexNet通过丢弃法(Dropout)来控制全连接层的模型复杂度。⽽LeNet并没有使⽤丢弃法。
第四,AlexNet引⼊了⼤量的图像增广,如翻转、裁剪和颜⾊变化,从而进⼀步扩大数据集来缓解过拟合。我们将在后面详细介绍图像增广。
import time
import torch
from torch import nn, optim
import torchvision
import sys
sys.path.append(".")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(torch.__version__)
print(torchvision.__version__)
print(device)
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
#卷积部分
self.conv = nn.Sequential(
nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2),# in_channels, out_channels, kernel_size, stride,padding
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Linear(256*5*5, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10),
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1)) #(batch_size,-1)
return output
打印看看网络结构。
net = AlexNet()
print(net)
3. 读取数据
虽然论⽂中AlexNet使⽤ImageNet数据集,但因为ImageNet数据集训练时间较长,我们仍⽤前面的 Fashion-MNIST数据集来演示AlexNet。读取数据的时候我们额外做了一步将图像高和宽扩大到AlexNet 使⽤的图像⾼和宽224。这个可以通过torchvision.transfroms.Resize实例来实现。也就是说, 我们在ToTensor实例前使用Resize实例,然后使用Compose实例来将这两个变换串联以⽅便调用。
# 可以把本函数保存在d2lzh_pytorch包中方便以后使用
def load_data_fashion_mnist(batch_size, resize=None, root='./data/Datasets/FashionMNIST'):
"""Download the fashion mnist dataset and then load into memory."""
trans = []
if resize:
trans.append(torchvision.transforms.Resize(size=resize))
trans.append(torchvision.transforms.ToTensor())
transform = torchvision.transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)
mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=4)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=4)
return train_iter, test_iter
batch_size = 128
# 如出现“out of memory”的报错信息,可减小batch_size或resize
train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=224)
4. 训练
这时候我们可以开始训练AlexNet了。相对于LeNet,由于图⽚尺⼨变⼤了⽽且模型变大了,所以需要更大的显存,也需要更更长的训练时间了。
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
5. 小结
1)AlexNet跟LeNet结构类似,但使用了更多的卷积层和更大的参数空间来拟合⼤规模数据集 ImageNet。它是浅层神经网络和深度神经网络的分界线。
2)虽然看上去AlexNet的实现⽐LeNet的实现也就多了⼏行代码而已,但这个观念上的转变和真正优秀实验结果的产生令学术界付出了很多年。
来源:CSDN
作者:CoreJT
链接:https://blog.csdn.net/sdu_hao/article/details/103201620