并行连接的网络 (GoogLeNet)

探索GoogLeNet的Inception架构创新,学习多尺度并行卷积的设计理念

发布于 2025-08-10 更新于 2025-08-101,487 字 4 分钟
并行连接的网络 (GoogLeNet)的文章封面
展开文章目录
  1. 1. GoogLeNet 网络架构设计
  2. 1.1 Inception 块的核心思想
  3. 1.2 四种并行路径设计
  4. 1.3 Inception 块的 PyTorch 实现
  5. 2. GoogLeNet 完整网络架构
  6. 2.1 网络整体设计
  7. 2.2 适配 Fashion-MNIST 的设计
  8. 2.3 网络结构分析
  9. 3. 模型训练与评估
  10. 3.1 训练配置与实现
  11. 3.2 训练结果与分析
  12. 参考资料

GoogLeNet的设计灵感

双重设计理念

  • “We Need To Go Deeper” - 通过更深的网络结构提高性能,借鉴 NiN 网络设计
  • “全面启动” - 同时引入多种卷积核尺寸的并行组合 (1×1, 3×3, 5×5)
  • “梦中梦” - 逐层深入的多层次特征捕捉概念

受《盗梦空间 (Inception)》影响

一方面,电影《盗梦空间 (Inception)》的台词”We Need To Go Deeper”被 GoogLeNet 用于强调模型通过更深的网络结构,进一步提高性能的方式。在具体实现上,借鉴了 NiN 网络。

另一方面,电影台湾译名”全面启动”似乎更能说明 GoogLeNet 同时引入多种卷积核尺寸的并行组合(1×1、3×3 和 5×5),捕捉多层次特征。这与电影中”梦中梦”逐层深入的概念不谋而合。

盗梦空间电影海报


1. GoogLeNet 网络架构设计

1.1 Inception 块的核心思想

1.2 四种并行路径设计

Inception并行路径

1×1 卷积层

  • 提取通道特征并降维
  • 计算效率最高的路径

1×1 卷积层 → 3×3 卷积层

  • 先对通道降维,减少计算量
  • 随后提取较大的空间特征

1×1 卷积层 → 5×5 卷积层

  • 先对通道降维,减少计算量
  • 随后提取更大的空间特征

3×3 最大池化层 → 1×1 卷积层

  • 先下采样特征图,保留重要信息
  • 随后调整通道数,匹配其他路径

Inception块结构示意图

1.3 Inception 块的 PyTorch 实现

Inception 块IncepBlock的 PyTorch 实现如下:

from typing import Tuple

import torch
from torch import nn


class IncepBlock(nn.Module):
    def __init__(self, in_channels: int, c1_out: int, c2_out: Tuple[int, int], c3_out: Tuple[int, int], c4_out: int):
        super().__init__()

        self.channel1 = nn.Sequential(  # 路径一: 1×1 卷积
            nn.Conv2d(in_channels, c1_out, kernel_size=1), nn.ReLU()
        )

        self.channel2 = nn.Sequential(  # 路径二: 1×1 卷积 -> 3×3 卷积
            nn.Conv2d(in_channels, c2_out[0], kernel_size=1), nn.ReLU(),
            nn.Conv2d(c2_out[0], c2_out[1], kernel_size=3, padding=1), nn.ReLU()
        )

        self.channel3 = nn.Sequential(  # 路径三: 1×1 卷积 -> 5×5 卷积
            nn.Conv2d(in_channels, c3_out[0], kernel_size=1), nn.ReLU(),
            nn.Conv2d(c3_out[0], c3_out[1], kernel_size=5, padding=2), nn.ReLU()
        )

        self.channel4 = nn.Sequential(  # 路径四: 3×3 最大池化 -> 1×1 卷积
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels, c4_out, kernel_size=1), nn.ReLU()
        )

    def forward(self, x):
        output1 = self.channel1(x)
        output2 = self.channel2(x)
        output3 = self.channel3(x)
        output4 = self.channel4(x)
        output = torch.cat([output1, output2, output3, output4], dim=1)
        return output

2. GoogLeNet 完整网络架构

2.1 网络整体设计

2.2 适配 Fashion-MNIST 的设计

GoogLeNet完整网络架构图

from typing import Tuple

