news 2026/9/30 2:32:42

GoogLeNet 含并行连结的网络(Inception)详解:从原理到多框架动手实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GoogLeNet 含并行连结的网络(Inception)详解:从原理到多框架动手实现
  • 人工智能
  • 深度学习
  • 机器学习
  • 教程

【免费下载链接】d2l-zh

《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。

项目地址:https://gitcode.com/GitHub_Trending/d2/d2l-zh
点击查看免费下载

《动手学深度学习》中文版(d2l-zh)在"现代卷积神经网络"一章中完整呈现了 GoogLeNet 的诞生背景、Inception 块设计思想与逐模块可运行实现。本文以 chapter_convolutional-modern/googlenet_origin.md 及其中文版 chapter_convolutional-modern/googlenet.md 为主体,结合仓库 d2l/ 下的多框架工具源码,系统讲解 GoogLeNet 如何在 2014 年 ImageNet 挑战赛中胜出,以及如何用 MXNet、PyTorch、TensorFlow 和 PaddlePaddle 从零实现它并在 Fashion-MNIST 上完成训练。

背景:为什么需要并行多尺度卷积

在 GoogLeNet 之前,流行的网络使用小到 $1 \times 1$、大到 $11 \times 11$ 的卷积核。论文《Going Deeper with Convolutions》(Szegedy 等,2015)的核心问题之一是:什么样大小的卷积核最合适?其洞察是:与其选择一个固定大小的卷积核,不如同时组合使用不同大小的卷积核——不同尺寸的滤波器可以有效地识别不同范围的图像细节,同时可以为不同滤波器分配不同数量的参数。本节介绍的 GoogLeNet 是原始模型的一个简化版本:省略了少数为稳定训练而添加的临时特性(如辅助分类器),因为如今更好的训练算法已使这些特性不再必要。

Inception 块:四条并行路径的构成

GoogLeNet 的基本卷积块被称为Inception 块(Inception block),很可能得名于电影《盗梦空间》(Inception)中的台词"我们需要走得更深"("We need to go deeper")。

如图fig_inception所示,Inception 块由四条并行路径组成:

  1. 路径 1:单个 $1 \times 1$ 卷积层,输出通道数为c1;
  2. 路径 2:$1 \times 1$ 卷积层(输出c2[0]通道)后接 $3 \times 3$ 卷积层(输出c2[1]通道,padding=1);
  3. 路径 3:$1 \times 1$ 卷积层(输出c3[0]通道)后接 $5 \times 5$ 卷积层(输出c3[1]通道,padding=2);
  4. 路径 4:$3 \times 3$ 最大汇聚层(padding=1)后接 $1 \times 1$ 卷积层(输出c4通道)。

中间两条路径先在输入上执行 $1 \times 1$ 卷积,以减少通道数、降低模型复杂度;四条路径都使用合适的填充使输入输出高宽一致;最后把四条路径的输出在通道维度上连结,构成块的输出。Inception 块中通常调整的超参数是每层输出通道数。

下面是 MXNet(Gluon)版本的实现:

from d2l import mxnet as d2l from mxnet import np, npx from mxnet.gluon import nn npx.set_np() class Inception(nn.Block): # c1--c4是每条路径的输出通道数 def __init__(self, c1, c2, c3, c4, **kwargs): super(Inception, self).__init__(**kwargs) # 线路1,单1x1卷积层 self.p1_1 = nn.Conv2D(c1, kernel_size=1, activation='relu') # 线路2,1x1卷积层后接3x3卷积层 self.p2_1 = nn.Conv2D(c2[0], kernel_size=1, activation='relu') self.p2_2 = nn.Conv2D(c2[1], kernel_size=3, padding=1, activation='relu') # 线路3,1x1卷积层后接5x5卷积层 self.p3_1 = nn.Conv2D(c3[0], kernel_size=1, activation='relu') self.p3_2 = nn.Conv2D(c3[1], kernel_size=5, padding=2, activation='relu') # 线路4,3x3最大汇聚层后接1x1卷积层 self.p4_1 = nn.MaxPool2D(pool_size=3, strides=1, padding=1) self.p4_2 = nn.Conv2D(c4, kernel_size=1, activation='relu') def forward(self, x): p1 = self.p1_1(x) p2 = self.p2_2(self.p2_1(x)) p3 = self.p3_2(self.p3_1(x)) p4 = self.p4_2(self.p4_1(x)) # 在通道维度上连结输出 return np.concatenate((p1, p2, p3, p4), axis=1)

PyTorch 版本的实现(注意 PyTorch 的卷积层默认不带激活函数,需在forward中显式调用F.relu):

