文章目录
1 LeNet (1989)
1.1 背景与起源
LeNet 是卷积神经网络(CNN)历史上最负盛名的网络之一,由 杨立昆 (Yann LeCun) 教授在 20世纪80年代末 提出。它的出现并非纯粹的学术研究,而是为了解决当时非常具体的商业应用问题。
1. 核心应用场景:
- 手写邮政编码识别:在美国,邮政系统是一个庞大的政府机构。为了实现信件分拣自动化,美国邮政局希望能自动识别信封上手写的邮政编码,从而将信件快速、准确地分发到对应的区域。
- 银行支票(Check)数字识别:即便在今天,支票在美国的日常支付(如付学费、个人间转账)中仍被广泛使用。当用户将支票存入 ATM 机时,机器需要自动识别支票上的手写金额,以完成存取款操作。这个需求推动了 AT&T(当时美国一个巨大的电信机构)资助并参与了这项研究。

2. 历史意义:
- LeNet 是最早被成功大规模商业部署的神经网络模型之一,在80年代末到90年代初,它已经被广泛应用于美国的银行和邮政系统。
- 这段成功的商业应用历史,证明了神经网络在解决实际图像识别问题上的巨大潜力,也使得 LeNet 成为深度学习发展史上的一个里程碑。
1.2 MNIST 数据集
在提出 LeNet 网络的同时,杨立昆团队也构建并发布了著名的 MNIST 数据集,它至今仍是许多人学习机器学习 的入门数据集。
- 数据集构成:
- 训练集: 50,000 张手写数字图片。
- 测试集: 10,000 张手写数字图片。
- 图片规格:
- 尺寸: 28×28 像素。这是一个经过预处理的、相对简单的数据集,数字基本都被缩放并放置在了图片的中央。
- 颜色: 灰度图(Grayscale),可以看作是黑白图像。
- 类别: 共 10 类,对应数字 0 到 9。
- 时代背景下的重要性:
- 在80年代末,计算机内存只有几兆(MB)的背景下,拥有数万张图片的数据集是名副其实的 “大数据” (Big Data)。
- LeNet 在 MNIST 上的成功,向当时的学术界和工业界展示了神经网络处理 “大数据” 的能力,其效果优于当时流行的支持向量机(SVM)、随机森林(Random Forest)等其他机器学习算法。

1.3 LeNet 网络架构
LeNet 的核心思想是 “卷积层提取空间信息,池化层降低敏感度,全连接层完成分类”。它奠定了现代卷积神经网络的基本结构:卷积 -> 池化 -> 卷积 -> 池化 -> 全连接 -> 输出。

1. 输入层 (Input)
- 输入一张 32×32 的灰度图像。
- 注意:原始 MNIST 图像是 28×28,这里增加内边距(Padding)到 32×32 是为了让后续的卷积操作更方便。
2. C1层:第一个卷积层 (Convolutional Layer)
- 操作: 使用 6 个 5×5 的卷积核对输入图像进行卷积。
- 输出: 得到 6 个 28×28 的特征图(Feature Map)。
- 目的: 提取图像中的初步边缘、角点等底层特征。
3. S2层:第一个池化层 (Pooling/Subsampling Layer)
- 操作: 对 C1 层的输出进行 2×2 的池化操作。
- 输出: 特征图尺寸减半,从 28×28 变为 14×14。通道数不变,仍为 6。
- 目的:
- 降低数据维度,减少计算量。
- 提升对微小位移、形变的容忍度(降低敏感度)。
4. C3层:第二个卷积层 (Convolutional Layer)
- 操作: 使用 16 个 5×5 的卷积核对 S2 层的输出进行卷积。
- 输出: 得到 16 个 10×10 的特征图。
- 目的: 在已提取的低层特征基础上,组合出更复杂、更抽象的特征。
5. S4层:第二个池化层 (Pooling/Subsampling Layer)
- 操作: 再次进行 2×2 的池化。
- 输出: 特征图尺寸再次减半,从 10×10 变为 5×5。通道数不变,为 16。
6. 展平 (Flatten)
- 将 S4 层输出的 5×5×16 的三维特征图,拉伸成一个一维向量,以便输入到全连接层。
7. C5/F5层:第一个全连接层 (Fully Connected Layer)
- 操作: 将拉伸后的向量输入到一个有 120 个神经元的全连接层。
- 输出: 一个长度为 120 的向量。
8. F6层:第二个全连接层 (Fully Connected Layer)
- 操作: 将 C5 层的 120 维向量输入到一个有 64 个神经元的全连接层。
- 输出: 一个长度为 64 的向量。
9. 输出层 (Output Layer)
- 操作: F6 层的输出连接到一个有 10 个神经元的输出层。当时使用的是“高斯连接 (Gaussian Connections)”,但在现代实践中,这等价于一个 Softmax 层。
- 输出: 得到一个包含 10 个值的向量,每个值代表输入图像属于对应数字(0-9)的概率。
1.4 代码实现
import torch
from torch import nn
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10))
X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape: \t',X.shape)