import torch
from torch import nn, Tensor


class IncepBlock(nn.Module):
    def __init__(self, in_channels: int, c1_out: int, c2_out: Tuple[int, int], c3_out: Tuple[int, int], c4_out: int):
        super().__init__()

        self.channel1 = nn.Sequential(  # 路径一: 1×1 卷积
            nn.Conv2d(in_channels, c1_out, kernel_size=1), nn.ReLU()
        )

        self.channel2 = nn.Sequential(  # 路径二: 1×1 卷积 -> 3×3 卷积
            nn.Conv2d(in_channels, c2_out[0], kernel_size=1), nn.ReLU(),
            nn.Conv2d(c2_out[0], c2_out[1], kernel_size=3, padding=1), nn.ReLU()
        )

        self.channel3 = nn.Sequential(  # 路径三: 1×1 卷积 -> 5×5 卷积
            nn.Conv2d(in_channels, c3_out[0], kernel_size=1), nn.ReLU(),
            nn.Conv2d(c3_out[0], c3_out[1], kernel_size=5, padding=2), nn.ReLU()
        )

        self.channel4 = nn.Sequential(  # 路径四: 3×3 最大汇聚 -> 1×1 卷积
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels, c4_out, kernel_size=1), nn.ReLU()
        )

    def forward(self, x) -> Tensor:
        output1 = self.channel1(x)
        output2 = self.channel2(x)
        output3 = self.channel3(x)
        output4 = self.channel4(x)
        output = torch.cat([output1, output2, output3, output4], dim=1)
        return output


class GoogLeNet(nn.Module):
    def __init__(self, num_classes: int):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=64, kernel_size=7, stride=2, padding=3), nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),

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

            IncepBlock(in_channels=192, c1_out=64, c2_out=(96, 128), c3_out=(16, 32), c4_out=32),
            IncepBlock(in_channels=256, c1_out=128, c2_out=(128, 192), c3_out=(32, 96), c4_out=64),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),

            IncepBlock(in_channels=480, c1_out=192, c2_out=(96, 208), c3_out=(16, 48), c4_out=64),
            IncepBlock(in_channels=512, c1_out=160, c2_out=(112, 224), c3_out=(24, 64), c4_out=64),
            IncepBlock(in_channels=512, c1_out=128, c2_out=(128, 256), c3_out=(24, 64), c4_out=64),
            IncepBlock(in_channels=512, c1_out=112, c2_out=(144, 288), c3_out=(32, 64), c4_out=64),
            IncepBlock(in_channels=528, c1_out=256, c2_out=(160, 320), c3_out=(32, 128), c4_out=128),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),

            IncepBlock(in_channels=832, c1_out=256, c2_out=(160, 320), c3_out=(32, 128), c4_out=128),
            IncepBlock(in_channels=832, c1_out=384, c2_out=(192, 384), c3_out=(48, 128), c4_out=128),
            nn.AdaptiveAvgPool2d(1), nn.Flatten(),
            nn.Linear(in_features=1024, out_features=num_classes)
        )

        self._initialize_weights()

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
                if m.bias is not None: nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                if m.bias is not None: nn.init.constant_(m.bias, 0)

    def forward(self, x) -> Tensor:
        return self.model(x)

2.3 网络结构分析

使用torchinfo库的summary函数执行输出维度测试:

from torchinfo import summary

