前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >googlenet 复现

googlenet 复现

原创
作者头像
用户7680342
修改2020-11-19 10:06:10
4700
修改2020-11-19 10:06:10
举报
文章被收录于专栏:人工智能基础人工智能基础

整体介绍

googlenet是2014年ilsvrc冠军,一共22层。这个网络中,作者使用了新的inception模块来进行多尺度信息融合,让神经网络变得更宽。同时googlenet比他的前辈alexnet相比,在精度大大提升的同时,参数数量大大减少(是alexnet的1/12),使得网络更加精简部署在移动设备上成为可能。 经过试验,googlenet保持一次推理只运行15亿次(乘法、加法)计算的常量。这样在实际生产中,googlenet也具有极大价值。

googlenet的实现中,最重要的就是其inception模块(启发自小李子的电影,盗梦空间。那里面有一句经典语录: we must go deeper. 论文中的deep有两个意思: 1> 更加深入的研究神经网络: 引出 inception模块; 2>字面意义上的更加深的网络结构)。

googlenet的inception模块的设计思路来自Network in network(https://zhuanlan.zhihu.com/p/31199400 提出 1*1卷积) 和 有关网络稀疏性的经典文章 https://arxiv.org/abs/1310.6343 ,并且是这些思路的具体实现(inception模块内部大量使用1*1卷积层,以较少数据维度/深度,引入更多的非线性层,减少了计算量,inception内容通过将一个卷积层拆解为多层小卷积的叠加,增加了稀疏性)。

inception 模块

论文中inception有一些变种,这里我们先看下原始版本的inception:

可以看出,前一层数据传入当前inception 模块后,会同时经过1*1, 3 * 3, 5 * 5以及3*3 的pooling的处理,之后这些输出被concat在一起。这个结构的一个重要的问题是,因为我们在顶层会将多个卷积/池化层的输出concat在一起,那么最终的输出通道数就是下层通道数的和,也就意味着如果将多个inception 叠到一起,会发生整个网络结构的通道数越来越多的问题(最终计算量和参数量都是出现爆炸现象)。因此论文中就提出了如下降维操作

如上图,在数据经过3*3 和5*5卷积核处理之前,数据先经过1*1 降维。在经过3*3 池化后也要通过1*1降维。最终保证inception模块整体输出的维度不会过大。

具体代码:

class BasicConv2d(nn.Module):

    def __init__(
        self,
        in_channels: int,
        out_channels: int,
        **kwargs: Any
    ) -> None:
        super(BasicConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, bias=False, **kwargs)
        self.bn = nn.BatchNorm2d(out_channels, eps=0.001)

    def forward(self, x: Tensor) -> Tensor:
        x = self.conv(x)
        x = self.bn(x)
        return F.relu(x, inplace=True)
    
class Inception(nn.Module):

    def __init__(
        self,
        in_channels: int,
        ch1x1: int,
        ch3x3red: int,
        ch3x3: int,
        ch5x5red: int,
        ch5x5: int,
        pool_proj: int,
        conv_block: Optional[Callable[..., nn.Module]] = None
    ) -> None:
        super(Inception, self).__init__()
        if conv_block is None:
            conv_block = BasicConv2d
        self.branch1 = conv_block(in_channels, ch1x1, kernel_size=1)

        self.branch2 = nn.Sequential(
            conv_block(in_channels, ch3x3red, kernel_size=1),
            conv_block(ch3x3red, ch3x3, kernel_size=3, padding=1)
        )

        self.branch3 = nn.Sequential(
            conv_block(in_channels, ch5x5red, kernel_size=1),
            # Here, kernel_size=3 instead of kernel_size=5 is a known bug.
            # Please see https://github.com/pytorch/vision/issues/906 for details.
            conv_block(ch5x5red, ch5x5, kernel_size=5, padding=1)
        )

        self.branch4 = nn.Sequential(
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1, ceil_mode=True),
            conv_block(in_channels, pool_proj, kernel_size=1)
        )

    def _forward(self, x: Tensor) -> List[Tensor]:
        branch1 = self.branch1(x)
        branch2 = self.branch2(x)
        branch3 = self.branch3(x)
        branch4 = self.branch4(x)

        outputs = [branch1, branch2, branch3, branch4]
        return outputs

    def forward(self, x: Tensor) -> Tensor:
        outputs = self._forward(x)
        return torch.cat(outputs, 1)
        

如之前描述:

inception 模块包含四个分支:

第一个分支是一个1 * 1的卷积层

第二个分支是 1 * 1 之后连一个3 * 3卷积层

第三个分支是 1 * 1 之后连一个5 * 5卷积层

第四个分支最大池化后连一个1 * 1卷积层

这里使用一个Inception类来描述模块,并且子分支的filter个数都可以参数化修改。

BasicCond2D很简单不再赘述。

