news 2026/4/18 12:26:13

从LeNet到ResNeXt:用PyTorch复现10大经典CNN架构(附完整代码与训练技巧)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从LeNet到ResNeXt:用PyTorch复现10大经典CNN架构(附完整代码与训练技巧)

从LeNet到ResNeXt:用PyTorch复现10大经典CNN架构(附完整代码与训练技巧)

1998年Yann LeCun在支票识别系统中首次应用卷积神经网络时,恐怕不会想到二十年后CNN会成为计算机视觉的基石。如今当我们翻阅这些经典论文,最震撼的往往不是模型的准确率数字,而是先驱者们如何在有限算力下突破认知边界——比如2012年AlexNet用ReLU激活函数解决梯度消失,或是2015年ResNet用残差连接让千层网络成为可能。

作为实践者,理解这些架构最好的方式就是亲手实现它们。本文将用PyTorch带你复现10个里程碑式CNN模型,每个实现都包含三个关键部分:

  • 架构核心:用代码还原论文中的创新设计
  • 训练技巧:针对不同模型的调参经验
  • 性能对比:在CIFAR-10上的实测结果

1. 环境配置与基础工具

在开始构建模型前,需要配置适合深度学习开发的环境。推荐使用Python 3.8+和PyTorch 1.10+的组合,这两个版本在API稳定性和功能支持上达到较好平衡。

1.1 依赖安装

conda create -n cnn python=3.8 conda install pytorch torchvision torchaudio cudatoolkit=11.3 -c pytorch pip install matplotlib tqdm tensorboard

1.2 数据加载器实现

所有模型将统一使用CIFAR-10数据集进行训练对比。这里实现一个增强版的数据加载器:

from torchvision import transforms train_transform = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.247, 0.243, 0.261)) ]) test_transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.247, 0.243, 0.261)) ])

提示:CIFAR-10图像尺寸为32x32,对于原始设计处理224x224输入的模型(如AlexNet),需要调整第一个卷积层的stride和padding

2. 从LeNet-5开始:CNN的雏形

1998年的LeNet-5架构虽然简单,但已经包含了现代CNN的所有核心要素。我们用PyTorch实现时特别要注意两点:

  1. 原始论文使用tanh激活而非ReLU
  2. 池化层是可训练的(现代网络已弃用此设计)

2.1 模型实现

import torch.nn as nn class LeNet5(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 6, 5, padding=2 if input_size==32 else 0) self.act1 = nn.Tanh() self.pool1 = nn.AvgPool2d(2) # 原始版本有可训练参数 self.conv2 = nn.Conv2d(6, 16, 5) self.act2 = nn.Tanh() self.pool2 = nn.AvgPool2d(2) self.fc1 = nn.Linear(16*5*5, 120) self.act3 = nn.Tanh() self.fc2 = nn.Linear(120, 84) self.act4 = nn.Tanh() self.fc3 = nn.Linear(84, 10) def forward(self, x): x = self.pool1(self.act1(self.conv1(x))) x = self.pool2(self.act2(self.conv2(x))) x = x.view(x.size(0), -1) x = self.act3(self.fc1(x)) x = self.act4(self.fc2(x)) return self.fc3(x)

2.2 训练技巧

  • 使用SGD优化器,学习率设为0.01
  • 关闭weight decay(原始论文未使用正则化)
  • 训练50个epoch约达到68%测试准确率

注意:LeNet-5最初设计用于灰度图像,输入通道数为1。处理彩色图像时需要调整第一个卷积层的输入通道

3. AlexNet:深度学习的引爆点

2012年AlexNet在ImageNet竞赛中一战成名,其成功主要来自三大创新:

  1. 使用ReLU解决梯度消失
  2. 引入Dropout防止过拟合
  3. 首次在CNN中使用GPU加速

3.1 关键实现细节

class AlexNet(nn.Module): def __init__(self): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(64, 192, kernel_size=5, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(192, 384, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(inplace=True), 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(), nn.Linear(256 * 6 * 6, 4096), nn.ReLU(inplace=True), nn.Dropout(), nn.Linear(4096, 4096), nn.ReLU(inplace=True), nn.Linear(4096, 10), ) def forward(self, x): x = self.features(x) x = x.view(x.size(0), 256 * 6 * 6) return self.classifier(x)

3.2 训练优化

由于原始AlexNet设计用于ImageNet(1000类),在CIFAR-10上需要调整:

  • 将第一个卷积层的stride从4改为1
  • 使用0.9动量的SGD,初始学习率0.01
  • 添加学习率衰减:每20个epoch乘以0.1
  • 训练100个epoch可达82%准确率

4. VGG-16:深度与规整的美学

牛津大学Visual Geometry Group提出的VGG网络证明了深度的重要性。其核心设计哲学是:

  • 仅使用3×3卷积堆叠
  • 每经过池化层通道数翻倍
  • 全连接层占据大部分参数

4.1 模块化实现

def make_layers(cfg): layers = [] in_channels = 3 for v in cfg: if v == 'M': layers += [nn.MaxPool2d(kernel_size=2, stride=2)] else: conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1) layers += [conv2d, nn.ReLU(inplace=True)] in_channels = v return nn.Sequential(*layers) class VGG16(nn.Module): def __init__(self): super().__init__() cfg = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'] self.features = make_layers(cfg) self.classifier = nn.Sequential( nn.Linear(512 * 1 * 1, 4096), # 原始为512*7*7,CIFAR-10调整 nn.ReLU(True), nn.Dropout(), nn.Linear(4096, 4096), nn.ReLU(True), nn.Dropout(), nn.Linear(4096, 10), ) def forward(self, x): x = self.features(x) x = x.view(x.size(0), -1) return self.classifier(x)