model = GoogLeNet(num_classes=10)
summary(model, input_size=(1, 1, 96, 96))
==========================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
==========================================================================================
GoogLeNet                                [1, 10]                   --
├─Sequential: 1-1                        [1, 10]                   --
│    └─Conv2d: 2-1                       [1, 64, 48, 48]           3,200
│    └─ReLU: 2-2                         [1, 64, 48, 48]           --
│    └─MaxPool2d: 2-3                    [1, 64, 24, 24]           --
│    └─Conv2d: 2-4                       [1, 64, 24, 24]           4,160
│    └─ReLU: 2-5                         [1, 64, 24, 24]           --
│    └─Conv2d: 2-6                       [1, 192, 24, 24]          110,784
│    └─ReLU: 2-7                         [1, 192, 24, 24]          --
│    └─MaxPool2d: 2-8                    [1, 192, 12, 12]          --
│    └─IncepBlock: 2-9                   [1, 256, 12, 12]          --
│    │    └─Sequential: 3-1              [1, 64, 12, 12]           12,352
│    │    └─Sequential: 3-2              [1, 128, 12, 12]          129,248
│    │    └─Sequential: 3-3              [1, 32, 12, 12]           15,920
│    │    └─Sequential: 3-4              [1, 32, 12, 12]           6,176
│    └─IncepBlock: 2-10                  [1, 480, 12, 12]          --
│    │    └─Sequential: 3-5              [1, 128, 12, 12]          32,896
│    │    └─Sequential: 3-6              [1, 192, 12, 12]          254,272
│    │    └─Sequential: 3-7              [1, 96, 12, 12]           85,120
│    │    └─Sequential: 3-8              [1, 64, 12, 12]           16,448
│    └─MaxPool2d: 2-11                   [1, 480, 6, 6]            --
│    └─IncepBlock: 2-12                  [1, 512, 6, 6]            --
│    │    └─Sequential: 3-9              [1, 192, 6, 6]            92,352
│    │    └─Sequential: 3-10             [1, 208, 6, 6]            226,096
│    │    └─Sequential: 3-11             [1, 48, 6, 6]             26,944
│    │    └─Sequential: 3-12             [1, 64, 6, 6]             30,784
│    └─IncepBlock: 2-13                  [1, 512, 6, 6]            --
│    │    └─Sequential: 3-13             [1, 160, 6, 6]            82,080
│    │    └─Sequential: 3-14             [1, 224, 6, 6]            283,472
│    │    └─Sequential: 3-15             [1, 64, 6, 6]             50,776
│    │    └─Sequential: 3-16             [1, 64, 6, 6]             32,832
│    └─IncepBlock: 2-14                  [1, 512, 6, 6]            --
│    │    └─Sequential: 3-17             [1, 128, 6, 6]            65,664
│    │    └─Sequential: 3-18             [1, 256, 6, 6]            360,832
│    │    └─Sequential: 3-19             [1, 64, 6, 6]             50,776
│    │    └─Sequential: 3-20             [1, 64, 6, 6]             32,832
│    └─IncepBlock: 2-15                  [1, 528, 6, 6]            --
│    │    └─Sequential: 3-21             [1, 112, 6, 6]            57,456
│    │    └─Sequential: 3-22             [1, 288, 6, 6]            447,408
│    │    └─Sequential: 3-23             [1, 64, 6, 6]             67,680
│    │    └─Sequential: 3-24             [1, 64, 6, 6]             32,832
│    └─IncepBlock: 2-16                  [1, 832, 6, 6]            --
│    │    └─Sequential: 3-25             [1, 256, 6, 6]            135,424
│    │    └─Sequential: 3-26             [1, 320, 6, 6]            545,760
│    │    └─Sequential: 3-27             [1, 128, 6, 6]            119,456
│    │    └─Sequential: 3-28             [1, 128, 6, 6]            67,712
│    └─MaxPool2d: 2-17                   [1, 832, 3, 3]            --
│    └─IncepBlock: 2-18                  [1, 832, 3, 3]            --
│    │    └─Sequential: 3-29             [1, 256, 3, 3]            213,248
│    │    └─Sequential: 3-30             [1, 320, 3, 3]            594,400
│    │    └─Sequential: 3-31             [1, 128, 3, 3]            129,184
│    │    └─Sequential: 3-32             [1, 128, 3, 3]            106,624
│    └─IncepBlock: 2-19                  [1, 1024, 3, 3]           --
│    │    └─Sequential: 3-33             [1, 384, 3, 3]            319,872
│    │    └─Sequential: 3-34             [1, 384, 3, 3]            823,872
│    │    └─Sequential: 3-35             [1, 128, 3, 3]            193,712
│    │    └─Sequential: 3-36             [1, 128, 3, 3]            106,624
│    └─AdaptiveAvgPool2d: 2-20           [1, 1024, 1, 1]           --
│    └─Flatten: 2-21                     [1, 1024]                 --
│    └─Linear: 2-22                      [1, 10]                   10,250
==========================================================================================
Total params: 5,977,530
Trainable params: 5,977,530
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 276.66
==========================================================================================
Input size (MB): 0.04
Forward/backward pass size (MB): 4.74
Params size (MB): 23.91
Estimated Total Size (MB): 28.69
==========================================================================================

