上一篇我们讲了 AlexNet 和 VGG,它们都把“深度 CNN 真能做事”这件事证明得很扎实。

AlexNet 更像是一次突破:堆更深、更宽的卷积层,性能明显上去了。VGG 则把网络设计“模块化”:一组组 3x3 卷积 + 最大汇聚,像积木一样反复堆叠,结构清晰、好复用。

但网络越深、通道越多,问题也就越明显:

  1. 全连接层太重
    卷积层辛苦提特征,最后一展平接全连接层,参数量会瞬间爆炸,不仅训练慢,还特别吃显存。

  2. 卷积核大小很难选
    1x1、3x3、5x5 各有优点。小核抓细节,大核看范围,到底该选谁?只选一个尺度就容易漏掉另一种特征。

于是,今天的两个主角就登场了:

  • NiN(Network in Network,网络中的网络):用 1x1 卷积在“每个像素位置”做小型处理,再配合全局平均汇聚,削弱甚至替代笨重的全连接层。
  • GoogLeNet:用 Inception 块把多种卷积路线并行,让网络自己学会组合不同尺度的特征。

一、先认识 1x1 卷积:最小但很关键

在理解 NiN 和 GoogLeNet 之前,我们必须先把 1x1 卷积讲清楚,因为它是这两类网络里的“高频零件”。

很多小白第一次看到 1x1 卷积会疑惑:卷积核只有一个格子,它连旁边像素都看不到,有什么用?

答案是:

1x1 卷积不负责看空间邻居,它主要负责融合通道信息、改变通道数量。

换句话说,它不是在“看邻居”,而是在“调通道”。所以它虽然小,但很关键。

说明一下:本文统一用“通道”这个说法,你也可能在别的资料里看到它被称为“特征图数”或“feature maps”。本质都是指同一维度上的多张特征图。

假设某一层的输出形状是:

批量大小 x 64通道 x 高 x 宽

某个像素位置上,不是只有一个数字,而是有 64 个通道值。1x1 卷积会在同一个位置,把这 64 个数字加权组合成新的通道。

你可以把它理解成:

  • 空间位置不动(不看邻居);
  • 只在“通道维度”做一次线性组合,再加上非线性激活。

所以它像什么?

它像一个“站在每个像素点上的小型全连接层”,专门把通道信息重新混合。

如果输出通道数是 32,那么每个像素点上的 64 维向量,就会被变换成 32 维向量,这就是“降维”;反过来变成 128 维,就是“升维”。

举个更具体的小例子(只看一个像素点):

原来这个像素点有 3 个通道值: [2, 0, 1]
1x1 卷积想要输出 2 个通道

第 1 个输出通道 = 2*0.5 + 0*0.2 + 1*0.1 = 1.1
第 2 个输出通道 = 2*(-0.3) + 0*0.7 + 1*0.4 = -0.2

你可以看到,1x1 卷积做的其实就是“通道方向的加权组合”。

这里的权重没有“必须加起来等于 1”的要求,它们是训练中自动学习的参数,可以是任意实数(正的、负的都可以),还通常会带一个偏置项。只有在像注意力这类“归一化权重”的场景,才会特意让权重和为 1。

再用一个小示意图把过程画出来(只看一个像素点):

输入通道(3个):   [x1, x2, x3]
                 |   |   |
                 |   |   |   (权重矩阵)
                 v   v   v
输出通道(2个):   [y1, y2]

y1 = w11*x1 + w12*x2 + w13*x3
y2 = w21*x1 + w22*x2 + w23*x3

这说明 1x1 卷积的“过程结果”就是:在同一空间位置上,把多个通道线性组合成新的通道。

1x1 卷积常见作用有三个(也是它在现代 CNN 里最常见的三种“岗位”):

  1. 升维:把通道数变多,增强表达能力。
  2. 降维:把通道数变少,减少后续计算量。
  3. 通道融合:让不同通道之间产生新的组合。

这颗“小螺丝”,后面会反复出现,你会发现它是“省计算 + 增表达”的万能小工具。


二、NiN:把全连接层变轻

1. 全连接层为什么麻烦?

传统 CNN 常见结构是:

卷积层 -> 汇聚层 -> 卷积层 -> 汇聚层 -> 展平 -> 全连接层 -> 输出

问题就出在“展平 -> 全连接层”。

展平之后,本来是一个 C×H×WC\times H\times WC×H×W 的三维特征图,直接被拉成一条超长向量。然后再接全连接层,就会产生大量参数。

简单画一下这个过程:

特征图(C x H x W)
   |  展平
   v
超长向量(长度 = C*H*W)
   |  全连接
   v
分类输出

结果就是:只要 C、H、WC、H、WCHW 稍微大一点,全连接层的参数量就会急剧增长。

假设卷积层最后输出:

512通道 x 7高 x 7宽

一展平就是:

512 x 7 x 7 = 25088

如果再接一个 4096 神经元的全连接层,参数量大约是:

