上一篇我们讲了 AlexNet 和 VGG,它们都把“深度 CNN 真能做事”这件事证明得很扎实。
AlexNet 更像是一次突破:堆更深、更宽的卷积层,性能明显上去了。VGG 则把网络设计“模块化”:一组组 3×3 卷积 + 最大汇聚,像积木一样反复堆叠,结构清晰、好复用。
但网络越深、通道越多,问题也就越明显:
全连接层太重
卷积层辛苦提特征,最后一展平接全连接层,参数量会瞬间爆炸,不仅训练慢,还特别吃显存。
卷积核大小很难选
1×1、3×3、5×5 各有优点。小核抓细节,大核看范围,到底该选谁?只选一个尺度就容易漏掉另一种特征。
于是,今天的两个主角就登场了:
- NiN(Network in Network,网络中的网络):用 1×1 卷积在“每个像素位置”做小型处理,再配合全局平均汇聚,削弱甚至替代笨重的全连接层。
- GoogLeNet:用 Inception 块把多种卷积路线并行,让网络自己学会组合不同尺度的特征。
一、先认识 1×1 卷积:最小但很关键
在理解 NiN 和 GoogLeNet 之前,我们必须先把 1×1 卷积讲清楚,因为它是这两类网络里的“高频零件”。
很多小白第一次看到 1×1 卷积会疑惑:卷积核只有一个格子,它连旁边像素都看不到,有什么用?
答案是:
1×1 卷积不负责看空间邻居,它主要负责融合通道信息、改变通道数量。
换句话说,它不是在“看邻居”,而是在“调通道”。所以它虽然小,但很关键。
说明一下:本文统一用“通道”这个说法,你也可能在别的资料里看到它被称为“特征图数”或“feature maps”。本质都是指同一维度上的多张特征图。
假设某一层的输出形状是:
批量大小 x 64通道 x 高 x 宽
某个像素位置上,不是只有一个数字,而是有 64 个通道值。1×1 卷积会在同一个位置,把这 64 个数字加权组合成新的通道。
你可以把它理解成:
- 空间位置不动(不看邻居);
- 只在“通道维度”做一次线性组合,再加上非线性激活。
所以它像什么?
它像一个“站在每个像素点上的小型全连接层”,专门把通道信息重新混合。
如果输出通道数是 32,那么每个像素点上的 64 维向量,就会被变换成 32 维向量,这就是“降维”;反过来变成 128 维,就是“升维”。
举个更具体的小例子(只看一个像素点):
原来这个像素点有 3 个通道值: [2, 0, 1]
1×1 卷积想要输出 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
你可以看到,1×1 卷积做的其实就是“通道方向的加权组合”。
这里的权重没有“必须加起来等于 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
这说明 1×1 卷积的“过程结果”就是:在同一空间位置上,把多个通道线性组合成新的通道。
1×1 卷积常见作用有三个(也是它在现代 CNN 里最常见的三种“岗位”):
这颗“小螺丝”,后面会反复出现,你会发现它是“省计算 + 增表达”的万能小工具。
二、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、WC、H、W 稍微大一点,全连接层的参数量就会急剧增长。
假设卷积层最后输出:
512通道 x 7高 x 7宽
一展平就是:
512 x 7 x 7 = 25088
如果再接一个 4096 神经元的全连接层,参数量大约是:
25088 x 4096 ≈ 1亿
这还只是其中一层。参数多会带来三个麻烦:
- 训练慢;
- 占显存;
- 容易过拟合(尤其是数据集不够大时)。
NiN 的思路就是:能不能少用这种庞大的全连接层?甚至让它“隐身”,只用卷积和汇聚完成分类?
2. NiN 块:卷积后面接小网络
NiN 的全称是 Network in Network,直译就是“网络中的网络”。
它的想法是:普通卷积层只是在局部区域里提取特征,但每个位置上不同通道之间的组合也很重要。那就在普通卷积后面,加上几个 1×1 卷积,让每个像素位置都拥有一个“小型网络”来处理通道信息。
你可以把一个 NiN 块理解成:
- 先用常规卷积“看空间”;
- 再用 1×1 卷积“拌通道”;
- 把通道混合得更聪明。
用流程图直观表示就是:
#mermaid-svg-2DmzKZC4uFPBx7z9{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2DmzKZC4uFPBx7z9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2DmzKZC4uFPBx7z9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2DmzKZC4uFPBx7z9 .error-icon{fill:#552222;}#mermaid-svg-2DmzKZC4uFPBx7z9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2DmzKZC4uFPBx7z9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2DmzKZC4uFPBx7z9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2DmzKZC4uFPBx7z9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2DmzKZC4uFPBx7z9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2DmzKZC4uFPBx7z9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2DmzKZC4uFPBx7z9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2DmzKZC4uFPBx7z9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2DmzKZC4uFPBx7z9 .marker.cross{stroke:#333333;}#mermaid-svg-2DmzKZC4uFPBx7z9 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2DmzKZC4uFPBx7z9 p{margin:0;}#mermaid-svg-2DmzKZC4uFPBx7z9 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-2DmzKZC4uFPBx7z9 .cluster-label text{fill:#333;}#mermaid-svg-2DmzKZC4uFPBx7z9 .cluster-label span{color:#333;}#mermaid-svg-2DmzKZC4uFPBx7z9 .cluster-label span p{background-color:transparent;}#mermaid-svg-2DmzKZC4uFPBx7z9 .label text,#mermaid-svg-2DmzKZC4uFPBx7z9 span{fill:#333;color:#333;}#mermaid-svg-2DmzKZC4uFPBx7z9 .node rect,#mermaid-svg-2DmzKZC4uFPBx7z9 .node circle,#mermaid-svg-2DmzKZC4uFPBx7z9 .node ellipse,#mermaid-svg-2DmzKZC4uFPBx7z9 .node polygon,#mermaid-svg-2DmzKZC4uFPBx7z9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2DmzKZC4uFPBx7z9 .rough-node .label text,#mermaid-svg-2DmzKZC4uFPBx7z9 .node .label text,#mermaid-svg-2DmzKZC4uFPBx7z9 .image-shape .label,#mermaid-svg-2DmzKZC4uFPBx7z9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-2DmzKZC4uFPBx7z9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2DmzKZC4uFPBx7z9 .rough-node .label,#mermaid-svg-2DmzKZC4uFPBx7z9 .node .label,#mermaid-svg-2DmzKZC4uFPBx7z9 .image-shape .label,#mermaid-svg-2DmzKZC4uFPBx7z9 .icon-shape .label{text-align:center;}#mermaid-svg-2DmzKZC4uFPBx7z9 .node.clickable{cursor:pointer;}#mermaid-svg-2DmzKZC4uFPBx7z9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2DmzKZC4uFPBx7z9 .arrowheadPath{fill:#333333;}#mermaid-svg-2DmzKZC4uFPBx7z9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2DmzKZC4uFPBx7z9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2DmzKZC4uFPBx7z9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2DmzKZC4uFPBx7z9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2DmzKZC4uFPBx7z9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2DmzKZC4uFPBx7z9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2DmzKZC4uFPBx7z9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2DmzKZC4uFPBx7z9 .cluster text{fill:#333;}#mermaid-svg-2DmzKZC4uFPBx7z9 .cluster span{color:#333;}#mermaid-svg-2DmzKZC4uFPBx7z9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2DmzKZC4uFPBx7z9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2DmzKZC4uFPBx7z9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-2DmzKZC4uFPBx7z9 .icon-shape,#mermaid-svg-2DmzKZC4uFPBx7z9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2DmzKZC4uFPBx7z9 .icon-shape p,#mermaid-svg-2DmzKZC4uFPBx7z9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2DmzKZC4uFPBx7z9 .icon-shape .label rect,#mermaid-svg-2DmzKZC4uFPBx7z9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2DmzKZC4uFPBx7z9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2DmzKZC4uFPBx7z9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2DmzKZC4uFPBx7z9 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
输入特征图
普通卷积 kxk
ReLU
1×1 卷积
ReLU
1×1 卷积
ReLU
输出特征图
这个流程的结果是:既保留空间信息,又在通道维度上做更复杂的组合。
代码如下:
from torch import nn
def nin_block(in_channels, out_channels, kernel_size, strides, padding):
# NiN 块:一个普通卷积 + 两个 1×1 卷积
# 普通卷积负责提取局部空间特征,后面的 1×1 卷积负责通道混合与非线性变换
return nn.Sequential(
# 主卷积:决定感受野大小、步幅与边界填充
nn.Conv2d(in_channels, out_channels,
kernel_size, stride=strides, padding=padding),
nn.ReLU(),
# 1×1 卷积:在每个像素位置上做通道融合,等价于位置上的小型全连接层
nn.Conv2d(out_channels, out_channels, kernel_size=1),
nn.ReLU(),
# 再叠一层 1×1 卷积:进一步增加通道间的非线性组合能力
nn.Conv2d(out_channels, out_channels, kernel_size=1),
nn.ReLU()
)
你可以把这个块理解成:
这样做的好处是:空间信息和通道信息都能被充分处理,但参数量又不会像大规模全连接层那样“爆炸”。
3. 全局平均汇聚:不再暴力展平
NiN 还有一个非常重要的设计:全局平均汇聚(Global Average Pooling)。
传统做法:
最后特征图 -> 展平 -> 全连接层 -> 类别分数
NiN 的做法:
最后特征图的通道数 = 类别数 -> 每个通道求平均 -> 类别分数
举个例子,假设要做 10 分类。NiN 让最后一层卷积直接输出 10 个通道:
- 第 1 个通道负责“类别 1”的证据;
- 第 2 个通道负责“类别 2”的证据;
- …
- 第 10 个通道负责“类别 10”的证据。
每个通道可能还是一张小图,比如 5×5。全局平均汇聚就把每张小图求一个平均值,得到 10 个数,正好对应 10 个类别。
这一步的直觉是:
- 如果某个通道整体都“亮”,说明它的类别证据强;
- 平均一下,就得到一个稳定的类别分数。
而且这种做法几乎不引入额外参数,对过拟合更友好。
nn.AdaptiveAvgPool2d((1, 1))
这行代码的意思是:不管输入特征图的高宽是多少,都平均汇聚成 1×1。
你可以把它理解成“把一整张特征图压成一个代表值”。
举个更直观的小例子(只看一个通道):
输入特征图是 2×2:
[[1, 3],
[5, 7]]
全局平均汇聚结果 = (1 + 3 + 5 + 7) / 4 = 4
这说明它并不关心空间位置细节,而是只保留“整体强度”。
再用一个小示意图表示它的过程和结果:
通道内的特征图(5×5) –> 求平均 –> 一个数(该通道的分数)
4. NiN 的入门记忆法
NiN 的核心可以记成两句话:
用 1×1 卷积在每个像素位置上做通道融合。
用全局平均汇聚减少对大型全连接层的依赖。
它让 CNN 变得更轻,也启发了后面很多网络设计,比如后来几乎所有现代 CNN 都会用到全局平均汇聚的思路。
三、GoogLeNet:不同卷积路线一起上
VGG 的思路很统一:几乎都用 3×3 卷积。
但真实图片很复杂。有些特征很小,比如边缘、纹理;有些特征更大,比如眼睛、轮子、脸部轮廓。不同大小的卷积核,擅长看的范围不一样。
如果只用一种卷积核,你就像只拿一种“放大镜”去看世界,细节可能很清楚,但整体结构又看不全。
那到底该用哪个?
GoogLeNet 的回答很直接:
不要只选一个,让它们并行工作。
这就是 Inception 块。
它的核心思想是:同一层里开多条路,让网络自己学会拼不同尺度的特征。
1. Inception 块的四条路线
一个 Inception 块里,输入会被分成四条路线:
1×1 卷积路线
负责快速做通道变换。
1×1 卷积 + 3×3 卷积路线
先降维,再看中等范围。
1×1 卷积 + 5×5 卷积路线
先降维,再看更大范围。
最大汇聚 + 1×1 卷积路线
先提取强特征,再调整通道数。
四条路线的输出高宽保持一致,最后在通道维度上拼接起来。
可以把它想成“四个小专家各自做一份报告,最后把报告合并”。
用一张并行流程图表示:
#mermaid-svg-HvprAXUxbdGnrdDp{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HvprAXUxbdGnrdDp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HvprAXUxbdGnrdDp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HvprAXUxbdGnrdDp .error-icon{fill:#552222;}#mermaid-svg-HvprAXUxbdGnrdDp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HvprAXUxbdGnrdDp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HvprAXUxbdGnrdDp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HvprAXUxbdGnrdDp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HvprAXUxbdGnrdDp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HvprAXUxbdGnrdDp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HvprAXUxbdGnrdDp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HvprAXUxbdGnrdDp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HvprAXUxbdGnrdDp .marker.cross{stroke:#333333;}#mermaid-svg-HvprAXUxbdGnrdDp svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HvprAXUxbdGnrdDp p{margin:0;}#mermaid-svg-HvprAXUxbdGnrdDp .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-HvprAXUxbdGnrdDp .cluster-label text{fill:#333;}#mermaid-svg-HvprAXUxbdGnrdDp .cluster-label span{color:#333;}#mermaid-svg-HvprAXUxbdGnrdDp .cluster-label span p{background-color:transparent;}#mermaid-svg-HvprAXUxbdGnrdDp .label text,#mermaid-svg-HvprAXUxbdGnrdDp span{fill:#333;color:#333;}#mermaid-svg-HvprAXUxbdGnrdDp .node rect,#mermaid-svg-HvprAXUxbdGnrdDp .node circle,#mermaid-svg-HvprAXUxbdGnrdDp .node ellipse,#mermaid-svg-HvprAXUxbdGnrdDp .node polygon,#mermaid-svg-HvprAXUxbdGnrdDp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HvprAXUxbdGnrdDp .rough-node .label text,#mermaid-svg-HvprAXUxbdGnrdDp .node .label text,#mermaid-svg-HvprAXUxbdGnrdDp .image-shape .label,#mermaid-svg-HvprAXUxbdGnrdDp .icon-shape .label{text-anchor:middle;}#mermaid-svg-HvprAXUxbdGnrdDp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HvprAXUxbdGnrdDp .rough-node .label,#mermaid-svg-HvprAXUxbdGnrdDp .node .label,#mermaid-svg-HvprAXUxbdGnrdDp .image-shape .label,#mermaid-svg-HvprAXUxbdGnrdDp .icon-shape .label{text-align:center;}#mermaid-svg-HvprAXUxbdGnrdDp .node.clickable{cursor:pointer;}#mermaid-svg-HvprAXUxbdGnrdDp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HvprAXUxbdGnrdDp .arrowheadPath{fill:#333333;}#mermaid-svg-HvprAXUxbdGnrdDp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HvprAXUxbdGnrdDp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HvprAXUxbdGnrdDp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HvprAXUxbdGnrdDp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HvprAXUxbdGnrdDp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HvprAXUxbdGnrdDp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HvprAXUxbdGnrdDp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HvprAXUxbdGnrdDp .cluster text{fill:#333;}#mermaid-svg-HvprAXUxbdGnrdDp .cluster span{color:#333;}#mermaid-svg-HvprAXUxbdGnrdDp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HvprAXUxbdGnrdDp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HvprAXUxbdGnrdDp rect.text{fill:none;stroke-width:0;}#mermaid-svg-HvprAXUxbdGnrdDp .icon-shape,#mermaid-svg-HvprAXUxbdGnrdDp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HvprAXUxbdGnrdDp .icon-shape p,#mermaid-svg-HvprAXUxbdGnrdDp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HvprAXUxbdGnrdDp .icon-shape .label rect,#mermaid-svg-HvprAXUxbdGnrdDp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HvprAXUxbdGnrdDp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HvprAXUxbdGnrdDp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HvprAXUxbdGnrdDp :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
输入特征图
1×1 卷积
1×1 卷积
3×3 卷积
1×1 卷积
5×5 卷积
3×3 最大汇聚
1×1 卷积
通道拼接
输出特征图
结果就是:同一层同时看到小范围、中范围、大范围的特征,再把它们合在一起。
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:1×1 卷积,快速做通道变换
self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
# 路线2:先 1×1 降维,再用 3×3 看中等范围
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:先 1×1 降维,再用 5×5 看更大范围
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:先最大汇聚提强特征,再用 1×1 调整通道数
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. 为什么路线里经常先放 1×1 卷积?
如果直接对很多通道做 5×5 卷积,计算量会很大。GoogLeNet 在 3×3 和 5×5 卷积前面加 1×1 卷积,常常是为了先减少通道数。
可以直观理解成:先把“厚厚的一摞书”压薄,再让大卷积核去翻。这样既保留信息,又不会让计算量爆炸。
给一个具体的“参数量对比”小例子:
假设输入通道 = 128,输出通道 = 32
直接 5×5 卷积参数量:
5*5*128*32 = 102,400
先 1×1 降维到 16 通道,再做 5×5:
1*1*128*16 = 2,048
5*5*16*32 = 12,800
总计 = 14,848
结果非常直观:先降维再卷积,参数量大幅减少。
举个直观例子:
512通道 -> 先用1×1压到32通道 -> 再做5×5卷积
这样后面的 5×5 卷积就轻很多。
所以,GoogLeNet 里的 1×1 卷积很像一个“中转站”:
- 先把厚厚的通道压薄;
- 再交给大卷积核处理;
- 最后把不同路线的结果拼起来。
3. GoogLeNet 的价值
GoogLeNet 的厉害之处在于:它很深,但参数量并不夸张。
它综合使用了几个关键思想:
- 多路线并行,提取不同尺度特征;
- 1×1 卷积降维,减少计算量;
- 借鉴全局平均汇聚,减少大型全连接层。
它告诉我们:
网络不一定只能一层接一层往下排,也可以在同一层里分出多条路,再把结果合并。
四、NiN 和 GoogLeNet 对比
| NiN | 1×1 卷积 + 全局平均汇聚 | 全连接层太重 | 用小网络替代大尾巴 |
| GoogLeNet | Inception 多路线并行 | 卷积核大小难选择 | 1×1、3×3、5×5 一起上 |
这两个网络都体现了一个变化:
CNN 不再只是简单地“卷积、汇聚、全连接”,而是开始认真设计网络内部的信息流动方式。
对新手来说,可以把它理解成:以前只是“堆层数”,现在开始“讲策略”。
再用一个生活化的对比小例子:
如果你关心“轻量、省参数” -> 更像 NiN
如果你关心“多尺度、多视角” -> 更像 GoogLeNet
五、小结
这一篇你需要记住四件事:
如果用一句话收尾:
NiN 让网络变轻,GoogLeNet 让网络变聪明。
下一篇我们要面对一个更深层的问题:网络越深真的越好吗?为什么有时加深之后反而更难训练?这就要轮到 批量规范化(Batch Normalization) 和 ResNet 出场了。
(注:文档部分内容参考《动手学深度学习》)
《动手学深度学习》现代卷积神经网络:https://zh.d2l.ai/chapter_convolutional-modern/index.html

