从AlexNet到ResNet,从里程碑到里程碑

来源:互联网 发布:淘宝网小衫春季女 编辑:程序博客网 时间:2024/06/06 19:24

本文主要介绍一下AlexNet、VGG、GoogLeNet以及ResNet这几个经典模型。顺便附上部分PyTorch实现。

网上的各种介绍很多,我也就不再重复说了。这篇文章主要是说说自己的感想。

今天看AlexNet其实已经颇为平淡了,毕竟这几年在网络工程上已经有了很大的进步,AlexNet的很多设计也不再被使用,例如LRN就被BN代替。不过当年AlexNet在两个GPU上跑的设计,倒是影响了后面出现Group Convolution的出现。

随后出现的VGG,主要的贡献就是验证了小kernel多层数的有效性,两层3x3的卷积层等价于一层5x5卷积层,却拥有了更多了非线性和更少的参数。VGG也因为其整洁的网络结构,成为其他模型在底层提取特征的首选。

跟VGG同年出现的GoogLeNet,又叫Inception,也是一个里程碑式的工作,也带动了后面一批工作的出现,比如Inception的后续若干版本,比如Xception。这个工作的网络结构很复杂,设计了Inception Module,又在中间层引入辅助分类器并且加权计算loss来克服梯度弥散问题,还用1x1的卷积层降维度。最后用了Global Average Pooling,应该是Network In Network首先使用的,既减少了模型的参数了又克服了过拟合。

以下是Inception Module的实现,我并没有加入BN。

class Inception(nn.Module):    def __init__(self, in_channels, k_1x1, k_3x3red, k_3x3, k_5x5red, k_5x5, pool_proj):        super(Inception, self).__init__()        self.b1 = nn.Sequential(            nn.Conv2d(in_channels=in_channels, out_channels=k_1x1, kernel_size=1),            nn.ReLU(inplace=True)        )        self.b2 = nn.Sequential(            nn.Conv2d(in_channels=in_channels, out_channels=k_3x3red, kernel_size=1),            nn.ReLU(inplace=True),            nn.Conv2d(in_channels=k_3x3red, out_channels=k_3x3, kernel_size=3, padding=1),            nn.ReLU(inplace=True)        )        self.b3 = nn.Sequential(            nn.Conv2d(in_channels=in_channels, out_channels=k_5x5red, kernel_size=1, padding=1),            nn.ReLU(inplace=True),            nn.Conv2d(in_channels=k_5x5red, out_channels=k_5x5, kernel_size=5, padding=1),            nn.ReLU(inplace=True)        )        self.b4 = nn.Sequential(            nn.MaxPool2d(kernel_size=3, stride=1),            nn.Conv2d(in_channels=in_channels, out_channels=pool_proj, kernel_size=1, padding=1),            nn.ReLU(inplace=True)        )    def forward(self, x):        y1 = self.b1(x)        y2 = self.b2(x)        y3 = self.b3(x)        y4 = self.b4(x)        return(torch.cat([y1, y2, y3, y4], 1))

最后是鼎鼎大名的ResNet。在我看来,Residual Learning这种想法的提出,是天才的是革命性的。从ResNet开始,神经网络在分类任务上第一次超越人类。整体的思路特别简单清晰,即skip connection,就是把数据复制一份,输入到卷积层中,再把卷积层的输出跟原始数据一一对应相加。这种设计有效的克服了梯度弥散的问题,从此之后,神经网络可以稳定的突破百层。而且也是在ResNet之后,神经网络的解构设计转向了block,不再从整体上重新设计网络。这种设计展现了极好的泛化能力,几乎所有新的block,即使在传统的类VGG的结构上已经取得了很不错的效果,加入Residual的思想之后,表现往往可以进一步提高,并且这种设计并没有大量提高模型的复杂度。后续基于ResNet又出现了很多优秀的工作,包括2017年CVPR上UniChicago提出的FractalNet,声称是跳出Residual Learning的一种新模型,我个人感觉本质上只是Residual Learning的另一种表现而已。

以下是用于深层ResNet的BottleNeck block的实现。

class BottleNeck(nn.Module):    def __init__(self, in_channels, out_channels, stride):        super(BottleNeck, self).__init__()        self.conv1 = nn.Sequential(            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1),            nn.BatchNorm2d(out_channels),            nn.ReLU(inplace=True)        )        self.conv2 = nn.Sequential(            nn.Conv2d(in_channels=out_channels, out_channels=out_channels, kernel_size=3, stride=stride, padding=1),            nn.BatchNorm2d(out_channels),            nn.ReLU(inplace=True)        )        self.conv3 = nn.Sequential(            nn.Conv2d(in_channels=out_channels, out_channels=(out_channels * 4), kernel_size=1),            nn.BatchNorm2d((out_channels * 4)),            nn.ReLU(inplace=True)        )        self.relu = nn.ReLU(inplace=True)        self.downsample = nn.Sequential(            nn.Conv2d(in_channels=in_channels, out_channels=(out_channels * 4), kernel_size=1, stride=stride),            nn.BatchNorm2d((out_channels * 4))        )    def forward(self, x):        identity = x        identity = self.downsample(identity)        x = self.conv1(x)        x = self.conv2(x)        x = self.conv3(x)        x = x + identity        x = self.relu(x)        return(x)


仔细读过以上四篇论文,并且写代码重现之后,再读其他论文就会有一种“这不就是把那XXX改了一下嘛”的感觉了。

完整实现在

AlexNet

VGG

GoogLeNet/Inception

ResNet

阅读全文
0 0
原创粉丝点击