深度学习经典架构:VGG与Inception解析
在深度学习领域,不同的网络架构各有其独特的设计理念和优势。本文将详细介绍VGG和Inception这两种经典的卷积神经网络架构,包括它们的特点、原理以及在PyTorch中的实现方法。
1. VGG网络架构
VGG网络由牛津大学的视觉几何组(Visual Geometry Group)提出,其主要贡献在于通过使用非常小的(3x3)卷积滤波器,对深度不断增加的网络进行了全面评估。与之前的网络相比,VGG网络有两个主要区别:
-
使用更小的(3x3)卷积滤波器
:之前的网络在第一个卷积层通常依赖于7x7或11x11的较大内核,而VGG网络在整个网络中仅使用3x3大小的内核。三个3x3滤波器的感受野与单个7x7滤波器相同,但使用三个较小的滤波器可以带来更多的非线性和更少的参数,从而加快学习速度并提高对过拟合的鲁棒性。
-
去除局部响应归一化(LRN)层
:局部响应归一化最初在Alexnet架构中引入,其目的是限制ReLU层的输出并鼓励侧向抑制。然而,VGG论文表明,添加LRN层并不能提高准确性,因此在其架构中去除了这些层。
VGG网络家族有5种不同的配置,主要区别在于层数(VGG - 11、VGG - 13、VGG - 16和VGG - 19)。无论具体配置如何,VGG网络家族都遵循共同的结构:
-
输入图像大小
:所有架构都处理224x224大小的输入图像。
-
5个卷积块(Conv Blocks)
:每个块可以有多个卷积层,后跟一个最大池化层。所有单个卷积层使用3x3内核,步长为1,不改变输出特征图的空间分辨率。每个卷积层后面都跟着一个ReLU层,增加非线性。每个卷积块末尾的最大池化层将空间分辨率降低一半。
-
3个全连接层(FC)
:第一个全连接层接受512x7x7大小的输入并转换为4096维输出,第二个全连接层将4096维输出转换为另一个4096维输出,最后一个全连接层将4096维输出转换为C维输出,其中C表示类的数量。
下面是VGG网络各部分的具体实现代码:
import torch
import torch.nn as nn
# 实现单个Conv Block
class ConvBlock(nn.Module):
def __init__(self, in_channels, num_conv_layers, num_features):
super(ConvBlock, self).__init__()
modules = []
for i in range(num_conv_layers):
modules.extend([
nn.Conv2d(
in_channels, num_features,
kernel_size=3, padding=1),
nn.ReLU(inplace=True)
])
in_channels = num_features
modules.append(nn.MaxPool2d(kernel_size=2))
self.conv_block = nn.Sequential(*modules)
def forward(self, x):
return self.conv_block(x)
# 实现Conv Backbone
class ConvBackbone(nn.Module):
def __init__(self, cfg):
super(ConvBackbone, self).__init__()
self.cfg = cfg
self.validate_config(cfg)
modules = []
for block_cfg in cfg:
in_channels, num_conv_layers, num_features = block_cfg
modules.append(ConvBlock(
in_channels, num_conv_layers, num_features))
self.features = nn.Sequential(*modules)
def validate_config(self, cfg):
assert len(cfg) == 5 # 5 Conv blocks
for i, block_cfg in enumerate(cfg):
assert type(block_cfg) == tuple and len(block_cfg) == 3
if i == 0:
assert block_cfg[0] == 3
else:
assert block_cfg[0] == cfg[i - 1][-1]
def forward(self, x):
return self.features(x)
# 实现VGG网络
class VGG(nn.Module):
def __init__(self, conv_backbone, num_classes):
super(VGG, self).__init__()
self.conv_backbone = conv_backbone
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, num_classes)
)
def forward(self, x):
conv_features = self.conv_backbone(x)
logits = self.classifier(
conv_features.view(
conv_features.shape[0], -1))
return logits
# 实例化VGG - 11网络
vgg11_cfg = [
(3, 1, 64),
(64, 1, 128),
(128, 2, 256),
(256, 2, 512),
(512, 2, 512)
]
vgg11_backbone = ConvBackbone(vgg11_cfg)
num_classes = 1000
vgg11 = VGG(vgg11_backbone, num_classes)
在实际应用中,我们通常使用torchvision包中已经实现的VGG网络:
import torchvision
vgg11 = torchvision.models.vgg11()
VGG网络架构的流程图如下:
graph LR
A[输入224x224图像] --> B[Conv Block 1]
B --> C[Conv Block 2]
C --> D[Conv Block 3]
D --> E[Conv Block 4]
E --> F[Conv Block 5]
F --> G[全连接层1]
G --> H[全连接层2]
H --> I[全连接层3]
I --> J[输出分类结果]
2. ReLU非线性激活函数
在深度学习中,非线性层赋予深度神经网络更强的表达能力,以建模复杂的数学函数。VGG网络(和Alexnet一样)使用了一种不同的非线性层,即修正线性单元(ReLU)。
Sigmoid和TanH激活函数存在梯度消失问题,当神经元的输出过高或过低时,梯度会变得很小。在深度网络中,由于梯度是通过反向传播计算的,每个层的导数会沿着网络相乘,如果每层的梯度都很小,初始层的梯度将非常接近0,导致训练无效。
ReLU函数的方程为:$ReLU(x) = max(0, x)$,当$x$大于0时,其导数为1(常数),其他地方为0,因此不会出现梯度消失问题。大多数现代深度网络都使用ReLU作为激活函数,在Alexnet论文中已经证明,使用ReLU非线性可以显著加快训练速度,因为它有助于更快收敛。
3. Inception网络架构
之前,增加神经网络的深度或宽度可以提高准确性,但这两种方法都存在两个主要缺点:盲目增加网络大小可能导致过拟合,以及增加训练和推理时的计算资源。Inception架构旨在解决这些缺点,允许在保持计算预算不变的情况下增加网络的深度和宽度。
Inception架构的核心思想是在每个级别使用多个不同大小的内核,然后对其输出进行加权组合,使网络能够学习对适当的内核赋予更多权重。原始的Inception模块使用3种不同的内核大小(1x1、3x3、5x5)对输入进行卷积,并进行3x3的最大池化,然后将输出连接起来。
然而,这种简单的Inception块存在一个主要缺陷,使用少量的5x5滤波器会显著增加参数数量。为了解决这个问题,Inception模块使用1x1卷积层在3x3和5x5滤波器之前减少输入通道数,从而大大减少了3x3和5x5卷积的参数数量。
使用降维后的Inception模块构建的神经网络架构被称为GoogLeNet,它有9个这样的Inception模块线性堆叠,深度为22层(包括池化层为27层),在最后一个Inception模块的末尾使用全局平均池化。为了防止网络中间部分“死亡”,论文引入了两个辅助分类器,通过对两个中间Inception模块的输出应用softmax并计算辅助损失,总损失函数是辅助损失和实际损失的加权和。
下面是Inception块的具体实现代码:
# 实现简单的Inception块
class NaiveInceptionModule(nn.Module):
def __init__(self, in_channels, num_features=64):
super(NaiveInceptionModule, self).__init__()
self.branch1x1 = torch.nn.Sequential(
nn.Conv2d(
in_channels, num_features,
kernel_size=1, bias=False),
nn.BatchNorm2d(num_features, eps=0.001),
nn.ReLU(inplace=True))
self.branch3x3 = torch.nn.Sequential(
nn.Conv2d(
in_channels, num_features,
kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(num_features, eps=0.001),
nn.ReLU(inplace=True))
self.branch5x5 = torch.nn.Sequential(
nn.Conv2d(
in_channels, num_features,
kernel_size=5, padding=2, bias=False),
nn.BatchNorm2d(num_features, eps=0.001),
nn.ReLU(inplace=True))
self.pool = torch.nn.MaxPool2d(
kernel_size=3, stride=1, padding=1)
def forward(self, x):
conv1x1 = self.branch1x1(x)
conv3x3 = self.branch3x3(x)
conv5x5 = self.branch5x5(x)
pool_out = self.pool(x)
out = torch.cat(
[conv1x1, conv3x3, conv5x5, pool_out], 1)
return out
# 实现降维后的Inception块
class Inceptionv1Module(nn.Module):
def __init__(self, in_channels, num_1x1=64,
reduce_3x3=96, num_3x3=128,
reduce_5x5=16, num_5x5=32,
pool_proj=32):
super(Inceptionv1Module, self).__init__()
self.branch1x1 = torch.nn.Sequential(
nn.Conv2d(
in_channels, num_1x1,
kernel_size=1, bias=False),
nn.BatchNorm2d(num_1x1, eps=0.001),
nn.ReLU(inplace=True))
self.branch3x3_1 = torch.nn.Sequential(
nn.Conv2d(
in_channels, reduce_3x3,
kernel_size=1, bias=False),
nn.BatchNorm2d(reduce_3x3, eps=0.001),
nn.ReLU(inplace=True))
self.branch3x3_2 = torch.nn.Sequential(
nn.Conv2d(
reduce_3x3, num_3x3,
kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(num_3x3, eps=0.001),
nn.ReLU(inplace=True))
self.branch5x5_1 = torch.nn.Sequential(
nn.Conv2d(
in_channels, reduce_5x5,
kernel_size=5, padding=2, bias=False),
nn.BatchNorm2d(reduce_5x5, eps=0.001),
nn.ReLU(inplace=True))
self.branch5x5_2 = torch.nn.Sequential(
nn.Conv2d(
reduce_5x5, num_5x5,
kernel_size=5, padding=2, bias=False),
nn.BatchNorm2d(num_5x5, eps=0.001),
nn.ReLU(inplace=True))
self.pool = torch.nn.Sequential(
torch.nn.MaxPool2d(
kernel_size=3, stride=1, padding=1),
nn.Conv2d(
in_channels, pool_proj,
kernel_size=1, bias=False),
nn.BatchNorm2d(pool_proj, eps=0.001),
nn.ReLU(inplace=True))
def forward(self, x):
conv1x1 = self.branch1x1(x)
conv3x3_1 = self.branch3x3_1(x)
conv3x3_2 = self.branch3x3_2(conv3x3_1)
conv5x5_1 = self.branch5x5_1(x)
conv5x5_2 = self.branch5x5_2(conv5x5_1)
pool_out = self.pool(x)
out = torch.cat(
[conv1x1, conv3x3_2, conv5x5_2, pool_out], 1)
return out
Inception模块的流程图如下:
graph LR
A[输入特征图] --> B[1x1卷积]
A --> C[1x1卷积 -> 3x3卷积]
A --> D[1x1卷积 -> 5x5卷积]
A --> E[3x3最大池化 -> 1x1卷积]
B --> F[连接输出]
C --> F
D --> F
E --> F
通过以上介绍,我们了解了VGG和Inception这两种经典的卷积神经网络架构的特点、原理和实现方法。这些架构在计算机视觉领域取得了显著的成果,为后续的研究和应用奠定了基础。
深度学习经典架构:VGG与Inception解析
4. 对比分析VGG和Inception架构
为了更清晰地了解VGG和Inception架构的差异,下面通过表格进行对比:
| 架构特点 | VGG | Inception |
| ---- | ---- | ---- |
| 卷积核大小 | 固定为3x3 | 多种(1x1、3x3、5x5) |
| 网络结构 | 多个卷积块加全连接层 | 多个Inception模块堆叠 |
| 计算复杂度 | 相对较高,参数较多 | 通过1x1卷积降维,计算量相对可控 |
| 应对不同尺度信息 | 单一卷积核难以适应不同尺度 | 多种卷积核组合,更好处理不同尺度 |
| 过拟合风险 | 盲目增加深度可能过拟合 | 一定程度上缓解过拟合问题 |
从表格中可以看出,VGG架构相对简单直接,通过增加网络深度来提升性能,但可能面临计算资源和过拟合的问题。而Inception架构则通过多种卷积核的组合和1x1卷积降维,在保持计算预算的同时,更好地处理不同尺度的信息,减少过拟合风险。
5. 实际应用中的选择
在实际应用中,选择VGG还是Inception架构需要根据具体的任务和数据情况来决定。以下是一些参考建议:
-
数据规模较小
:如果数据集较小,VGG架构可能更容易过拟合,此时Inception架构可能更合适,因为它具有更好的泛化能力。
-
对计算资源有限制
:如果计算资源有限,如在移动设备或嵌入式系统上部署模型,Inception架构的计算复杂度相对较低,更适合这种场景。
-
需要处理不同尺度信息
:如果任务需要处理不同尺度的目标,如目标检测、图像分类等,Inception架构的多尺度处理能力使其成为更好的选择。
-
追求简单架构和可解释性
:如果对模型的可解释性有较高要求,或者希望使用简单的架构进行快速实验,VGG架构的结构相对简单,更易于理解和调试。
6. 总结与展望
本文详细介绍了VGG和Inception这两种经典的卷积神经网络架构。VGG通过使用小的3x3卷积核和去除LRN层,在图像分类任务上取得了优异的成绩,其架构简单易懂,为后续的研究提供了基础。Inception架构则通过多尺度卷积和1x1卷积降维,解决了增加网络深度和宽度带来的过拟合和计算资源问题,在计算机视觉领域得到了广泛应用。
随着深度学习的不断发展,这两种架构也在不断改进和优化。未来,我们可以期待更多结合两者优点的新型架构出现,以应对更复杂的任务和数据。同时,随着硬件技术的进步,我们也可以利用更强大的计算资源来训练更大、更复杂的模型,进一步提升模型的性能。
在实际应用中,我们可以根据具体的任务需求和数据特点,灵活选择合适的架构,并结合其他技术如数据增强、模型融合等,来提高模型的准确性和泛化能力。
以下是一个选择架构的决策流程图:
graph TD
A[任务需求] --> B{数据规模大小}
B -->|小| C{对计算资源要求}
C -->|有限| D[选择Inception]
C -->|充足| E{是否需处理不同尺度信息}
E -->|是| D
E -->|否| F{对可解释性要求}
F -->|高| G[选择VGG]
F -->|低| D
B -->|大| H{对计算资源要求}
H -->|有限| D
H -->|充足| E
通过以上的分析和总结,希望能帮助读者更好地理解VGG和Inception架构,并在实际应用中做出更合适的选择。
VGG与Inception架构深度解析
超级会员免费看
4万+

被折叠的 条评论
为什么被折叠?



