深度卷积神经⽹络(ALEXNET)

《动手学深度学习pytorch》部分学习笔记,仅用作自己复习。

深度卷积神经⽹络(ALEXNET)

在LeNet提出后的将近20年里,神经网络⼀度被其他机器学习方法超越,如支持向量机。虽然LeNet可以在早期的⼩数据集上取得好的成绩,但是在更大的真实数据集上的表现并不尽如⼈意

一⽅面,神经网络计算复杂。虽然20世纪90年年代也有过⼀些针对神经网络的加速硬件,但并没有像之后GPU那样⼤量普及。因此,训练⼀个多通道、多层和有⼤量参数的卷积神经网络在当年年很难完成。另一⽅面,当年研究者还没有大量深入研究参数初始化和⾮凸优化算法等诸多领域,导致复杂的神经⽹络的训练通常较困难。

神经网络可以直接基于图像的原始像素进行分类。这种称为端到端(end-to-end)的方法节省了很多中间步骤。然而,在很长一段时间⾥更流⾏的是研究者通过勤劳与智慧所设计并⽣成的⼿工特征。这类图像分类研究的主要流程是:

  • 1. 获取图像数据集;
  • 2. 使⽤已有的特征提取函数⽣成图像的特征;
  • 3. 使⽤机器学习模型对图像的特征分类。

计算机视觉流程中真正重要的是数据和特征。也就是说,使⽤较干净的数据集和较有效的特征甚⾄比机器学习模型的选择对图像分类结果的影响更大。 

学习特征表示

在相当⻓的时间里,特征都是基于各式各样⼿工设计的函数从数据中提取的。事实上,不少研究者通过提出新的特征提取函数不不断改进图像分类结果。这⼀度为计算机视觉的发展做出了重要贡献。

⼀些研究者则持异议。他们认为特征本身也应该由学习得来。他们还相信,为了表征⾜够复杂的输入,特征本身应该分级表示。持这⼀想法的研究者相信,多层神经网络可能可以学得数据的多级表征,并逐级表示越来越抽象的概念或模式。以图像分类为例例,⼆维卷积层中物体边缘检测的例子。在多层神经网络中,图像的第一级的表示可以是在特定的位置和⻆度是否出现边缘;⽽而第二级的表示说不定能够将这些边缘组合出有趣的模式,如花纹;在第三级的表示中,也许上一级的花纹能进一步汇合成对应物体特定部位的模式。这样逐级表示下去,最终,模型能够较容易根据最后一级的表示完成分类任务。需要强调的是,输入的逐级表示由多层模型中的参数决定,而这些参数都是学出来的

尽管⼀直有⼀群执着的研究者不断钻研,试图学习视觉数据的逐级表征,然⽽很⻓一段时间⾥里里这些野⼼都未能实现。这其中有诸多因素值得我们⼀一分析。

缺失要素一:数据

包含许多特征的深度模型需要⼤量的有标签的数据才能表现得⽐其他经典⽅法更好。限于早期计算机有限的存储和90年代有限的研究预算,⼤部分研究只基于小的公开数据集。这一状况在2010年年前后兴起的大数据浪潮中得到改善。特别是,2009年诞⽣生的ImageNet数据集包含了1,000⼤类物体,每类有多达数千张不同的图像。ImageNet数据集同时推动计算机视觉和机器学习研究进⼊新的阶段,使此前的传统⽅法不再有优势。

缺失要素二:硬件

深度学习对计算资源要求很⾼。早期的硬件计算能力有限,这使训练较复杂的神经网络变得很困难。然而,通⽤GPU的到来改变了这⼀一格局。很久以来,GPU都是为图像处理理和计算机游戏设计的,尤其是针对⼤吞吐量的矩阵和向量乘法从而服务于基本的图形变换。值得庆幸的是,这其中的数学表达与深度网络中的卷积层的表达类似。通⽤GPU这个概念在2001年年开始兴起,涌现出诸如OpenCL和CUDA之类的编程框架。这使得GPU也在2010年前后开始被机器学习社区使⽤。

ALEXNET