from d2l import torch as d2l import torch from torch import nn from torch.nn import functional as F class Inception(nn.Module): # c1--c4是每条路径的输出通道数 def __init__(self, in_channels, c1, c2, c3, c4, **kwargs): super(Inception, self).__init__(**kwargs) # 线路1,单1x1卷积层 self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1) # 线路2,1x1卷积层后接3x3卷积层 self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1) self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1) # 线路3,1x1卷积层后接5x5卷积层 self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1) self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2) # 线路4,3x3最大汇聚层后接1x1卷积层 self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1) self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1) def forward(self, x): p1 = F.relu(self.p1_1(x)) p2 = F.relu(self.p2_2(F.relu(self.p2_1(x)))) p3 = F.relu(self.p3_2(F.relu(self.p3_1(x)))) p4 = F.relu(self.p4_2(self.p4_1(x))) # 在通道维度上连结输出 return torch.cat((p1, p2, p3, p4), dim=1)

TensorFlow(Keras)版本的实现(所有padding使用'same'以保持空间尺寸):

from d2l import tensorflow as d2l import tensorflow as tf class Inception(tf.keras.Model): # c1--c4是每条路径的输出通道数 def __init__(self, c1, c2, c3, c4): super().__init__() # 线路1,单1x1卷积层 self.p1_1 = tf.keras.layers.Conv2D(c1, 1, activation='relu') # 线路2,1x1卷积层后接3x3卷积层 self.p2_1 = tf.keras.layers.Conv2D(c2[0], 1, activation='relu') self.p2_2 = tf.keras.layers.Conv2D(c2[1], 3, padding='same', activation='relu') # 线路3,1x1卷积层后接5x5卷积层 self.p3_1 = tf.keras.layers.Conv2D(c3[0], 1, activation='relu') self.p3_2 = tf.keras.layers.Conv2D(c3[1], 5, padding='same', activation='relu') # 线路4,3x3最大汇聚层后接1x1卷积层 self.p4_1 = tf.keras.layers.MaxPool2D(3, 1, padding='same') self.p4_2 = tf.keras.layers.Conv2D(c4, 1, activation='relu') def call(self, x): p1 = self.p1_1(x) p2 = self.p2_2(self.p2_1(x)) p3 = self.p3_2(self.p3_1(x)) p4 = self.p4_2(self.p4_1(x)) # 在通道维度上连结输出 return tf.keras.layers.Concatenate()([p1, p2, p3, p4])

中文版文档还提供了PaddlePaddle的变体(Inception(nn.Layer),结构与 PyTorch 完全对应,仅在 forward 中使用paddle.concat(x=[...], axis=1)),完整代码见 chapter_convolutional-modern/googlenet.md 第 122-154 行。

为什么这个网络如此有效?首先考虑滤波器的组合:它们以多种滤波器尺寸探索图像,意味着不同大小的滤波器可以高效识别不同范围的图像细节;同时,我们可以为不同滤波器分配不同数量的参数——这正是通道数分配比的设计自由度所在。

GoogLeNet 模型:九个 Inception 块的堆叠

如图fig_inception_full所示,GoogLeNet 共使用9 个 Inception 块和全局平均汇聚层的堆叠来生成估计:Inception 块之间的最大汇聚层降低维度;第一个模块类似 AlexNet 和 LeNet;Inception 块的组合从 VGG 继承;全局平均汇聚层避免了在末端堆叠全连接层(该思想来自 NiN,详见 chapter_convolutional-modern/nin.md)。

模块一(b1):大幅降采样

第一个模块使用 64 通道、$7 \times 7$ 卷积层(步幅 2、填充 3),后接 $3 \times 3$ 最大汇聚层(步幅 2、填充 1)。它对 $96 \times 96$ 的输入先做 $7 \times 7$ 大感受野特征提取,再下采样到 $24 \times 24$:

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

(Gluon 版本为nn.Conv2D(64, kernel_size=7, strides=2, padding=3, activation='relu')配合nn.MaxPool2D(pool_size=3, strides=2, padding=1);Keras 版本以padding='same'等效。)

模块二(b2):1×1 降维 + 3×3 扩维

第二个模块使用两个卷积层:先 64 通道 $1 \times 1$ 卷积,再 $3 \times 3$ 卷积将通道数扩大三倍到 192。这对应于 Inception 块中的第二条路径,随后接 $3 \times 3$ 最大汇聚层:

