alex、vgg、googlet的pytorch代码实现---附CNN常见问题汇总
前言
AlexNet、GoogLeNet 和 VGGNet 均起源于 ImageNet 大规模视觉识别挑战赛(ILSVRC),该比赛旨在推动图像识别技术的发展,这些模型在不同年份取得了优异成绩,推动了深度学习在计算机视觉领域的广泛应用。它们的起源和时间如下:
AlexNet:2012 年,Alex Krizhevsky 等人提出了 AlexNet 模型,并在 ImageNet 图像分类竞赛中取得了显著成绩。该模型由五层卷积和三层全连接组成,使用了 ReLU 激活函数和 Dropout 等技术,证明了深度卷积神经网络在图像识别任务中的强大能力,开启了深度学习的黄金时代。
VGGNet:2014 年,牛津大学的 Visual Geometry Group(VGG)团队提出了 VGGNet 模型。它在 AlexNet 基础上提出了更深的网络,主要使用 3x3 的卷积核,网络层数达到 16 层和 19 层,如 VGG-16 和 VGG-19,提高了模型的泛化能力和识别精度,在 ImageNet 竞赛中进一步提升了识别性能。
GoogLeNet:2014 年,Google 团队提出了 Inception 结构,并基于此构建了 GoogLeNet 模型。GoogLeNet 在当年的 ImageNet 分类任务上击败了 VGG 夺得冠军。该模型通过引入不同尺寸的卷积核和池化操作,实现了多尺度特征提取,同时使用 1x1 卷积核降维,减少计算量,提高了模型的表达能力和识别性能。
1.AlexNet

在网络设计上其实并非如上图所示,上图包含了GPU通信的部分。这是由当时GPU受限制引起的,作者使用两块GPU进行计算,因此分为上下两部分。但是以目前GPU的处理能力,单GPU足够了,因此其结构图可以如下所示:

AlexNet网络结构相对简单,共8层,前5层是卷积层,后3层是全连接层。
原模型前两个卷积层所使用的卷积核的大小分别为11*11、5*5,由于寡人是在CIFAIR10运行的,所以调整了卷积核的大小,下文的vgg、google,也是同理,这不影响理解这些经典网络,学到结构与思路即可。
# AlexNet模型定义
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.features = nn.Sequential(
# 第一个卷积层
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# 第二个卷积层
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# 第三个卷积层
nn.Conv2d(192, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
# 第四个卷积层
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
# 第五个卷积层
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
)
# 全连接层(适配CIFAR-10的4x4输出尺寸)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(256 * 4 * 4, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.5),
nn.Linear(4096, 10)
)
def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x
这里有细节,第三、四层卷积层没有使用池化,为什么?因为池化会丢失细节,不池化,虽然会增大计算量,但是模型可以学到更复杂的特征。
2.VGGNet:

VGGNET最明显的改进是减小卷积核的大小,增加卷积层的数目。
采用多个卷积层,用较小的卷积核代替具有卷积核的较大卷积层,一方面可以减少参数,而且作者认为它等价于更多的非线性映射,提高了拟合的表达能力。
小卷积核优点:
1.多个小卷积核连起来能提取更复杂的特征,感受野也不小。
2.参数量比大卷积核少多了,这样模型训练起来更快,也不容易过拟合。
# VGG16模型定义
class VGG16(nn.Module):
def __init__(self):
super(VGG16, self).__init__()
self.features = nn.Sequential(
# 第一个卷积块2c1p
nn.Conv2d(3, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# 第二个卷积块2c1p
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# 第三个卷积块3c1p
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# 第四个卷积块3c1p
nn.Conv2d(256, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# 第五个卷积块3c1p
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2)
)
# 全连接层(适配CIFAR-10的4x4输出尺寸) 13c+3fc 块、层、核
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(512 * 1 * 1, 4096), # CIFAR-10经过5次池化后尺寸为32/2^5=1
nn.ReLU(inplace=True),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.5),
nn.Linear(4096, 10)
)
def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x
之所以叫vgg16是因为13个卷积层+3个全连接层。针对VGG16进行具体分析发现,VGG16共包含:
13个卷积层(Convolutional Layer),分别用conv3-XXX表示 (XXX为输出通道数,3代表kernel_size)
3个全连接层(Fully connected Layer),分别用FC-XXXX表示(XXX为输出神经元个数)
5个池化层(Pool layer),分别用maxpool表示
一个大的卷积块里,进行多次卷积层的操作,然后进行激活函数,最后整个卷积块进行池化操作。
这个结构安排是很有深意的。
3.GoogLeNet
GoogLeNet,也被称为 Inception-v1
GoogLeNet 提出了一种并联结构,下图是论文中提出的 inception 原始结构,将特征矩阵同时输入到多个分支进行处理,并将输出的特征矩阵按深度进行拼接,得到最终输出。inception 增加网络深度和宽度的同时减少参数。
注意:每个分支所得特征矩阵的高和宽必须相同(通过调整 stride 和 padding),以保证输出特征能在深度上进行拼接。

