AlexNet
参考资料:
(1)ImageNet十年历任霸主之AlexNet - 知乎 (zhihu.com)
引入
AlexNet在2012年以第一名在Top-1分类精度霸榜ImageNet,并超过第二名近10个百分点,并且值得说明的是,霸榜2013年的ZFNet也就是对AlexNet进行调参后得到了更好的结果。相比于古早的LeNet实现的十分类,AlexNet能够成功进行一千分类并且达到了一个新高度。此外,AlexNet证明了神经网络的深度对模型效果至关重要,并且可以利用GPU大大加速这一过程。AlexNet虽然相比于VGG的热度和知名度没有那么高(值得说明的是已经相当高了),但是个人认为其里程碑的意义要比VGG高。其效果虽然逊于VGG,但是无论是归一化的思想,还是Dropout、ReLU的应用,亦或是深层网络利用GPU加速,为后续各项研究提供了一个很好的研究基础。
模型结构
AlexNet 包含八层:前五层是卷积层,其中一些是最大池化层,后三层是全连接层。除最后一层外,网络被拆分为两个部分,每个部分在一个 GPU 上运行。整个结构可以写成:
( C N N → L R N → M P ) 2 → ( C N N 3 → M P ) → ( F C → D O ) 2 → L i n e a r → S o f t m a x (CNN→LRN→MP)^2→(CNN^3→MP)→(FC→DO)^2→Linear→Softmax (CNN→LRN→MP)2→(CNN3→MP)→(FC→DO)2→Linear→Softmax
其中各个字母分别代表着:
- CNN = 卷积层(后面紧接着激活函数 ReLU)
- LRN = 局部响应归一化(Local Response Normalization)
- MP = 最大池化(Maxpooling)
- FC = 全连接层(后面紧接着激活函数 ReLU)
- 线性 = 全连接层(未激活)
- DO = 随机丢失(Dropout)
更为详细的结构图如下图所示:
Local Response Normalization
Local Response Normalization是一种归一化方式,主要针对的是卷积核不同通道上相同位置的参数。用数学公式表示就是:
b x , y i = a x , y i / ( k + α ∑ j = m a x ( 0 , i − n 2 ) j = m i n ( N − 1 , i + n 2 ) a x , y j 2 ) β b^i_{x,y}=a^i_{x,y}/(k+\alpha\sum_{j=max(0,i-\frac{n}{2})}^{j=min(N-1,i+\frac{n}{2})}{a^j_{x,y}}^2)^\beta bx,yi=ax,yi/(k+αj=max(0,i−2n)∑j=min(N−1,i+2n)ax,yj2)β
其中 a x , y i a^i_{x,y} ax,yi是第 i i i个卷积核上位置为 ( x , y ) (x,y) (x,y)的输入参数, b x , y i b^i_{x,y} bx,yi是第 i i i个卷积核上位置为 ( x , y ) (x,y) (x,y)的输出参数, N N N是总卷积核的数量, n n n是归一化邻居数量(因为可能不是同时对所有卷积核/通道进行归一化), α , β , k \alpha,\beta,k α,β,k是超参数。在论文中,采用的是𝑘=2, 𝑛=5, 𝛼=0.0001, 𝛽=0.75的值。
这一操作对于像AlexNet这种深层次网络比较必要,因为AlexNet会使用上百个通道,使用这种归一化可以一定程度上帮助模型收敛(类似于数据预处理中的归一化的作用)。从神经科学角度上来看,归一化可以看成是神经元群体之前相互抑制作用。
ReLU
ReLU是由一个分段函数组成的,在自变量小于0的部分恒等于0,在自变量大于0的部分等于自变量,即 R e L U = m a x ( 0 , x ) ReLU=max(0,x) ReLU=max(0,x)。相比于Sigmoid,ReLU更容易收敛,且由于梯度比较容易求(大于0的导数恒为1)。当因变量很大的时候,经过Sigmoid激活后基本都接近于1,导致梯度消失,而ReLU会避免这种情况。
Dropout
Dropout即在训练过程中(注意预测的时候不使用),按照特定概率随机丢失一定的神经元数据,这样可以有效降低过拟合,但是其代价是可能会导致模型收敛的epoch数更多。
实现代码
Pytorch框架中的torchvision库可以很方便的对其进行调用和实现。模型结构如下:
AlexNet(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))
(1): ReLU(inplace=True)
(2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
(3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(4): ReLU(inplace=True)
(5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(7): ReLU(inplace=True)
(8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(9): ReLU(inplace=True)
(10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU(inplace=True)
(12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(avgpool): AdaptiveAvgPool2d(output_size=(6, 6))
(classifier): Sequential(
(0): Dropout(p=0.5, inplace=False)
(1): Linear(in_features=9216, out_features=4096, bias=True)
(2): ReLU(inplace=True)
(3): Dropout(p=0.5, inplace=False)
(4): Linear(in_features=4096, out_features=4096, bias=True)
(5): ReLU(inplace=True)
(6): Linear(in_features=4096, out_features=1000, bias=True)
)
)
值得说明的是,现在Pytorch库中的AlexNet并不是原论文提出的模型,而是一种经过参数调整后效果更好一些的模型。如果要封装成一个类,并控制输出的维度,可以使用如下代码:
import torch.nn as nn
import torchvision.models as models
from torchvision.models.alexnet import AlexNet_Weights
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.alexnet = models.alexnet(weights=AlexNet_Weights.IMAGENET1K_V1)
self.dim_feat = 1000
self.alexnet.classifier[2] = nn.Linear(4096, 1000)
def forward(self, x):
output = self.alexnet(x)
return output