注意inception模块最后输出forward里面,使用torch.cat(outputs,1)对多个特征图进行连接,这里cat的第二个参数为1 ,即将多个特征图按照输出特征维度’摞起来‘ (默认数据维度排列为:[batch,channel, hight, width])

网络整体结构

最终的googlenet就是将多个inception模块摞起来:

  1. 对输入做两次 卷积+池化操作,
  2. 经过两个inception模块,
  3. 接一次池化,
  4. 接5 次inception 模块,
  5. 一次池化,
  6. 两次inception
  7. 全局池化
  8. dropout + 全连接
  9. softmax

整体的架构图很大这里不贴了,只贴一个网络结构描述以及对应参数个数等信息的表格(原始论文中的参数和计算量表好像有问题,以下表格是来自很多博客给出的修正后的参数和计算量表格):

这里简单计算一下参数数量和计算量:

第一个卷积层:

  1. 卷积层参数: 7 * 7 * 3 * 64 = 9408,
  2. ops数目: 7*7*3*64*112*112=118,013,952

3a inception模块:

  1. 参数(不包含偏置项):

(192*64(第一个1*1) + 192*96(3*3之前的1*1) + 9*128*96(3*3) +192*16(5*5之前的1*1) + 25*16*32(5*5) + 192*32(池化后的1*1)) =

(12288 +18432 +110592 +3072 + 12800 +6144)= 159.5k

  1. 计算量:参数* 28 * 28 = 163328*28*28 = 128,049,152 大约128个M

其余类似,这里有个具体的计算表: https://zhuanlan.zhihu.com/p/32566024

整体代码如下:

class GoogLeNet(nn.Module):
    def __init__(
        self,
        num_classes: int = 1000,
    ) -> None:
        super(GoogLeNet, self).__init__()
        
        conv_block = BasicConv2d
        inception_block = Inception
        
        self.conv1 = conv_block(3, 64, kernel_size=7, stride=2, padding=3)
        self.maxpool1 = nn.MaxPool2d(3, stride=2, ceil_mode=True)
        self.conv2 = conv_block(64, 64, kernel_size=1)
        self.conv3 = conv_block(64, 192, kernel_size=3, padding=1)
        self.maxpool2 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.inception3a = inception_block(192, 64, 96, 128, 16, 32, 32)
        self.inception3b = inception_block(256, 128, 128, 192, 32, 96, 64)
        self.maxpool3 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.inception4a = inception_block(480, 192, 96, 208, 16, 48, 64)
        self.inception4b = inception_block(512, 160, 112, 224, 24, 64, 64)
        self.inception4c = inception_block(512, 128, 128, 256, 24, 64, 64)
        self.inception4d = inception_block(512, 112, 144, 288, 32, 64, 64)
        self.inception4e = inception_block(528, 256, 160, 320, 32, 128, 128)
        self.maxpool4 = nn.MaxPool2d(2, stride=2, ceil_mode=True)

        self.inception5a = inception_block(832, 256, 160, 320, 32, 128, 128)
        self.inception5b = inception_block(832, 384, 192, 384, 48, 128, 128)


        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.dropout = nn.Dropout(0.2)
        self.fc = nn.Linear(1024, num_classes)

    def _forward(self, x: Tensor) -> Tuple[Tensor, Optional[Tensor], Optional[Tensor]]:
        # N x 3 x 224 x 224
        x = self.conv1(x)
        # N x 64 x 112 x 112
        x = self.maxpool1(x)
        # N x 64 x 56 x 56
        x = self.conv2(x)
        # N x 64 x 56 x 56
        x = self.conv3(x)
        # N x 192 x 56 x 56
        x = self.maxpool2(x)

        # N x 192 x 28 x 28
        x = self.inception3a(x)
        # N x 256 x 28 x 28
        x = self.inception3b(x)
        # N x 480 x 28 x 28
        x = self.maxpool3(x)
        # N x 480 x 14 x 14
        x = self.inception4a(x)
        # N x 512 x 14 x 14
        
        x = self.inception4b(x)
        # N x 512 x 14 x 14
        x = self.inception4c(x)
        # N x 512 x 14 x 14
        x = self.inception4d(x)
        # N x 528 x 14 x 14
        
        x = self.inception4e(x)
        # N x 832 x 14 x 14
        x = self.maxpool4(x)
        # N x 832 x 7 x 7
        x = self.inception5a(x)
        # N x 832 x 7 x 7
        x = self.inception5b(x)
        # N x 1024 x 7 x 7

        x = self.avgpool(x)
        # N x 1024 x 1 x 1
        x = torch.flatten(x, 1)
        # N x 1024
        x = self.dropout(x)
        x = self.fc(x)
        # N x 1000 (num_classes)
        return x, aux2, aux1


    def forward(self, x: Tensor) -> GoogLeNetOutputs:
        
        x = self._forward(x)
        return x

注意:

在googlenet中,有三个辅助训练的输出,在后续的论文中,发现辅助训练输出没有什么用,这里我们就没有设置该输出了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 整体介绍
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档