在 inception 的基础上,还可以加上降维功能的结构,如下图所示,在原始 inception 结构的基础上,在分支 2,3,4 上加入了卷积核大小为 1x1 的卷积层,目的是为了降维(减小深度),减少模型训练参数,减少计算量。

下面是一个简化的 GoogLeNet 网络结构概述:
初始层
卷积层:GoogLeNet 开始于一个 7x7 的卷积层,通常带有步幅为 2,用于初步提取图像中的基本 特征。
最大池化层:紧随其后的是一个 3x3 的最大池化层,步幅为 2,用于减小特征图的尺寸,降低计算量。
卷积层:之后是两个连续的 3x3 卷积层,用于进一步特征提取。
最大池化层:再次进行最大池化,继续减小特征图的尺寸。
Inception 模块
GoogLeNet 的核心是 Inception 模块,它们通常在初始层之后被多次重复。一个典型的 Inception 模块包含以下部分:
1x1 卷积:用于降维,减少后续计算的成本。
3x3 卷积:用于提取局部特征,通常在 1x1 卷积之后。
5x5 卷积:用于捕获更大范围的特征,同样在 1x1 卷积之后。
最大池化:用于捕捉图像中的重要特征,同时在池化之后使用 1x1 卷积以保持通道数。
所有这些操作的输出会被拼接在一起,形成 Inception 模块的输出。
辅助分类器
GoogLeNet 在中间部分包含一到两个辅助分类器,它们在训练期间提供额外的监督信号,帮助网络学习更加鲁棒的特征表示。辅助分类器包括一个平均池化层,一个或多个 1x1 卷积层,一个全连接层,最后是一个 softmax 分类层。
输出层
在一系列 Inception 模块之后,GoogLeNet 使用全局平均池化层来将特征图转换为固定长度的向量。然后,这个向量被馈送到一个全连接层,最后是 softmax 分类层,用于生成各个类别的概率预测。

