一、前言
二、论文解读
1. Inception 模块
2. 网络深度问题
3. 全局平均池化
4. 卷积层的并行结构
5. 1x1 卷积核
6.详细的网络结构
三、代码复现
● 难度:夯实基础⭐⭐
● 语言:Python3、Pytorch3
● 时间:4月1日-4月7日
要求:
1、了解并学习图2中的卷积层运算量的计算过程(储备知识->卷积层运算量的计算,有我的推导过程,建议先自己手动推导,然后再看)
2、 了解并学习卷积层的并行结构与1x1卷积核部分内容(重点)
3、尝试根据模型框架图写入相应的pytorch代码,并使用Inception v1完成猴痘病识别
论文:Going deeper with convolutions
为了加深网络,文章中提出了Inception 结构,它是一个能够有效减少参数数量的结构。在之前的卷积网络中,使用多个卷积核并行处理一张图片,不同卷积核之间的输出结果串联成了当前卷积层的输出。而在Inception 结构中,则是使用了多个不同的卷积核并行处理一张图片。具体来说,Inception 模块由四个分支组成:1x1卷积核,3x3卷积核、5x5卷积核以及3x3最大池化,最后将这些不同的分支进行拼接。其中,1x1卷积核的作用是对通道数进行降维,这样就可以降低模型的参数量。此外,文章还针对Inception中可能存在的梯度消失和梯度爆炸问题,提出了使用BN层进行正则化的方式。
作者进一步讨论了如何在训练过程中解决网络深度问题。当网络的深度增加时,梯度消失和梯度爆炸问题也随之变得越来越严重,导致网络难以收敛。作者提出的方法是使用一个残差学习的结构来训练网络。Residual Block 将输入值与该层中间结果最后的输出相加,这样网络就可以保证每一层的输出都建立在前面层输出的基础上,避免梯度消失和爆炸问题。
此外,作者还提出了一个新的替代方案—— Global Average Pooling。在传统的卷积神经网络中,常常会通过全连接层将卷积层的输出转化为特征,再交给softmax分类。在过去,我们称这些特征为向量,其他领域则将其称之为“词嵌入”(word embedding)。然而,作者认为将这个过程替换为全局平均池化的过程就可以达到同样的效果,做到参数数量的大幅度减少,同时能够保留较好的特征。
卷积操作是计算非常耗时的操作,大量的运算需要消耗大量的时间和计算资源。在卷积层的并行结构中,我们使用多组卷积核来同时处理一组待处理数据,以此来提高计算速度。
在深度卷积网络中,一个卷积层通常使用多组卷积核来对输入数据进行处理,不同的卷积核会提取图片中不同尺寸和不同颜色空间的特征。这样在一个卷积层中我们就可以使用多组卷积核并行处理同一张图片生成多个特征图,每个卷积核专门处理输入数据的不同方面,最终再将每个卷积核生成的特征图进行并集,组合成一个更全面的特征图,这样实现了卷积层并行处理。
1x1 卷积核是卷积层的核心,在网络中被广泛使用,通常起到降维打击的作用。它一般用来在网络中调整特征图的深度,即指定输出特征含有多少个属性。以1x1的卷积核取代原来的全连接层,可以大大减少网络的参数量和计算量。
在文章中,作者提出了一个经典的例子:假设有一个 28x28x192 的特征图,如果需要将其连接到一个 2048 个神经元的全连接层中,那么 GAP(全局平均池化)的做法就是使用一个 1x1x2048 的卷积核,而使用全连接层的做法就是需要的参数量为 28x28x192x2048 = 52.43M,而使用 1x1 卷积核的做法需要的参数量为 192x2048 = 393.216K,前者参数的数量相对后者大出100倍级别,这就是 1x1 卷积核的优秀性能所在。
当然,1x1 卷积核的作用不仅仅是降维,同时还可以通过调整通道数进行增加宽度,增强特征图的多样性和丰富性。
pytorch
import torch
import torch.nn as nn
import torch.nn.functional as F
# 定义Inception Block中的各种卷积层
# (这里的k参数代表了Inception Block中的不同卷积层的卷积核大小)
def conv_block(in_channels, out_channels, k, stride=1, padding=0):
layers = []
# 首先进行1x1卷积核的卷积
layers.append(nn.Conv2d(in_channels, out_channels, kernel_size=1))
# 对1x1卷积核的输出再进行kxk卷积核的卷积
layers.append(nn.Conv2d(out_channels, out_channels, kernel_size=k, stride=stride, padding=padding))
# 对两次卷积操作的结果进行Batch Normalization处理
layers.append(nn.BatchNorm2d(out_channels))
# 使用ReLU激活函数对输出进行非线性化变换
layers.append(nn.ReLU())
return nn.Sequential(*layers)
# 定义Inception Block
class InceptionBlock(nn.Module):
def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
super(InceptionBlock, self).__init__()
# 第一支路径:1x1卷积核 -> 3x3卷积核
self.branch1 = conv_block(in_channels, ch3x3red, k=1)
self.branch2 = nn.Sequential(
# 第二支路径:1x1卷积核 -> 5x5卷积核
conv_block(in_channels, ch5x5red, k=1),
conv_block(ch5x5red, ch5x5, k=5, padding=2)
)
self.branch3 = nn.Sequential(
# 第三支路径:1x1卷积核 -> 1x7卷积核 -> 7x1卷积核 -> 3x3卷积核
conv_block(in_channels, ch1x1, k=1),
conv_block(ch1x1, ch3x3, k=3, padding=1)
)
self.branch4 = nn.Sequential(
# 第四支路径:3x3最大化池化 -> 1x1卷积核
nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
conv_block(in_channels, pool_proj, k=1)
)
def forward(self, x):
# 将输入在四个分支中进行处理
branch1 = self.branch1(x)
branch2 = self.branch2(x)
branch3 = self.branch3(x)
branch4 = self.branch4(x)
# 将所有分支输出在Channel维度上拼接起来
outputs = [branch1, branch2, branch3, branch4]
return torch.cat(outputs, 1)
# 定义整个Inception v1网络架构
class InceptionV1(nn.Module):
def __init__(self, num_classes=1000):
super(InceptionV1, self).__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3)
self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.conv2 = nn.Conv2d(64, 192, kernel_size=3, stride=1, padding=1)
self.maxpool2 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# 共设5个Inception Block
self.inception3a = InceptionBlock(192, 64, 96, 128, 16, 32, 32)
self.inception3b = InceptionBlock(256, 128, 128, 192, 32, 96, 64)
self.inception4a = InceptionBlock(480, 192, 96, 208, 16, 48, 64)
self.inception4b = InceptionBlock(512, 160, 112, 224, 24, 64, 64)
self.inception4c = InceptionBlock(512, 128, 128, 256, 24, 64, 64)
self.inception4d = InceptionBlock(512, 112, 144, 288, 32, 64, 64)
self.inception4e = InceptionBlock(528, 256, 160, 320, 32, 128, 128)
self.inception5a = InceptionBlock(832, 256, 160, 320, 32, 128, 128)
self.inception5b = InceptionBlock(832, 384, 192, 384, 48, 128, 128)
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.dropout = nn.Dropout(p=0.4)
self.fc = nn.Linear(1024, num_classes)
def forward(self, x):
x = F.relu(self.conv1(x))
x = self.maxpool1(x)
x = F.relu(self.conv2(x))
x = self.maxpool2(x)
x = self.inception3a(x)
x = self.inception3b(x)
x = self.inception4a(x)
x = self.inception4b(x)
x = self.inception4c(x)
x = self.inception4d(x)
x = self.inception4e(x)
x = self.inception5a(x)
x = self.inception5b(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.dropout(x)
x = self.fc(x)
return x