3. 模型训练与评估

3.1 训练配置与实现

继续使用training_tools.py中的工具训练评估模型:

from typing import Tuple

import torch
from torch import nn, Tensor, optim

from training_tools import fashionMNIST_loader, Trainer


class IncepBlock(nn.Module):
    def __init__(self, in_channels: int, c1_out: int, c2_out: Tuple[int, int], c3_out: Tuple[int, int], c4_out: int):
        super().__init__()

        self.channel1 = nn.Sequential(  # 路径一: 1×1 卷积
            nn.Conv2d(in_channels, c1_out, kernel_size=1), nn.ReLU()
        )

        self.channel2 = nn.Sequential(  # 路径二: 1×1 卷积 -> 3×3 卷积
            nn.Conv2d(in_channels, c2_out[0], kernel_size=1), nn.ReLU(),
            nn.Conv2d(c2_out[0], c2_out[1], kernel_size=3, padding=1), nn.ReLU()
        )

        self.channel3 = nn.Sequential(  # 路径三: 1×1 卷积 -> 5×5 卷积
            nn.Conv2d(in_channels, c3_out[0], kernel_size=1), nn.ReLU(),
            nn.Conv2d(c3_out[0], c3_out[1], kernel_size=5, padding=2), nn.ReLU()
        )

        self.channel4 = nn.Sequential(  # 路径四: 3×3 最大汇聚 -> 1×1 卷积
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels, c4_out, kernel_size=1), nn.ReLU()
        )

    def forward(self, x) -> Tensor:
        output1 = self.channel1(x)
        output2 = self.channel2(x)
        output3 = self.channel3(x)
        output4 = self.channel4(x)
        output = torch.cat([output1, output2, output3, output4], dim=1)
        return output


class GoogLeNet(nn.Module):
    def __init__(self, num_classes: int):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=64, kernel_size=7, stride=2, padding=3), nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),

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

            IncepBlock(in_channels=192, c1_out=64, c2_out=(96, 128), c3_out=(16, 32), c4_out=32),
            IncepBlock(in_channels=256, c1_out=128, c2_out=(128, 192), c3_out=(32, 96), c4_out=64),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),

            IncepBlock(in_channels=480, c1_out=192, c2_out=(96, 208), c3_out=(16, 48), c4_out=64),
            IncepBlock(in_channels=512, c1_out=160, c2_out=(112, 224), c3_out=(24, 64), c4_out=64),
            IncepBlock(in_channels=512, c1_out=128, c2_out=(128, 256), c3_out=(24, 64), c4_out=64),
            IncepBlock(in_channels=512, c1_out=112, c2_out=(144, 288), c3_out=(32, 64), c4_out=64),
            IncepBlock(in_channels=528, c1_out=256, c2_out=(160, 320), c3_out=(32, 128), c4_out=128),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),

            IncepBlock(in_channels=832, c1_out=256, c2_out=(160, 320), c3_out=(32, 128), c4_out=128),
            IncepBlock(in_channels=832, c1_out=384, c2_out=(192, 384), c3_out=(48, 128), c4_out=128),
            nn.AdaptiveAvgPool2d(1), nn.Flatten(),
            nn.Linear(in_features=1024, out_features=num_classes)
        )

        self._initialize_weights()

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
                if m.bias is not None: nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                if m.bias is not None: nn.init.constant_(m.bias, 0)

    def forward(self, x) -> Tensor:
        return self.model(x)


if __name__ == '__main__':
    BATCH_SIZE = 128
    EPOCHS_NUM = 30
    LEARNING_RATE = 0.005

    model = GoogLeNet(num_classes=10)
    train_loader, test_loader = fashionMNIST_loader(BATCH_SIZE, resize=96)
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(model.parameters(), LEARNING_RATE)
    platform = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    with Trainer(model, train_loader, test_loader, criterion, optimizer, platform) as trainer:
        trainer.train(EPOCHS_NUM)

3.2 训练结果与分析