inception模块的代码实现:
# Inception模块定义
class Inception(nn.Module):
def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
# in_channels: 输入特征图的通道数\ch1x1: 1×1 卷积的输出通道数\
# ch3x3red: 3×3 卷积前的 1×1 降维卷积的输出通道数\
# ch3x3: 3×3 卷积的输出通道数\ch5x5red: 5×5 卷积前的 1×1 降维卷积的输出通道数\
# ch5x5: 5×5 卷积的输出通道数\pool_proj: 池化层后的 1×1 卷积的输出通道数
super(Inception, self).__init__()
# 1x1卷积路径
self.branch1 = nn.Sequential(
nn.Conv2d(in_channels, ch1x1, kernel_size=1),
nn.ReLU(inplace=True)
)
# 1x1卷积+3x3卷积路径
self.branch2 = nn.Sequential(
nn.Conv2d(in_channels, ch3x3red, kernel_size=1),
nn.ReLU(inplace=True),
nn.Conv2d(ch3x3red, ch3x3, kernel_size=3, padding=1),
nn.ReLU(inplace=True)
)
# 1x1卷积+5x5卷积路径
self.branch3 = nn.Sequential(
nn.Conv2d(in_channels, ch5x5red, kernel_size=1),
nn.ReLU(inplace=True),
nn.Conv2d(ch5x5red, ch5x5, kernel_size=3, padding=1),
nn.ReLU(inplace=True)
)
# 池化+1x1卷积路径
self.branch4 = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1, ceil_mode=True),
nn.Conv2d(in_channels, pool_proj, kernel_size=1),
nn.ReLU(inplace=True)
)
def forward(self, x):
branch1 = self.branch1(x)
branch2 = self.branch2(x)
branch3 = self.branch3(x)
branch4 = self.branch4(x)
outputs = [branch1, branch2, branch3, branch4]
return torch.cat(outputs, 1) # 在通道维度拼接
1. 拼接
Inception 模块的核心思想是并行使用不同尺寸的卷积核和池化操作,同时捕获不同尺度的特征。例如:1×1 卷积:捕获局部特征,降低通道数、3×3 卷积:捕获中等尺度特征、5×5 卷积:捕获大尺度特征、池化层:捕获全局特征。
这些分支的输出需要合并成一个特征张量,才能继续在后续层中处理。
return torch.cat(outputs, 1) 这行代码的作用是将 Inception 模块中四个并行分支的输出,在通道维度上进行拼接。下面详细解释这个操作:
2. torch.cat函数详解
torch.cat(tensors, dim=0) 是 PyTorch 中用于张量拼接的函数:
参数:
tensors:需要拼接的张量列表(如[tensor1, tensor2, ...])
dim:指定在哪个维度上进行拼接(0 表示行,1 表示列,对于图像通常为通道维度)
返回:拼接后的新张量
googlent模块的代码实现:
# GoogLeNet模型定义
class GoogLeNet(nn.Module):
def __init__(self, num_classes=10):
super(GoogLeNet, self).__init__()
# 输入: 3x32x32
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
nn.LocalResponseNorm(5, alpha=0.0001, beta=0.75)
)
# 输出: 64x16x16
self.conv2 = nn.Sequential(
nn.Conv2d(64, 64, kernel_size=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.LocalResponseNorm(5, alpha=0.0001, beta=0.75),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
# 输出: 192x8x8
self.inception3a = Inception(192, 64, 96, 128, 16, 32, 32)
self.inception3b = Inception(256, 128, 128, 192, 32, 96, 64)
self.maxpool3 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# 输出: 480x4x4
self.inception4a = Inception(480, 192, 96, 208, 16, 48, 64)
self.inception4b = Inception(512, 160, 112, 224, 24, 64, 64)
self.inception4c = Inception(512, 128, 128, 256, 24, 64, 64)
self.inception4d = Inception(512, 112, 144, 288, 32, 64, 64)
self.inception4e = Inception(528, 256, 160, 320, 32, 128, 128)
self.maxpool4 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# 输出: 832x2x2
self.inception5a = Inception(832, 256, 160, 320, 32, 128, 128)
self.inception5b = Inception(832, 384, 192, 384, 48, 128, 128)
# 全局平均池化和全连接层(适配CIFAR-10)
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 = self.conv1(x)
x = self.conv2(x)
x = self.inception3a(x)
x = self.inception3b(x)
x = self.maxpool3(x)
x = self.inception4a(x)
x = self.inception4b(x)
x = self.inception4c(x)
x = self.inception4d(x)
x = self.inception4e(x)
x = self.maxpool4(x)
x = self.inception5a(x)
x = self.inception5b(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.dropout(x)
x = self.fc(x)
return x
在这个 GoogLeNet 实现中,没有包含辅助分类器,这是为了简化模型,尤其针对 CIFAR-10 这类小数据集。但在原始 GoogLeNet中,辅助分类器是重要的设计之一,用于解决深度网络训练中的梯度消失问题。
辅助分类器的代码实现:
class AuxiliaryClassifier(nn.Module):
def __init__(self, in_channels, num_classes):
super().__init__()
self.avgpool = nn.AvgPool2d(kernel_size=5, stride=3) # 降维
self.conv = nn.Conv2d(in_channels, 128, kernel_size=1) # 1×1卷积降维
self.fc1 = nn.Linear(128 * 4 * 4, 1024) # 全连接层
self.fc2 = nn.Linear(1024, num_classes) # 输出层
def forward(self, x):
x = self.avgpool(x) # 例如:512×14×14 → 512×4×4
x = self.conv(x) # 512×4×4 → 128×4×4
x = torch.flatten(x, 1) # 展平为 128×4×4=2048
x = F.relu(self.fc1(x), inplace=True)
x = F.dropout(x, 0.7, training=self.training) # 更强的Dropout
x = self.fc2(x)
return x
4.CNN常见问题汇总
1.为什么rgb图像经过六个卷积核就变成了6通道的而不是18通道?
对于 RGB 图像,卷积核同时作用于红、绿、蓝三个通道,是为了综合利用图像在不同颜色空间的信息来提取整合特征。卷积操作本质上是对局部区域进行加权求和,也就是一种线性组合。每个卷积核都有一组权重值,这些权重值决定了它对输入数据不同部分的 “关注度”。当使用一个卷积核时,这个卷积核会分别与 RGB 图像的红、绿、蓝通道进行卷积运算,然后将这三个卷积结果相加(通常还会加上一个偏置项),最终得到一个单一通道的特征图。
2.1乘1卷积核降通道数是为什么?
1乘1大小的卷积核,它仅仅不会改变图片的大小,而非有降通道的能力。重点在于几个卷积核。
比如输入图像是5通道的,但是用了2个3乘3卷积核,这样降通道了,但是改变了图像的大小。
只有使用1乘1才不会改变图像的大小。用1个卷积核,无论输入通道数是多少,对之前各通道进行特征整合,整合到1个通道。
所以说,降通道数,关于在于几个卷积核,而非1乘1。
3.卷积层与卷积核
卷积层里包含卷积核,并非一个。这一层的输出通道数等于这一层卷积核个数。
4.张量拼接
支持张量操作的函数里都能涉及维度的概念。
一些函数如torch.cat 决定在什么维度进行拼接,
((a,b,c.....),x)将a,b,c等多个张量张量在x维度进行拼接。维度在代码里表示需要减去1。
combined = torch.cat((input, hidden), 1)
二维数组 1维是行,2维是列
三维数组 1维是数量,2维是高度,3维是宽度
四维数组 1维是数量,2维是高度,3维是宽度,4维是通道数
更多推荐

所有评论(0)