2 AlexNet (2012)
2.1 时代背景
在 AlexNet (2012年) 出现之前,机器学习和计算机视觉领域由其他范式主导。
1. 主流机器学习方法(~2000-2010年):核方法 (Kernel Methods)
- 代表: 支持向量机 (SVM)。
- 核心流程:
- 人工特征提取: 从原始数据中抽取出有意义的特征。
- 核函数: 通过核函数(如线性核、高斯核)在高维空间中计算样本间的相关性。
- 凸优化: 将问题转化为一个凸优化问题,有完美的数学理论支撑,可以找到全局最优解。
- 优点: 理论优美、结果稳定、可解释性强。

2. 主流计算机视觉方法:几何模型与特征工程
- 核心思想: 将视觉问题描述为几何问题(例如,通过多个相机位置还原三维场景),并依赖精巧的物理模型假设。

- 成功的关键: 特征工程 (Feature Engineering)。研究者们的绝大部分精力都投入到如何为特定问题设计出最优的特征提取算法上,例如著名的 SIFT 特征。
- 流程: 原始图片 -> 人工设计的特征提取器 (如SIFT) -> 简单的分类模型 (如SVM)。
- 结论: 在这个时代,特征的好坏远比分类模型本身更重要。专家的领域知识主要体现在特征提取环节。

AlexNet 的成功并非偶然,而是数据量、硬件算力和算法三者发展到特定阶段的必然结果。
- 数据 (Data): 数据样本量呈指数级增长。
- 算力 (Computation): GPU 的兴起 使得近十年算力的增长速度超越了数据量的增长速度。
- 算法 (Algorithm): 不同时代,数据和算力的发展水平决定了最适合的算法范式。
- 90年代 (LeNet): 数据和算力都有限,参数量小、内存要求不高的卷积网络(使用随机梯度下降)是可行的选择。
- 2000年代 (SVM): 算力和内存增长,足以支撑核方法在当时数据规模上的计算(例如计算整个核矩阵)。
- 2010年代 (AlexNet): 算力极大丰富。研究者可以用海量的计算来构建更深、更复杂的神经网络,以此“用计算换精度”,从大规模数据中挖掘更深层的信息。
历史的演进,是数据、硬件和算法三者在不同发展阶段相互作用的结果。现在,我们正处在算力充裕,适合大规模神经网络的时代。

2.3 ImageNet 数据集
ImageNet 是点燃深度学习革命的直接导火索。
- 背景: 由李飞飞、李凯等教授在2010年左右构建。
- 与 MNIST 的巨大差异:
- 复杂度: 自然场景下的彩色真实物体图片 vs. 居中、标准化的黑白手写数字。
- 图片尺寸: 平均 469×387 像素,尺寸不一 vs. 固定的 28×28 像素。
- 类别数量: 1000个 类别 vs. 10个类别。
- 样本数量: 用于竞赛的部分包含 120万 个训练样本 vs. 5万个。
- 意义: ImageNet 的巨大规模和复杂性,为像 AlexNet 这样深层、庞大的神经网络提供了前所未有的训练平台和用武之地。

2.4 AlexNet 架构

1. 核心思想:一个更深、更胖的 LeNet
AlexNet 在基本架构上延续了 LeNet 的思想,但通过加深网络层次、加宽网络宽度(增加通道数和神经元数量),极大地提升了模型的容量和学习能力。