25088 x 4096 ≈ 1亿

这还只是其中一层。参数多会带来三个麻烦:

  • 训练慢;
  • 占显存;
  • 容易过拟合(尤其是数据集不够大时)。

NiN 的思路就是:能不能少用这种庞大的全连接层?甚至让它“隐身”,只用卷积和汇聚完成分类?

2. NiN 块:卷积后面接小网络

NiN 的全称是 Network in Network,直译就是“网络中的网络”。

它的想法是:普通卷积层只是在局部区域里提取特征,但每个位置上不同通道之间的组合也很重要。那就在普通卷积后面,加上几个 1x1 卷积,让每个像素位置都拥有一个“小型网络”来处理通道信息。

你可以把一个 NiN 块理解成:

  • 先用常规卷积“看空间”;
  • 再用 1x1 卷积“拌通道”;
  • 把通道混合得更聪明。

用流程图直观表示就是:

输入特征图

普通卷积 kxk

ReLU

1x1 卷积

ReLU

1x1 卷积

ReLU

输出特征图

这个流程的结果是:既保留空间信息,又在通道维度上做更复杂的组合。

代码如下:

from torch import nn

def nin_block(in_channels, out_channels, kernel_size, strides, padding):
    # NiN 块:一个普通卷积 + 两个 1x1 卷积
    # 普通卷积负责提取局部空间特征,后面的 1x1 卷积负责通道混合与非线性变换
    return nn.Sequential(
   # 主卷积:决定感受野大小、步幅与边界填充
        nn.Conv2d(in_channels, out_channels,
                  kernel_size, stride=strides, padding=padding),
        nn.ReLU(),
   # 1x1 卷积:在每个像素位置上做通道融合,等价于位置上的小型全连接层
        nn.Conv2d(out_channels, out_channels, kernel_size=1),
        nn.ReLU(),
   # 再叠一层 1x1 卷积:进一步增加通道间的非线性组合能力
        nn.Conv2d(out_channels, out_channels, kernel_size=1),
        nn.ReLU()
    )

你可以把这个块理解成:

  1. 先用普通卷积看局部空间;
  2. 再用两个 1x1 卷积混合通道;
  3. 每一步后面都接 ReLU,增加非线性表达能力。

这样做的好处是:空间信息和通道信息都能被充分处理,但参数量又不会像大规模全连接层那样“爆炸”。

3. 全局平均汇聚:不再暴力展平

NiN 还有一个非常重要的设计:全局平均汇聚(Global Average Pooling)

传统做法:

最后特征图 -> 展平 -> 全连接层 -> 类别分数

NiN 的做法:

最后特征图的通道数 = 类别数 -> 每个通道求平均 -> 类别分数

举个例子,假设要做 10 分类。NiN 让最后一层卷积直接输出 10 个通道:

  • 第 1 个通道负责“类别 1”的证据;
  • 第 2 个通道负责“类别 2”的证据;
  • 第 10 个通道负责“类别 10”的证据。

每个通道可能还是一张小图,比如 5x5。全局平均汇聚就把每张小图求一个平均值,得到 10 个数,正好对应 10 个类别。

这一步的直觉是:

  • 如果某个通道整体都“亮”,说明它的类别证据强;
  • 平均一下,就得到一个稳定的类别分数。

而且这种做法几乎不引入额外参数,对过拟合更友好。

nn.AdaptiveAvgPool2d((1, 1))

这行代码的意思是:不管输入特征图的高宽是多少,都平均汇聚成 1x1。

你可以把它理解成“把一整张特征图压成一个代表值”。

举个更直观的小例子(只看一个通道):

输入特征图是 2x2:
[[1, 3],
 [5, 7]]

全局平均汇聚结果 = (1 + 3 + 5 + 7) / 4 = 4

这说明它并不关心空间位置细节,而是只保留“整体强度”。

再用一个小示意图表示它的过程和结果:

通道内的特征图(5x5)  -->  求平均  -->  一个数(该通道的分数)

4. NiN 的入门记忆法

NiN 的核心可以记成两句话:

用 1x1 卷积在每个像素位置上做通道融合。
用全局平均汇聚减少对大型全连接层的依赖。

它让 CNN 变得更轻,也启发了后面很多网络设计,比如后来几乎所有现代 CNN 都会用到全局平均汇聚的思路。


三、GoogLeNet:不同卷积路线一起上

VGG 的思路很统一:几乎都用 3x3 卷积。

但真实图片很复杂。有些特征很小,比如边缘、纹理;有些特征更大,比如眼睛、轮子、脸部轮廓。不同大小的卷积核,擅长看的范围不一样。

如果只用一种卷积核,你就像只拿一种“放大镜”去看世界,细节可能很清楚,但整体结构又看不全。

那到底该用哪个?

GoogLeNet 的回答很直接:

不要只选一个,让它们并行工作。

这就是 Inception 块