2012年年,AlexNet横空出世。这个模型的名字来源于论文第⼀作者的姓名Alex Krizhevsky [1]。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的全连接层。这两个巨大的全连接层带来将近1 GB的模型参数。由于早期显存的限制,最早的AlexNet使⽤双数据流的设计使⼀个GPU只需要处理一半模型。幸运的是,显存在过去几年年得到了⻓足的发展,因此通常我们不再需要这样的特别设计了。

第⼆,AlexNetsigmoid激活函数改成了更加简单的ReLU激活函数。

一⽅面,ReLU激活函数的计算更简单,例如它并没有sigmoid激活函数中的求幂运算。

另⼀方面,ReLU激活函数在不同的参数初始化⽅法下使模型更容易训练。这是由于当sigmoid激活函数输出极接近0或1时,这些区域的梯度⼏乎为0,从⽽造成反向传播⽆法继续更新部分模型参数;⽽ReLU激活函数在正区间的梯度恒为1。因此,若模型参数初始化不不当,sigmoid函数可能在正区间得到⼏乎为0的梯度,从⽽令模型⽆无法得到有效训练。

第三,AlexNet通过丢弃法来控制全连接层的模型复杂度。而LeNet并没有使⽤用丢弃法。
第四,AlexNet引⼊了大量的图像增⼴,如翻转、裁剪和颜⾊变化,从而进一步扩大数据集来缓解过拟合。

实现稍微简化过的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(
            # in_channels, out_channels, kernel_size, stride, padding
            nn.Conv2d(1, 96, 11, 4), 
            nn.ReLU(),
            # kernel_size, stride
            nn.MaxPool2d(3, 2), 
            # 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
            nn.Conv2d(96, 256, 5, 1, 2),
            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))
        return output

打印看看⽹络结构。

AlexNet(
  (conv): Sequential(
    (0): Conv2d(1, 96, kernel_size=(11, 11), stride=(4, 4))
    (1): ReLU()
    (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(96, 256, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (4): ReLU()
    (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (6): Conv2d(256, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (7): ReLU()
    (8): Conv2d(384, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (9): ReLU()
    (10): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU()
    (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (fc): Sequential(
    (0): Linear(in_features=6400, out_features=4096, bias=True)
    (1): ReLU()
    (2): Dropout(p=0.5)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU()
    (5): Dropout(p=0.5)
    (6): Linear(in_features=4096, out_features=10, bias=True)
  )
)

读取数据

虽然论文中AlexNet使用ImageNet数据集,但因为ImageNet数据集训练时间较长,我们仍⽤前⾯的Fashion-MNIST数据集来演示AlexNet。读取数据的时候我们额外做了一步将图像⾼和宽扩大到AlexNet使⽤的图像⾼和宽224。这个可以通过 torchvision.transforms.Resize 实例来实现。也就是说,我们在 ToTensor 实例前使⽤ Resize 实例,然后使用 Compose 实例例来将这两个变换串联以方便调用。

# 本函数已保存在d2lzh_pytorch包中方便以后使用
def load_data_fashion_mnist(batch_size, resize=None, root='~/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)

训练

开始训练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)

输出:

training on  cuda
epoch 1, loss 0.0047, train acc 0.770, test acc 0.865, time 128.3 sec
epoch 2, loss 0.0025, train acc 0.879, test acc 0.889, time 128.8 sec
epoch 3, loss 0.0022, train acc 0.898, test acc 0.901, time 130.4 sec
epoch 4, loss 0.0019, train acc 0.908, test acc 0.900, time 131.4 sec
epoch 5, loss 0.0018, train acc 0.913, test acc 0.902, time 129.9 sec

小结

  • AlexNet跟LeNet结构类似,但使⽤了更多的卷积层和更大的参数空间来拟合⼤规模数据集ImageNet它是浅层神经网络和深度神经网络的分界线。
  • 虽然看上去AlexNet的实现比LeNet的实现也就多了几⾏代码⽽已,但这个观念上的转变和真正优秀实验结果的产生令学术界付出了很多年。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clark-dj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值