b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1), nn.ReLU(), nn.Conv2d(64, 192, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

模块三(b3):两个 Inception 块串联

第三个模块串联两个完整的 Inception 块(PyTorch 的Inception(in_channels, c1, c2, c3, c4)需要显式传入输入通道数;Gluon/Keras 版本自动推导):

  • 第一个块的输入为 192 通道,输出通道数 $64+128+32+32=256$,四路径输出通道数之比为 $64:128:32:32 = 2:4:1:1$;
  • 第二、三条路径先把输入通道分别降到 $96/192 = 1/2$ 和 $16/192 = 1/12$,再接第二层卷积;
  • 第二个块的输入为 256 通道,输出通道数增至 $128+192+96+64=480$,四路径之比为 $128:192:96:64 = 4:6:3:2$,其中第二、三路径分别先降到 $1/2$ 和 $1/8$。
b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32), Inception(256, 128, (128, 192), (32, 96), 64), nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

模块四(b4):五个 Inception 块串联

第四个模块更复杂,串联 5 个 Inception 块,其输出通道数分别是:

  • $192+208+48+64=512$
  • $160+224+64+64=512$
  • $128+256+64+64=512$
  • $112+288+64+64=528$
  • $256+320+128+128=832$

通道分配思路与模块三一致:含 $3 \times 3$ 卷积的第二条路径输出通道最多,其次是仅含 $1 \times 1$ 卷积的第一条路径,再次是含 $5 \times 5$ 卷积的第三条路径和含 $3 \times 3$ 最大汇聚的第四条路径;第二、三条路径会先按比例减小通道数,且这些比例在各个 Inception 块中略有不同:

b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64), Inception(512, 160, (112, 224), (24, 64), 64), Inception(512, 128, (128, 256), (24, 64), 64), Inception(512, 112, (144, 288), (32, 64), 64), Inception(528, 256, (160, 320), (32, 128), 128), nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

模块五(b5):全局平均汇聚 + 输出层

第五模块包含输出通道数为 $256+320+128+128=832$ 和 $384+384+128+128=1024$ 的两个 Inception 块。第五模块后面紧跟输出层,同 NiN 一样使用全局平均汇聚层将每个通道的高和宽变成 1;最后把输出变成二维数组,再接一个输出个数为标签类别数(Fashion-MNIST 为 10)的全连接层:

b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128), Inception(832, 384, (192, 384), (48, 128), 128), nn.AdaptiveAvgPool2d((1,1)), nn.Flatten()) net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))

(Gluon 版本使用nn.GlobalAvgPool2D()后接nn.Dense(10);TensorFlow 版本则把net封装成函数net()返回Sequential([b1(), b2(), b3(), b4(), b5(), Dense(10)]),因为d2l.train_ch6()需要传入一个构建函数以便在strategy.scope()内完成模型构建与编译,从而利用可用的 CPU/GPU 设备。)

各模块输出形状验证

GoogLeNet 计算复杂,不如 VGG 那样便于修改通道数。为了使 Fashion-MNIST 上的训练短小精悍,本节将输入的高和宽从 224 降到 96,这简化了计算。以下代码逐层打印各模块输出的形状变化:

X = torch.rand(size=(1, 1, 96, 96)) for layer in net: X = layer(X) print(layer.__class__.__name__,'output shape:\t', X.shape)

训练模型

和前面的章节一样,使用 Fashion-MNIST 数据集训练,训练前将图片转换为 $96 \times 96$ 分辨率。训练配置为:学习率lr=0.1、轮数num_epochs=10、批量大小batch_size=128:

lr, num_epochs, batch_size = 0.1, 10, 128 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96) d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

从仓库源码看训练细节:d2l.load_data_fashion_mnist在 d2l/torch.py 中实现——当传入resize=96时,会在ToTensor()前插入transforms.Resize(96),随后以 4 个工作进程(get_dataloader_workers())加载 FashionMNIST 数据。d2l.train_ch6定义在 d2l/torch.py,它对Linear与Conv2d层用nn.init.xavier_uniform_做权重初始化,使用 SGD 优化器与交叉熵损失,在 GPU 上逐 epoch 训练并动态绘制训练损失、训练精度与测试精度曲线(MXNet、TensorFlow、PaddlePaddle 的对应实现分别在 d2l/mxnet.py、d2l/tensorflow.py、d2l/paddle.py 中)。

小结

  • Inception 块相当于一个有 4 条路径的子网络,通过不同窗口形状的卷积层和最大汇聚层并行抽取信息,并使用 $1 \times 1$ 卷积层减少每像素级别上的通道维数,从而降低模型复杂度;最大汇聚层则降低分辨率。
  • GoogLeNet 将多个设计精细的 Inception 块与其他层(卷积层、全连接层)串联起来,其中 Inception 块的通道数分配之比是在 ImageNet 数据集上通过大量实验得来的。
  • GoogLeNet 和它的后继者们一度是 ImageNet 上最有效的模型之一,以较低的计算复杂度提供了类似的测试精度。