它的核心思想是:同一层里开多条路,让网络自己学会拼不同尺度的特征。

1. Inception 块的四条路线

一个 Inception 块里,输入会被分成四条路线:

  1. 1x1 卷积路线
    负责快速做通道变换。

  2. 1x1 卷积 + 3x3 卷积路线
    先降维,再看中等范围。

  3. 1x1 卷积 + 5x5 卷积路线
    先降维,再看更大范围。

  4. 最大汇聚 + 1x1 卷积路线
    先提取强特征,再调整通道数。

四条路线的输出高宽保持一致,最后在通道维度上拼接起来。

可以把它想成“四个小专家各自做一份报告,最后把报告合并”。

用一张并行流程图表示:

输入特征图

1x1 卷积

1x1 卷积

3x3 卷积

1x1 卷积

5x5 卷积

3x3 最大汇聚

1x1 卷积

通道拼接

输出特征图

结果就是:同一层同时看到小范围、中范围、大范围的特征,再把它们合在一起。

import torch
from torch import nn
from torch.nn import functional as F

class Inception(nn.Module):
    def __init__(self, in_channels, c1, c2, c3, c4):
        super().__init__()

        # 路线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:先最大汇聚提强特征,再用 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)))

        # dim=1 表示在通道维度拼接
        return torch.cat((p1, p2, p3, p4), dim=1)

这段代码看起来比 VGG 复杂,但主线很清楚:

同一个输入,走四条不同路线;每条路线看问题的角度不同;最后把结果合并。

换个说法:网络不需要在“选一个卷积核”,而是直接把多个尺度都用上,再让自己学会如何组合。

2. 为什么路线里经常先放 1x1 卷积?

如果直接对很多通道做 5x5 卷积,计算量会很大。GoogLeNet 在 3x3 和 5x5 卷积前面加 1x1 卷积,常常是为了先减少通道数。

可以直观理解成:先把“厚厚的一摞书”压薄,再让大卷积核去翻。这样既保留信息,又不会让计算量爆炸。

给一个具体的“参数量对比”小例子:

假设输入通道 = 128,输出通道 = 32

直接 5x5 卷积参数量:
5*5*128*32 = 102,400

先 1x1 降维到 16 通道,再做 5x5:
1*1*128*16 = 2,048
5*5*16*32  = 12,800
总计       = 14,848

结果非常直观:先降维再卷积,参数量大幅减少。

举个直观例子:

512通道 -> 先用1x1压到32通道 -> 再做5x5卷积

这样后面的 5x5 卷积就轻很多。

所以,GoogLeNet 里的 1x1 卷积很像一个“中转站”:

  • 先把厚厚的通道压薄;
  • 再交给大卷积核处理;
  • 最后把不同路线的结果拼起来。

3. GoogLeNet 的价值

GoogLeNet 的厉害之处在于:它很深,但参数量并不夸张。

它综合使用了几个关键思想:

  • 多路线并行,提取不同尺度特征;
  • 1x1 卷积降维,减少计算量;
  • 借鉴全局平均汇聚,减少大型全连接层。

它告诉我们:

网络不一定只能一层接一层往下排,也可以在同一层里分出多条路,再把结果合并。


四、NiN 和 GoogLeNet 对比

网络 重点设计 解决的问题 小白记忆
NiN 1x1 卷积 + 全局平均汇聚 全连接层太重 用小网络替代大尾巴
GoogLeNet Inception 多路线并行 卷积核大小难选择 1x1、3x3、5x5 一起上

这两个网络都体现了一个变化:

CNN 不再只是简单地“卷积、汇聚、全连接”,而是开始认真设计网络内部的信息流动方式。

对新手来说,可以把它理解成:以前只是“堆层数”,现在开始“讲策略”。

再用一个生活化的对比小例子:

如果你关心“轻量、省参数” -> 更像 NiN
如果你关心“多尺度、多视角” -> 更像 GoogLeNet

五、小结

这一篇你需要记住四件事:

  1. 1x1 卷积虽然不看邻居像素,但能融合通道、升降维、减少计算量,是现代 CNN 的高频工具。
  2. NiN 用 1x1 卷积增强局部表达,用全局平均汇聚减少全连接层,让网络更轻、更不容易过拟合。
  3. GoogLeNet 的 Inception 块让多种卷积路线并行,自动覆盖不同尺度的特征。
  4. 现代 CNN 的设计重点,逐渐从“堆更多层”变成“让信息更聪明地流动”。

如果用一句话收尾:

NiN 让网络变轻,GoogLeNet 让网络变聪明。

下一篇我们要面对一个更深层的问题:网络越深真的越好吗?为什么有时加深之后反而更难训练?这就要轮到 批量规范化(Batch Normalization)ResNet 出场了。

(注:文档部分内容参考《动手学深度学习》)
《动手学深度学习》现代卷积神经网络:https://zh.d2l.ai/chapter_convolutional-modern/index.html

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