2. 范式转变:从“特征工程”到“端到端学习”
- 传统方法:
人类专家设计特征
->标准机器学习模型
- AlexNet (深度学习):
原始像素
->CNN自动学习特征
->分类器
- 整个网络(特征提取部分 + 分类部分)是一个整体,进行端到端 (End-to-End) 的联合训练。
- CNN 学到的特征是为最终的分类任务“量身定制”的,比人工设计的特征更高效。
- 这使得模型构建不再依赖繁琐的领域知识,更容易跨问题、跨学科迁移。
3. AlexNet 架构详解
层级 | LeNet | AlexNet | 主要区别与解读 |
---|---|---|---|
输入 | 32×32×1 (灰度图) | 224×224×3 (RGB彩色图) | 输入尺寸和复杂度大幅增加。 |
卷积层 1 | 5×5 卷积核, 6通道输出 | 11×11 卷积核, 96通道输出, 步幅(Stride)=4 | 用更大的卷积核适应大尺寸输入;通道数激增,以捕捉更多模式;大步幅快速降低特征图尺寸,节省算力。 |
池化层 1 | 2×2 平均池化, 步幅=2 | 3×3 最大池化, 步幅=2 | 使用**最大池化(Max Pooling)**保留更显著的特征;更大的池化窗口提供更强的平移不变性。 |
卷积层 2 | 5×5 卷积核, 16通道输出 | 5×5 卷积核, 256通道输出 | 通道数继续大幅增加。 |
新增结构 | (无) | 3个连续的卷积层 + 1个池化层 | 显著增加了网络深度,3个 3×3 卷积层堆叠,通道数高达384,进一步提取复杂特征。 |
全连接层 | 2个隐藏层 (120, 84个神经元) | 2个隐藏层 (各4096个神经元) | 神经元数量巨大,以匹配1000类的复杂分类任务。 |
输出层 | 10个神经元 (Softmax) | 1000个神经元 (Softmax) | 对应 ImageNet 的1000个类别。 |
2.5 AlexNet 的关键技术细节
AlexNet 的成功不仅在于其“大”,还在于引入了几个关键的“小技巧”,这些技巧后来成为了深度学习的标配。
- 激活函数:使用 ReLU
- 用 ReLU 替代了 LeNet 中的 Sigmoid 函数。
- 优点: 在正数区梯度恒为1,有效减缓了梯度消失问题,使得训练更深的网络成为可能,且收敛速度更快。
- 模型正则化:使用丢弃法 (Dropout)
- 在两个巨大的全连接层(4096神经元)后加入了 Dropout。
- 作用: 训练时随机“丢弃”一部分神经元,防止神经元之间产生复杂的协同适应,是一种非常有效的正则化手段,能显著减轻过拟合。
- 数据增强 (Data Augmentation)
- 背景: 即使有120万张图片,对于一个拥有数千万参数的模型来说仍然不够。
- 方法: 在训练时,对每一个采样的图片进行随机实时变换,包括:
- 随机裁剪 (Random Crop)
- 随机调整亮度和色温 (Hue)
- 目的: 极大地扩充了数据集,让模型学习对位置、光照、颜色等变化不敏感的本质特征,从而提升泛化能力,防止模型死记硬背。

AlexNet 是一个名副其实的“计算巨兽”。虽然参数量相比 LeNet 增加了几百倍,但由于卷积的参数共享特性,计算量的增加幅度更大。尽管如此,从今天的标准看,AlexNet 已经算是一个非常“便宜”的网络了。
对比项 | LeNet | AlexNet | 倍数 |
---|---|---|---|
可学习参数 | 约 6万 | 约 4600万 | ~760倍 (远超课程口误的10倍) |
计算量 (FLOPs) | 约 4百万 | 约 10亿 | ~250倍 |