4.2 训练注意事项

  • 使用Xavier初始化卷积层权重
  • 批量大小不宜过大(推荐32-64)
  • 学习率初始设为0.05,每30个epoch衰减
  • 训练150个epoch可达89%准确率

5. ResNet-50:残差学习的革命

当网络深度超过20层后,传统CNN会出现梯度消失问题。ResNet通过残差连接(skip connection)解决了这一难题,其核心公式可以表示为:

y = F(x, {W_i}) + x

5.1 残差块实现

class Bottleneck(nn.Module): expansion = 4 def __init__(self, in_planes, planes, stride=1): super().__init__() self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False) self.bn1 = nn.BatchNorm2d(planes) self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(planes) self.conv3 = nn.Conv2d(planes, self.expansion*planes, kernel_size=1, bias=False) self.bn3 = nn.BatchNorm2d(self.expansion*planes) self.shortcut = nn.Sequential() if stride != 1 or in_planes != self.expansion*planes: self.shortcut = nn.Sequential( nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(self.expansion*planes) ) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = F.relu(self.bn2(self.conv2(out))) out = self.bn3(self.conv3(out)) out += self.shortcut(x) return F.relu(out)

5.2 训练配置

  • 使用He初始化卷积层权重
  • 优化器选择SGD,动量0.9,初始学习率0.1
  • 学习率余弦衰减策略
  • 配合Label Smoothing正则化
  • 训练200个epoch可达94%准确率

6. ResNeXt-50:基数优于深度

ResNeXt提出"基数(cardinality)"概念,通过在残差块内引入并行路径来提升模型容量。其计算量公式为:

FLOPs = O(输入通道 × 输出通道 × 基数 × 卷积核面积)

6.1 分组卷积实现

class ResNeXtBlock(nn.Module): def __init__(self, in_channels, out_channels, stride=1, cardinality=32): super().__init__() mid_channels = out_channels // 2 self.conv1 = nn.Conv2d(in_channels, mid_channels, 1, bias=False) self.bn1 = nn.BatchNorm2d(mid_channels) self.conv2 = nn.Conv2d(mid_channels, mid_channels, 3, stride=stride, padding=1, groups=cardinality, bias=False) self.bn2 = nn.BatchNorm2d(mid_channels) self.conv3 = nn.Conv2d(mid_channels, out_channels, 1, bias=False) self.bn3 = nn.BatchNorm2d(out_channels) self.shortcut = nn.Sequential() if stride != 1 or in_channels != out_channels: self.shortcut = nn.Sequential( nn.Conv2d(in_channels, out_channels, 1, stride=stride, bias=False), nn.BatchNorm2d(out_channels) ) def forward(self, x): residual = self.shortcut(x) x = F.relu(self.bn1(self.conv1(x))) x = F.relu(self.bn2(self.conv2(x))) x = self.bn3(self.conv3(x)) return F.relu(x + residual)

6.2 性能对比

在相同参数量下,ResNeXt-50比ResNet-50在CIFAR-10上表现更优:

模型参数量测试准确率训练时间
ResNet-5025.5M94.2%4.2h
ResNeXt-5025.0M95.1%5.1h

7. 模型部署与优化

完成训练后,我们需要考虑如何在实际环境中高效部署这些模型。PyTorch提供了多种工具来优化推理性能:

7.1 模型量化

model = ResNet50() model.load_state_dict(torch.load('resnet50.pth')) quantized_model = torch.quantization.quantize_dynamic( model, {nn.Linear, nn.Conv2d}, dtype=torch.qint8 ) torch.save(quantized_model.state_dict(), 'resnet50_quant.pth')

7.2 ONNX导出

dummy_input = torch.randn(1, 3, 32, 32) torch.onnx.export(model, dummy_input, "resnet50.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}})

8. 从代码看CNN演进史

通过亲手实现这些经典架构,我们可以清晰看到CNN发展的技术脉络:

  1. 结构简化:从AlexNet的特殊设计到VGG的规整堆叠
  2. 深度突破:ResNet的残差连接解决了深度网络训练难题
  3. 维度扩展:ResNeXt通过基数概念开辟新方向
  4. 效率优化:MobileNet等轻量级架构的兴起

每个时代的突破都源于对当时技术瓶颈的创造性解决。如今虽然Transformer在视觉领域崭露头角,但CNN的许多设计思想仍在影响着新一代架构的发展。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 12:19:50

知识图谱里的“辈分”怎么算?聊聊HAKE如何用极坐标建模语义层级

知识图谱中的"家族树":HAKE模型如何用极坐标破解语义层级之谜 想象一下你正在整理一个庞大家族的族谱——从曾祖父辈到玄孙辈,每个人在家族树中的位置清晰可见。这种层级结构在人类社会中无处不在,而知识图谱中的实体同样存在着类似…

作者头像 李华
网站建设 2026/4/18 12:18:16

[Java毕设2026]宿舍管理系统_SpringBoot+Vue【文末附源码】

系统介绍 宿舍管理系统是一套面向高校、职校和学生公寓场景的数字化宿舍管理平台,围绕楼栋、房间、床位、学生和宿舍日常事务,打造一套清晰、高效、可追踪的业务管理系统。 系统概述 本系统采用前后端分离架构,前端基于 Vue 3 Element Pl…

作者头像 李华
网站建设 2026/4/18 12:13:15

外呼系统有哪些模式?新手小白要如何挑选?

刚入电销行业,面对各式各样的外呼系统你是不是看花了眼?商家说得天花乱坠,功能眼花缭乱,价格千差万别。别急,今天这篇新手避坑指南,帮你拨开迷雾,聚焦外呼系统最核心的问题——外呼线路有哪些&a…

作者头像 李华