Day 22:经典CNN架构 — LeNet到VGG
今日目标:从历史中理解CNN的进化逻辑,手写LeNet/AlexNet/VGG,掌握架构设计原则
预计阅读:10分钟 | 动手操作:40分钟
一、CNN进化史:每代解决什么问题
1998 LeNet-5 → 第一个CNN,证明卷积可以识别手写数字
2012 AlexNet → 深度学习革命开始,GPU+ReLU+Dropout
2014 VGGNet → 证明深度很重要,全用3×3小卷积核
2014 GoogLeNet → 多尺度特征提取,Inception模块
2015 ResNet → 残差连接,让152层网络能训练
2017 MobileNet → 端侧部署,深度可分离卷积
...
每代架构都在解决一个核心问题,理解了问题,就理解了架构!
二、LeNet-5 (1998) — 卷积神经网络的开山之作
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import torch.optim as optim
torch.manual_seed(42)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# ===== LeNet-5 =====
class LeNet5(nn.Module):
"""
LeNet-5:Yann LeCun 1998年提出,用于手写数字识别
架构:C1→S2→C3→S4→C5→F6→Output
输入: 32×32 灰度图
输出: 10个数字类别
设计哲学:
- 卷积提取特征,池化下采样,全连接分类
- 这个三阶段模式影响至今
"""
def __init__(self, num_classes=10):
super().__init__()
# 特征提取器
self.features = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5), # 32×32 → 28×28×6
nn.Tanh(), # 当年用Tanh,现在用ReLU
nn.AvgPool2d(kernel_size=2, stride=2), # 28×28 → 14×14×6
nn.Conv2d(6, 16, kernel_size=5), # 14×14 → 10×10×16
nn.Tanh(),
nn.AvgPool2d(kernel_size=2, stride=2), # 10×10 → 5×5×16
)
# 分类器
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120),
nn.Tanh(),
nn.Linear(120, 84),
nn.Tanh(),
nn.Linear(84, num_classes),
)
def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x
# 测试
model_lenet = LeNet5(num_classes=10)
dummy = torch.randn(1, 1, 32, 32)
output = model_lenet(dummy)
print(f"LeNet-5: 输入 {dummy.shape} → 输出 {output.shape}")
print(f"参数量: {sum(p.numel() for p in model_lenet.parameters()):,}")
# 用LeNet-5训练MNIST
def train_lenet_on_mnist():
transform = transforms.Compose([
transforms.Resize(32),
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)),
])
train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('./data', train=False, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False)
model = LeNet5().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
for epoch in range(5):
model.train()
correct, total = 0, 0
for x, y in train_loader:
x, y = x.to(device), y.to(device)
optimizer.zero_grad()
loss = criterion(model(x), y)
loss.backward()
optimizer.step()
correct += (model(x).argmax(1) == y).sum().item()
total += y.size(0)
model.eval()
with torch.no_grad():
test_correct = sum((model(x.to(device)).argmax(1) == y.to(device)).sum().item()
for x, y in test_loader)
print(f"Epoch {epoch+1}: Train Acc={correct/total:.2%}, Test Acc={test_correct/len(test_dataset):.2%}")
# train_lenet_on_mnist()
LeNet-5 的历史意义:
- 第一次把卷积+池化+全连接的三段式结构固化为CNN标准范式
- 证明了端到端学习(不需要手工特征)的可行性
- 在银行支票识别上达到商业可用水平
三、AlexNet (2012) — 深度学习革命的起点
class AlexNet(nn.Module):
"""
AlexNet:2012年ImageNet冠军,深度学习革命的开端
核心创新:
1. ReLU替代Sigmoid/Tanh → 解决梯度消失
2. Dropout防止过拟合
3. 数据增强(随机裁剪、水平翻转)
4. 双GPU并行训练(当时显存不够)
5. Local Response Normalization (LRN,现在被BN取代)
输入: 224×224×3
输出: 1000类
"""
def __init__(self, num_classes=1000):
super().__init__()
self.features = nn.Sequential(
# Block 1: 224×224 → 55×55
nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
# Block 2: 55×55 → 27×27
nn.Conv2d(64, 192, kernel_size=5, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
# Block 3: 27×27 → 13×13
nn.Conv2d(192, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
# Block 4: 13×13
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
# Block 5: 13×13 → 6×6
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
)
self.classifier = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(256 * 6 * 6, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Linear(4096, num_classes),
)
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
model_alexnet = AlexNet(num_classes=1000)
dummy = torch.randn(1, 3, 224, 224)
output = model_alexnet(dummy)
print(f"\nAlexNet: 输入 {dummy.shape} → 输出 {output.shape}")
print(f"参数量: {sum(p.numel() for p in model_alexnet.parameters()):,}")
# AlexNet的问题分析
print("\nAlexNet架构问题:")
print(" 1. 第一层用11×11大卷积核 → 参数多、计算量大")
print(" 2. 全连接层4096维 → 占模型参数的90%以上")
print(" 3. 需要224×224固定输入 → 不灵活")
print(" 4. 这些缺陷催生了VGGNet的改进")
AlexNet 的历史意义:
- 当年ImageNet Top-5错误率从26%降到15.3%,吊打所有传统方法
- 证明了深度学习在视觉任务上的巨大潜力
- GPU训练+ReLU+Dropout成为标配
四、VGGNet (2014) — 深度就是力量
class VGGNet(nn.Module):
"""
VGGNet:牛津大学VGG组提出,证明深度的重要性
核心创新:
1. 全用3×3小卷积核(堆叠两个3×3 = 一个5×5的感受野,但参数更少)
2. 统一的结构:n个Conv+ReLU → MaxPool → 通道数翻倍
3. 只增深度,不增复杂度
两个3×3 vs 一个5×5:
参数量: 2×(3×3×C×C) = 18C² vs 5×5×C×C = 25C² → 减少28%
但感受野相同!
版本:VGG11/VGG13/VGG16/VGG19(数字=层数)
"""
def __init__(self, config='VGG16', num_classes=1000):
super().__init__()
# VGG各版本的配置
cfgs = {
'VGG11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
'VGG13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
'VGG16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
'VGG19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}
self.features = self._make_layers(cfgs[config])
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.5),
nn.Linear(4096, num_classes),
)
def _make_layers(self, cfg):
layers = []
in_channels = 3
for v in cfg:
if v == 'M':
layers.append(nn.MaxPool2d(2))
else:
layers.extend([
nn.Conv2d(in_channels, v, kernel_size=3, padding=1),
nn.BatchNorm2d(v), # 原版VGG没有BN,这里加上
nn.ReLU(inplace=True),
])
in_channels = v
return nn.Sequential(*layers)
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
# 对比各版本
for name in ['VGG11', 'VGG13', 'VGG16', 'VGG19']:
model = VGGNet(config=name, num_classes=1000)
params = sum(p.numel() for p in model.parameters())
print(f"{name:<8s}: {params:>15,} 参数")
# 感受野分析
print("\nVGG的感受野(3×3堆叠的魅力):")
print(" 1个3×3 → 感受野 = 3×3")
print(" 2个3×3 → 感受野 = 5×5 (等于1个5×5)")
print(" 3个3×3 → 感受野 = 7×7 (等于1个7×7)")
print(" 堆叠3×3的优势:")
print(" - 更少的参数:3×3×3×C² = 27C² vs 7×7×C² = 49C²")
print(" - 更多的非线性:3个ReLU vs 1个ReLU")
print(" - 更强的表达能力")
VGGNet 的历史意义:
- 证明了深度比单个大卷积核更重要
- 统一了CNN的设计语言:3×3卷积 + 2×2池化
- VGG16至今仍是目标检测的常用backbone(如Faster R-CNN)
五、三代架构对比
def compare_architectures():
"""对比三代CNN的关键指标"""
models = {
'LeNet-5': {
'year': 1998,
'input_size': '32×32',
'layers': 5,
'params': '60K',
'top_1_acc': '~99% (MNIST)',
'key_innovation': 'CNN三阶段范式',
'activation': 'Tanh',
'normalization': '无',
'dropout': '无',
},
'AlexNet': {
'year': 2012,
'input_size': '224×224',
'layers': 8,
'params': '60M',
'top_1_acc': '~63% (ImageNet)',
'key_innovation': 'ReLU+Dropout+GPU',
'activation': 'ReLU',
'normalization': 'LRN',
'dropout': '0.5',
},
'VGG16': {
'year': 2014,
'input_size': '224×224',
'layers': 16,
'params': '138M',
'top_1_acc': '~74% (ImageNet)',
'key_innovation': '全3×3小卷积核',
'activation': 'ReLU',
'normalization': '无(原版)',
'dropout': '0.5',
},
}
print(f"{'指标':<16s} {'LeNet-5':<16s} {'AlexNet':<16s} {'VGG16':<16s}")
print('-' * 64)
keys = ['year', 'input_size', 'layers', 'params', 'top_1_acc', 'key_innovation']
for key in keys:
print(f"{key:<16s} {str(models['LeNet-5'][key]):<16s} "
f"{str(models['AlexNet'][key]):<16s} "
f"{str(models['VGG16'][key]):<16s}")
compare_architectures()
六、动手实践:从零搭建经典架构
6.1 实战:统一训练框架对比三代架构
class ModelBenchmark:
"""统一训练框架,对比不同架构的性能"""
def __init__(self, model, device, lr=0.001):
self.model = model.to(device)
self.device = device
self.criterion = nn.CrossEntropyLoss()
self.optimizer = optim.Adam(model.parameters(), lr=lr)
def train_epoch(self, loader):
self.model.train()
correct, total, loss_sum = 0, 0, 0.0
for x, y in loader:
x, y = x.to(self.device), y.to(self.device)
self.optimizer.zero_grad()
loss = self.criterion(self.model(x), y)
loss.backward()
self.optimizer.step()
loss_sum += loss.item() * x.size(0)
correct += (self.model(x).argmax(1) == y).sum().item()
total += x.size(0)
return loss_sum / total, correct / total
@torch.no_grad()
def evaluate(self, loader):
self.model.eval()
correct, total = 0, 0
for x, y in loader:
x, y = x.to(self.device), y.to(self.device)
correct += (self.model(x).argmax(1) == y).sum().item()
total += x.size(0)
return correct / total
def benchmark_on_cifar10():
"""在CIFAR-10上对比LeNet-5、AlexNet、VGG"""
# CIFAR-10适配版(输入32×32,10类)
models = {
'LeNet-5 (adapted)': LeNet5(num_classes=10),
'AlexNet (adapted)': AlexNet(num_classes=10),
'VGG11 (adapted)': VGGNet(config='VGG11', num_classes=10),
}
# 数据
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)),
])
train_dataset = datasets.CIFAR10('./data', train=True, download=True, transform=transform)
test_dataset = datasets.CIFAR10('./data', train=False, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False)
results = {}
for name, model in models.items():
print(f"\n训练 {name}...")
# 注意:LeNet-5输入是灰度图,需要转换
if 'LeNet' in name:
# 对于LeNet,需要把RGB转灰度
model = LeNet5(num_classes=10)
# 简化处理:只取第一个通道
gray_transform = transforms.Compose([
transforms.Grayscale(),
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,)),
])
# 跳过,直接看原理
trainer = ModelBenchmark(model, device, lr=0.001)
for epoch in range(5):
train_loss, train_acc = trainer.train_epoch(train_loader)
val_acc = trainer.evaluate(test_loader)
results[name] = {
'params': sum(p.numel() for p in model.parameters()),
'val_acc': val_acc,
}
print(f" {name}: 参数量={results[name]['params']:,}, 准确率={val_acc:.2%}")
return results
# benchmark_on_cifar10()
6.2 实战:计算每层输出尺寸
def trace_feature_map_sizes(model_name):
"""追踪网络每一层的特征图尺寸变化"""
models = {
'LeNet-5': LeNet5(num_classes=10),
'AlexNet': AlexNet(num_classes=1000),
'VGG16': VGGNet(config='VGG16', num_classes=1000),
}
model = models[model_name]
input_sizes = {
'LeNet-5': (1, 1, 32, 32),
'AlexNet': (1, 3, 224, 224),
'VGG16': (1, 3, 224, 224),
}
x = torch.randn(input_sizes[model_name])
print(f"\n{model_name} 特征图尺寸变化:")
print(f"{'层':<5s} {'输出尺寸':<20s} {'空间尺寸':<12s} {'通道数':<8s}")
print('-' * 50)
print(f"{'输入':<5s} {str(x.shape):<20s} {f'{x.shape[2]}×{x.shape[3]}':<12s} {x.shape[1]:<8d}")
with torch.no_grad():
if hasattr(model, 'features'):
for i, layer in enumerate(model.features):
x = layer(x)
if isinstance(layer, (nn.Conv2d, nn.MaxPool2d, nn.AvgPool2d)):
print(f"L{i+1:<4d} {str(x.shape):<20s} "
f"{f'{x.shape[2]}×{x.shape[3]}':<12s} {x.shape[1]:<8d}")
trace_feature_map_sizes('VGG16')
七、常见坑点
坑1:VGG的全连接层太大
# VGG16的FC层: 512×7×7=25088 → 4096 → 4096 → 1000
# 仅FC层参数就超过120M,占模型参数的90%以上
# ❌ 端侧部署时全连接层太大了
# ✅ 现代方法:用全局平均池化替代全连接层
# 如ResNet的: nn.AdaptiveAvgPool2d(1) → nn.Linear(512, 1000)
坑2:不同架构的输入尺寸要求不同
# LeNet-5: 32×32
# AlexNet: 224×224
# VGG: 224×224
# 如果输入尺寸不对,需要调整或加AdaptiveAvgPool2d
坑3:从零训练VGG很慢
# VGG16有138M参数,从零训练需要大量GPU时间
# 实际使用中99%都是加载预训练权重
# model = torchvision.models.vgg16(weights='IMAGENET1K_V1')
坑4:BatchNorm在VGG原版中不存在
# 原版VGG没有BN,但现代实现通常加上
# 有BN的VGG更容易训练,收敛更快
# 面试常问:VGG为什么没有BN?因为BN是2015年才提出的
八、今日作业
- 手写架构:实现LeNet-5,在MNIST上达到>98%准确率
- 架构对比:对比LeNet-5/AlexNet/VGG的参数量和计算量,画一张表格
- 感受野:计算VGG16的感受野,验证堆叠3×3的效果
- 打卡:评论区发你的对比结果,格式:“Day 22/100 打卡:经典CNN架构已掌握!”
今日小结
今天你学会了:
✅ CNN进化史:LeNet-5 → AlexNet → VGG
✅ LeNet-5:CNN三阶段范式(卷积→池化→全连接)
✅ AlexNet:ReLU+Dropout+GPU的革命
✅ VGG:全3×3小卷积核堆叠,深度就是力量
✅ 两个3×3 vs 一个5×5的参数量对比
✅ 三代架构的完整对比表
✅ 统一训练框架对比不同架构
✅ 特征图尺寸追踪
✅ 4个经典坑点
明日预告
Day 23:ResNet与残差连接 — 终结梯度消失
残差学习、Bottleneck、ResNet-18/34/50/101/152、Identity Mapping
🔥 关注我,每天解锁一个端侧AI技能!
微信公众号:xxx | 小红书:xxx | CSDN:xxx
评论区打卡,一起坚持100天!
附:小红书图文版
封面标题建议:CNN进化史 | LeNet→AlexNet→VGG,三代经典一篇看懂 🏛️
P1 — 封面
标题:经典CNN架构
副标题:LeNet / AlexNet / VGG
关键词:CNN / 架构进化 / 深度学习
P2 — LeNet-5 (1998)
第一个CNN,手写数字识别
卷积→池化→全连接三阶段
60K参数,银行支票识别商用
P3 — AlexNet (2012)
深度学习革命起点
ReLU代替Sigmoid解决梯度消失
Dropout+数据增强防过拟合
60M参数,ImageNet夺冠
P4 — VGGNet (2014)
全用3×3小卷积核堆叠
2个3×3 = 1个5×5感受野,参数更少
深度从11层到19层
138M参数,至今仍是最流行backbone
P5 — 进化规律
更深:5层→8层→16层
更小核:11×11→5×5→3×3
更统一:VGG之后架构设计标准化
下一站:ResNet终结梯度消失
P6 — 今日作业
手写LeNet/AlexNet/VGG + 架构对比
评论区打卡 Day 22/100
标签:#CNN #LeNet #AlexNet #VGG #深度学习
CSDN发布提示:CSDN版本建议在每代架构放一张网络结构图,在对比部分用表格,在感受野部分放一张堆叠3×3的示意图。
更多推荐


所有评论(0)