2.7 代码实现
import torch
from torch import nn
net = nn.Sequential(
# 这里使用一个11*11的更大窗口来捕捉对象。
# 同时,步幅为4,以减少输出的高度和宽度。
# 另外,输出通道的数目远大于LeNet
nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 使用三个连续的卷积层和较小的卷积窗口。
# 除了最后的卷积层,输出通道的数量进一步增加。
# 在前两个卷积层之后,汇聚层不用于减少输入的高度和宽度
nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Flatten(),
# 这里,全连接层的输出数量是LeNet中的好几倍。使用dropout层来减轻过拟合
nn.Linear(6400, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
# 最后是输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10))
X = torch.randn(1, 1, 224, 224)
for layer in net:
X=layer(X)
print(layer.__class__.__name__,'output shape:\t',X.shape)

3 VGG (2014)
3.1 思想
AlexNet 的成功证明了“更深、更大”的网络是有效的,但它的架构设计却显得有些“随意”和“不规则”。这给后续的研究者们带来了一个核心问题:
-
如何系统性地、有原则地构建更深、更强大的网络?
简单地复制粘贴 AlexNet 的某些层,或者随意增减层数和通道数,缺乏清晰的设计思路。为了解决这个问题,VGG (Visual Geometry Group at Oxford) 提出了一个全新的、更加规整和模块化的设计思想。
核心动机:为“更深、更大”的网络设计提供一个清晰、可重复的框架,而不是依赖于临时的、随意的结构调整。

-
VGG 的核心思想:用可重复的“块”来构建网络
VGG 的设计哲学可以类比为“搭乐高”或“码砖”:先设计一个标准化的、可重复使用的基础组件(VGG 块),然后通过堆叠这些组件来构建出完整的、非常深的网络。
这个思想是对 AlexNet 中一个模式的提炼和泛化:AlexNet 后半部分连续使用了三个结构相同的卷积层。VGG 将这个思路发扬光大,并将其标准化。

3.2 VGG 块 (VGG Block)
VGG 块是 VGG 网络的基本构成单元,其设计非常简洁和规整。
- 连续的卷积层:
- 一个 VGG 块包含 N 个连续的卷积层。
- 卷积核尺寸 (Kernel Size): 固定为 3×3。这是一个关键的设计选择。
- 填充 (Padding): 固定为 1。这保证了经过卷积后,特征图的高和宽保持不变。
- 通道数 (Channels): 块内所有卷积层的输入和输出通道数相同,设为 M。
- 池化层:
- 在 N 个卷积层之后,连接一个最大池化层 (Max Pooling)。
- 池化窗口: 2×2 (回归了 LeNet 的设计,与 AlexNet 的 3×3 不同)。
- 步幅 (Stride): 固定为 2。这保证了每次池化后,特征图的高和宽都减半。
VGG 块的两个超参数:
N
: 每个块中卷积层的数量。M
: 每个块中的通道数。

为什么选择 3×3 卷积核?
VGG 的研究者发现,在保持计算开销大致相同的情况下:
使用多个堆叠的 3×3 卷积层,其效果优于使用单个尺寸更大的卷积层(如 5×5)。
例如,两个 3×3 的卷积层堆叠,其感受野(receptive field)等效于一个 5×5 的卷积层,但前者拥有更多的非线性变换(经过了两次激活函数),使得模型表达能力更强,网络可以做得更深。
3.3 VGG 整体架构

VGG 网络的整体架构就是将 AlexNet 的卷积部分替换为一系列 VGG 块的串联。
- 卷积部分: 由 N 个 VGG 块串联而成。通过改变块的数量以及每个块内部的卷积层数和通道数,可以得到不同的 VGG 模型变种(如 VGG-16, VGG-19)。
- 全连接部分: VGG 的全连接层部分完全沿用了 AlexNet 的设计,即两个 4096 个神经元的隐藏层,最后连接到输出层。
从 LeNet 到 VGG 的演进回顾:
- LeNet: 2个卷积层 + 2个全连接层。
- AlexNet: 一个更大、更深、结构不规则的 LeNet。
- VGG: 一个更大、更深、结构极其规整的 AlexNet。它将 AlexNet 中间新增的、相对规整的部分提炼为 VGG 块,并用其统一了整个卷积层设计。

3.4 性能对比
- 精度 (Accuracy): VGG 相较于 AlexNet 在 ImageNet 上的精度有显著提升。
- 速度 (Speed): VGG 的计算量巨大,推理速度比 AlexNet 慢很多(约慢5-6倍)。
- 内存 (Memory): VGG 是一个“内存大户”,其巨大的中间特征图和全连接层占用了非常多的内存资源。
总结:VGG 通过更深、更规整的设计,用更多的计算和内存开销换取了更高的模型精度。

3.5 代码实现
import torch
from torch import nn
def vgg_block(num_convs, in_channels, out_channels):
layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1))
layers.append(nn.ReLU())
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
return nn.Sequential(*layers)
def vgg(conv_arch):
conv_blks = []
in_channels = 1
# 卷积层部分
for (num_convs, out_channels) in conv_arch:
conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
in_channels = out_channels
return nn.Sequential(
*conv_blks, nn.Flatten(),
# 全连接层部分
nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 10))
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
net = vgg(conv_arch)
X = torch.randn(size=(1, 1, 224, 224))
for blk in net:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t',X.shape)