查看完整训练过程
第 001/30 轮,训练损失:1.0233,训练精度:64.77%,测试损失:0.5896,测试精度:78.30%
第 002/30 轮,训练损失:0.5145,训练精度:81.14%,测试损失:0.4663,测试精度:83.26%
第 003/30 轮,训练损失:0.4330,训练精度:84.10%,测试损失:0.4222,测试精度:84.27%
第 004/30 轮,训练损失:0.3915,训练精度:85.55%,测试损失:0.3902,测试精度:85.33%
第 005/30 轮,训练损失:0.3597,训练精度:86.72%,测试损失:0.6181,测试精度:78.45%
第 006/30 轮,训练损失:0.3360,训练精度:87.59%,测试损失:0.4024,测试精度:85.58%
第 007/30 轮,训练损失:0.3194,训练精度:88.19%,测试损失:0.3629,测试精度:86.59%
第 008/30 轮,训练损失:0.3041,训练精度:88.78%,测试损失:0.3193,测试精度:88.33%
第 009/30 轮,训练损失:0.2902,训练精度:89.14%,测试损失:0.3558,测试精度:86.62%
第 010/30 轮,训练损失:0.2797,训练精度:89.57%,测试损失:0.3258,测试精度:88.02%
第 011/30 轮,训练损失:0.2684,训练精度:90.09%,测试损失:0.2906,测试精度:89.48%
第 012/30 轮,训练损失:0.2612,训练精度:90.34%,测试损失:0.3176,测试精度:88.67%
第 013/30 轮,训练损失:0.2493,训练精度:90.71%,测试损失:0.2911,测试精度:89.44%
第 014/30 轮,训练损失:0.2429,训练精度:90.96%,测试损失:0.3492,测试精度:87.41%
第 015/30 轮,训练损失:0.2351,训练精度:91.34%,测试损失:0.3176,测试精度:88.10%
第 016/30 轮,训练损失:0.2292,训练精度:91.44%,测试损失:0.2931,测试精度:88.95%
第 017/30 轮,训练损失:0.2221,训练精度:91.71%,测试损失:0.3761,测试精度:86.24%
第 018/30 轮,训练损失:0.2123,训练精度:92.17%,测试损失:0.2816,测试精度:89.70%
第 019/30 轮,训练损失:0.2087,训练精度:92.14%,测试损失:0.3294,测试精度:88.39%
第 020/30 轮,训练损失:0.2000,训练精度:92.52%,测试损失:0.2823,测试精度:89.97%
第 021/30 轮,训练损失:0.1973,训练精度:92.78%,测试损失:0.2764,测试精度:90.12%
第 022/30 轮,训练损失:0.1918,训练精度:92.79%,测试损失:0.2800,测试精度:89.67%
第 023/30 轮,训练损失:0.1846,训练精度:93.20%,测试损失:0.2640,测试精度:90.43%
第 024/30 轮,训练损失:0.1796,训练精度:93.38%,测试损失:0.2875,测试精度:89.47%
第 025/30 轮,训练损失:0.1744,训练精度:93.61%,测试损失:0.2566,测试精度:90.57%
第 026/30 轮,训练损失:0.1676,训练精度:93.80%,测试损失:0.2848,测试精度:89.85%
第 027/30 轮,训练损失:0.1627,训练精度:94.03%,测试损失:0.2633,测试精度:90.86%
第 028/30 轮,训练损失:0.1585,训练精度:94.17%,测试损失:0.2793,测试精度:90.02%
第 029/30 轮,训练损失:0.1545,训练精度:94.38%,测试损失:0.2631,测试精度:90.83%
第 030/30 轮,训练损失:0.1463,训练精度:94.52%,测试损失:0.2876,测试精度:90.21%

GoogLeNet训练过程可视化


参考资料

如果这篇记录对你有帮助,可以留下一句回应。

前往留言

DISCUSSION

讨论与回应

欢迎补充细节、指出问题,或分享与这篇文章有关的经验。

昵称与邮箱为必填项,邮箱仅用于头像和回复通知,不会公开。网址可以留空。

正在准备留言区…

演示赞赏界面

谢谢你愿意支持长期写作。

第一阶段不会发起支付或收集任何信息。接入真实赞赏渠道后,这里会展示清楚的金额、渠道和完成状态。

输入关键词开始搜索 · 按 Esc 关闭
打开完整搜索页 →