1 前言
在之前的文章中我们多次谈到,如何有效的对输入数据进行特征提取,然后再将提取得到的特征输入到下游的任务模型中是深度学习中的一个重要研究方向。尤其是在图像处理这个领域中,自卷积操作问世以来,如何设计一个有效的卷积神经网络结构就成为了一个热门的研究点。研究者们通过设计不同架构的卷积网络来对输入的图像进行特征提取,都希望设计出的模型能够表现出强大的学习能力,以此来提高下游任务的精度。
在上一篇文章中, 笔者介绍了第一个经典的卷积神经网络LeNet5,在接下来的内容中笔者将开始介绍它的继任者AlexNet网络。公众号后台回复“论文”即可获取论文下载链接!
2 AlexNet网络
2012年,AlexNet网络横空出世,而这一名字也取自于第一作者的名字Alex Krizhevsky [3]。简单来说,AlexNet是在LeNet5的基础上进行了改进。不同于LeNet5的是AlexNet采用了5层卷积+3层全连接的网络结构,最后AlexNet以很大的优势赢得了ImageNet 2012图像识别挑战赛。下面我们就来具体的看看AlexNet的网络结构。
2.1 AlextNet结构
图 1. 原始AlexNet网络结构图
如图1所示为AlexNet原始的网络结构图,可以发现看起来并不是那么的直观明了。同时,受限于当时GPU缓存的大小,所以作者当时在训练这一网络时,将其分成了上下两个部分分别在不同的GPU上运算。但是,我们现在就大可不必这样做,直接合并在一起即可。因此,我们可以重新将其画成如下形式。
图 2. AlexNet网络结构图
如图2所示就是重画后的结构图(原图.vsd
文件请加微信’nulls8’获取)。这里需要说明一点的是,图1中卷积后特征图的大小好像有点错误。例如第一次卷积后的大小应该是
54
×
54
54\times54
54×54,但原图中却写的
55
×
55
55\times55
55×55。这导致原图中后面所有特征图的大小都出现了错误,但是在图2中我们进行了修正。从图2可以看出,虽然AlexNet与LeNet的设计理念非常相似,但也有着显著的区别:
AlexNet第一层中的卷积窗口的大小是 11 × 11 11\times11 11×11。这是因为ImageNet中绝大多数图像的高和宽均比MNIST图像的高和宽大10倍以上,ImageNet图像的物体占用更多的像素,所以需要更大的卷积窗口来捕获物体。第二层中的卷积窗口形状减小到 5 × 5 5×5 5×5,之后全采用 3 × 3 3\times3 3×3的卷积窗口。此外,第一、第二和第五个卷积层之后都使用了窗口形状为 3 × 3 3\times3 3×3、步幅为 S = 2 S=2 S=2的最大池化层。同时,AlexNet使用的卷积通道数也大于LeNet5中的卷积通道数的数十倍。紧接着最后一个卷积层的是两个输出个数为 4096 4096 4096的全连接层。
AlexNet将sigmoid激活函数改成了更加简单的ReLU激活函数(尽管我们之前在实现LeNet时就已经使用了ReLU)。一方面,ReLU激活函数在计算上会更加简单,它并没有sigmoid中的求幂运算。另一方面,当sigmoid激活函数的输出接近0或1时,这些区域的梯度几乎为0,从而造成反向传播无法继续更新部分模型的参数;而ReLU激活函数在正区间的梯度恒为1。因此,若模型参数初始化不当,sigmoid函数可能得到的梯度近乎都为0,从而令模型无法得到有效训练。
同时,在AlexNet中还引入了一些新的方法来提高模型的分类准确率:①通过丢弃法(Dropout)来控制全连接层的模型复杂度;②在第一层、第二层卷积后都进行了Local Response Normalization的操作;③引入了大量的图像增广,如翻转、裁剪和颜色变化,从而进一步扩大数据集来缓解过拟合。
最后,据图2所示的网络结构,我们可以得出下面这张表:
其中输入形状和输出形状的三个维度分别为[in_channels,width,height]
,卷积核形状的四个维度分别为[w,w,in_channels,out_channels]
。从上面这张表也可以看出,AlexNet网络结构的参数量大约在6千万左右,而倒数第三个全连接几乎就占了三分之一。
2.2 AlextNet实现
下面我们就来安装图2所示的网络结构实现一个简洁版(不包含数据增强和标准化)AlexNet网络模型。
- 前向传播
class AlexNet(nn.Module): def __init__(self): super(AlexNet, self).__init__() self.conv = nn.Sequential( nn.Conv2d(in_channels=1, out_channels=96, kernel_size=11, stride=4), # PrintLayer(name="①卷积层:"), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), # kernel_size, stride nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1), nn.ReLU(), nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1), nn.ReLU(), nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, stride=1, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), ) # 这里全连接层的输出个数比LeNet中的大数倍。使用了丢弃层Dropout来缓解过拟合 self.fc = nn.Sequential( nn.Flatten(), nn.Linear(in_features=256 * 5 * 5, out_features=4096), nn.ReLU(), nn.Dropout(p=0.5), nn.Linear(in_features=4096, out_features=4096), nn.ReLU(), nn.Dropout(p=0.5), # 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000 nn.Linear(in_features=4096, out_features=10), ) def forward(self, img): feature = self.conv(img) output = self.fc(feature) return output
如上代码所示就是整个AlexNet网络的前向传播过程。可以发现,我们在通过Pytorch实现一个复杂网络的前向传播过程时,可以分块的通过多个nn.Sequential()
来进行组合。例如上面代码中第一个nn.Sequential()
实现的就是卷积池化部分的功能,而第二个nn.Sequential()
实现的是全连接层部分的功能。这样写的好处就是可以增大代码的可读性,同时也易于修改或者复用。当然,如果方便的话你依旧可以将这些操作都放到一个nn.Sequential()
中。 - 模型训练
...... device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') self.net.to(device) for epoch in range(self.epochs): for i, (x, y) in enumerate(train_iter): x, y = x.to(device), y.to(device) logits = self.net(x) l = loss(logits, y) optimizer.zero_grad() l.backward() optimizer.step() # 执行梯度下降 if i % 50 == 0: acc = (logits.argmax(1) == y).float().mean() print("Epochs[{}/{}]---batch {}---acc {:.4}---loss {:.4}".format( epoch+1, self.epochs, i, acc, l)) self.net.eval()# 切换到评估模式 print("Epochs[{}/{}]--acc on test {:.4}".format(epoch+1, self.epochs,self.evaluate(test_iter, self.net,device))) self.net.train()# 切回到训练模式......
由于篇幅有限,这里就之说几个新增加的知识点,对于其它部分的代码在此就不再赘述,可以直接参见源码[4]。第一:由于网络参数的极具增多,所以我们需要通过GPU来加快网络的训练速度。上述代码第一行就指明了,如果存在cuda
就使用GPU,不存在则使用CPU(GPU配置参见此处)。第二:由于我们在构建网络的时候使用了Dropout,所以需要在训练和测试模式之间切换,因此需要调用模型对应的.eval()
和.train()
方法。 - 训练结果
Epochs[1/5]---batch 0---acc on test 0.874Epochs [2/5]---batch 0---acc on test 0.882Epochs [3/5]---batch 0---acc on test 0.8956Epochs [4/5]---batch 0---acc on test 0.8982Epochs [5/5]---batch 0---acc on test 0.9043
可以看到,大约在5个Epochs后,模型在测试集上的准确率就达到了0.9043,而同样的情况下LeNet5的结果只有0.87。因此,可以明显的发现,AlexNet在性能上的确优于LeNet5模型。不过AlexNet对Fashion-MNIST数据集来说可能过于复杂,大家可以试着简化模型来使训练更快,同时保证准确率不会出现明显下降的情况。
3 总结
在本篇文章中,笔者首先介绍了AlexNet网络模型的架构和原理,同时也对模型中的参数量进行了介绍;其次介绍了如何通过Pytorch来对这一网络结构进行实现,并同时介绍了在Pytorch中如何通过GPU来对模型进行训练;最后我们还对比了5轮迭代后,LeNet5和AlexNet网络模型在测试集上的准确率。在下一篇的文章中,我们将开始学习卷积网络中的第三个经典模型VGG。
本次内容就到此结束,感谢您的阅读!如果你觉得上述内容对你有所帮助,欢迎关注并传播本公众号!青山不改,绿水长流,我们月来客栈见!
引用
[1]https://zh.d2l.ai/
[2]https://tangshusen.me/Dive-into-DL-PyTorch
[3]Krizhevsky A, Sutskever I, Hinton G E. Imagenet classification with deep convolutional neural networks[C]//Advances in neural information processing systems. 2012: 1097-1105.
[4]示例代码:https://github.com/moon-hotel/DeepLearningWithMe