练习与思考

  1. 尝试 GoogLeNet 的后续版本,并实现运行、观察实验结果,包括:
    • 添加批量规范化层(batch normalization),详见 chapter_convolutional-modern/batch-norm.md;
    • 对 Inception 块进行调整(Inception-v2/v3,Szegedy 等 2016);
    • 使用标签平滑(label smoothing)进行模型正则化;
    • 加入残差连接(Inception-ResNet,Szegedy 等 2017),详见 chapter_convolutional-modern/resnet.md。
  2. 最小图像尺寸:GoogLeNet 能工作的最小图像尺寸是多少?(可结合各模块的填充与步幅推导空间尺寸的下限,并动手验证。)
  3. 参数规模对比:将 AlexNet、VGG 和 NiN 的模型参数大小与 GoogLeNet 比较。后两个网络架构是如何显著减少模型参数大小的?(提示:NiN 的全局平均汇聚替代全连接层、VGG 的重复块设计,都是减少参数的关键思想。)

延伸阅读

  • 本章所属的"现代卷积神经网络"章节索引见 chapter_convolutional-modern/index.md,其中按历史脉络依次介绍了 AlexNet、VGG、NiN、GoogLeNet、批量规范化(batch normalization)、ResNet 与 DenseNet;
  • 完整的多框架工具函数(数据加载、训练循环、动画绘制等)集中在仓库 d2l/ 目录下,包括 d2l/mxnet.py、d2l/torch.py、d2l/tensorflow.py 与 d2l/paddle.py。
  • 人工智能
  • 深度学习
  • 机器学习
  • 教程

【免费下载链接】d2l-zh

《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。

项目地址:https://gitcode.com/GitHub_Trending/d2/d2l-zh
点击查看免费下载
上一篇:A2UI React Renderer 样式架构深度解析:Light DOM 下的七层样式体系与级联优先级设计
下一篇:Unleash 前端表格处理架构决策:客户端与服务端数据流的设计规范(ADR 解读)

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 2:32:05

软文推广采购避坑指南:从行业价值到头部服务商选型全攻略

在品牌营销精细化、流量成本持续攀升的当下,传统硬性广告投放存在成本高、时效短、留存弱、信任度低等短板,已经难以满足企业长效品牌建设需求。而软文推广凭借公信力强、内容留存久、搜索引擎收录稳定、适配AI信息采信逻辑、可沉淀品牌数字资产等核心优…

作者头像 李华
网站建设 2026/9/30 2:31:45

海外储能升压一体机:箱变保护如何适配环网柜狭小空间

摘 要:储能升压一体机行业处于高速扩容阶段,集成化、液冷大功率机型成为主流,一体化方案凭借成本、施工、运维优势逐步替代传统分体设备。储能升压一体机高低侧多为环网柜,空间小,且变压器容量大,因此需要把…

作者头像 李华
网站建设 2026/9/30 2:31:27

Linux下shell执行多用户命令

一、前言 国庆期间,个人服务器断电了,导致服务器上面数据库等应用全部关闭了,但是启动应用时候需要切换到对应的用户,让同事启动,又存在各种应用比较麻烦,个人应用也没有提前编写维护手册,导致服…

作者头像 李华
网站建设 2026/9/30 2:31:10

深度拆解|Dif.Sh 开源特性开关(Feature Flags)技术架构与工程落地全解析

一、前言在现代前端、后端及全栈持续交付体系中,特性开关(Feature Flags/Feature Toggles)是支撑灰度发布、渐进式迭代、A/B测试、故障快速回滚的核心工程化能力。传统特性开关解决方案普遍存在架构割裂、配置与代码分离、版本不可追溯、评审…

作者头像 李华
网站建设 2026/9/30 2:30:23

triage - OUT-OF-SCOPE

范围外知识库(Out-of-Scope Knowledge Base) 仓库中的 .out-of-scope/ 目录存储被拒绝的功能请求的持久记录。它有两个用途: 机构记忆 —— 功能为何被拒绝,使问题关闭后推理不会丢失去重 —— 当新问题与先前的拒绝匹配时&#x…

作者头像 李华
网站建设 2026/9/30 2:29:50

wiliwili 快速上手指南:在 Switch、PS4 与 PC 上流畅观看 B 站

wiliwili 快速上手指南:在 Switch、PS4 与 PC 上流畅观看 B 站 【免费下载链接】wiliwili 第三方B站客户端,目前可以运行在PC全平台、PSVita、PS4 、Xbox 和 Nintendo Switch上 项目地址: https://gitcode.com/GitHub_Trending/wi/wiliwili wiliw…

作者头像 李华