拓冰建站拓冰建站
首页 / 资讯中心 / 正文

卷积神经网络CNN精讲:从LeNet-5到AlexNet的PyTorch实战

这次我们直接聊卷积神经网络CNN。很多教材把 CNN 放在深度学习入门的第一课既是因为它在图像识别里几乎是绕不开的基础结构也是因为从 CNN 可以顺理成章引出 LeNet-5、AlexNet 这些经典模型帮你把“神经网络到底怎么一步步识别图片”的路径理清楚。这篇文章不绕弯子直接拆解卷积层、池化层、激活函数、全连接层再把 LeNet-5 和 AlexNet 的每一层结构都拉出来看一遍最后给出一套可以实际运行的 PyTorch 代码带你从零跑通一个手写数字识别模型。如果你正在学机器学习或者刚接触深度学习、只听说过“CNN”但没弄懂内部机制这篇文章可以直接收藏。全文按“概念 - 结构 - 代码 - 训练 - 排错”的顺序展开读完你能自己画出 LeNet-5 和 AlexNet 的结构图也能独立跑一个最简单的 CNN 训练任务。1. CNN 核心能力速览能力项说明输入类型图像、序列数据、语音频谱、文本向量等最常见的是二维图像核心组件卷积层、池化层、激活函数、全连接层主要功能图像分类、目标检测、图像分割、特征提取、图像生成等经典模型LeNet-5、AlexNet、VGG、ResNet、GoogLeNet 等典型应用手写数字识别、人脸识别、医学影像分析、自动驾驶视觉感知硬件要求纯 CPU 可以跑小模型大规模训练建议使用 GPU 加速推荐框架PyTorch、TensorFlow、Keras、MindSpore学习门槛需要线性代数、微积分基础会 Python 基本语法适合场景深度学习入门、图像分类任务、特征提取实验、算法面试复习这里先给一个整体结论CNN 不是某种单一模型而是一套由卷积、池化、激活、全连接组合起来的“特征提取 分类”架构。理解 CNN 的关键不在于背公式而在于搞清楚每一层到底对输入做了什么。2. 适用场景与学习路径CNN 最适合两类人。第一类是刚入门深度学习的学生或开发者。你不需要先啃完整本数学书只要知道矩阵乘法、导数、梯度下降的基本含义就能通过 CNN 把“前向传播、反向传播、梯度更新”这条主线串起来。LeNet-5 和 AlexNet 都是很好的研究对象LeNet-5 结构小、参数少适合在 CPU 上跑通AlexNet 引入了 ReLU、Dropout、数据增强等现代技巧能让你看到深度学习在 2012 年之后为什么爆发。第二类是需要做图像任务的工程技术人员。你手里可能有一批分类图片需要快速建立基线模型。CNN 的通用流程非常固定读取数据 - 预处理 - 搭建卷积网络 - 训练 - 评估 - 调参。先跑通 LeNet-5再替换成 AlexNet 或 ResNet是成本最低的起步方式。CNN 也有自己的适用边界。它擅长处理具有局部相关性的数据比如图像里的空间相邻像素、语音里的连续帧、文本里的相邻词。但如果你的任务是纯表格结构化数据每一列之间没有明显的局部空间关系MLP 或树模型往往更合适。另外CNN 对图像尺寸、数据分布比较敏感训练样本太少时容易过拟合需要配合数据增强或迁移学习。学习路径建议按照“卷积层 - 池化层 - 激活函数 - 全连接层 - LeNet-5 - AlexNet - 上手实现”的顺序走下去。这个顺序和下面文章章节一致。3. 环境准备与前置知识3.1 软件环境无论你选 PyTorch 还是 TensorFlow都需要先建立一个干净的 Python 环境。推荐使用 Anaconda 管理环境避免不同项目的依赖互相冲突。# 创建虚拟环境 conda create -n cnn_tutorial python3.9 # 激活环境 conda activate cnn_tutorial # 安装 PyTorchCPU 版示例GPU 版请按官网命令安装 pip install torch torchvision如果本机有 NVIDIA 显卡建议安装 CUDA 版本的 PyTorch。到 PyTorch 官网选择对应命令即可这里不再粘贴完整命令因为不同 CUDA 版本命令不同。安装后可以用下面的代码检查 CUDA 是否可用。import torch print(torch.__version__) print(torch.cuda.is_available())3.2 数据集准备本文代码示例使用 MNIST 手写数字数据集。它包含 60000 张训练图片和 10000 张测试图片每张都是 28x28 的灰度图。PyTorch 的 torchvision 会自动下载也可以手动下载后放入本地目录。3.3 硬件门槛LeNet-5 参数量约 6 万训练 MNIST 用 CPU 几分钟就能完成一轮。AlexNet 原始参数量约 6000 万在 CPU 上训练会很慢建议使用 GPU或者只做前向推理而不训练。本文的完整训练示例基于 LeNet-5 风格的小网络普通笔记本电脑可以跑。4. 卷积层CNN 的特征提取器4.1 卷积在做什么卷积层的作用是提取局部特征。它用一个固定大小的窗口卷积核在输入图像上滑动每次计算窗口内像素与卷积核的加权和得到一个新的特征值。比如一个 3x3 的卷积核在 28x28 的图像上滑动最终会生成一张新的特征图。这句话值得反复琢磨卷积核就是一组权重它负责检测一种局部模式。第一个卷积层可能检测边缘、线条后面的卷积层会把前面提取的局部特征组合成更复杂的模式比如眼睛、轮子、人脸。4.2 关键参数参数含义kernel_size卷积核大小常用 3x3、5x5stride卷积核每次滑动的步长默认 1padding在输入四周补零的数量控制输出尺寸out_channels输出特征图数量即这一层用了多少个卷积核in_channels输入通道数灰度图为 1RGB 图为 3输出特征图尺寸计算公式如下。输出尺寸 floor((输入尺寸 2 * padding - kernel_size) / stride) 1例如输入 28x28kernel_size5stride1padding0输出尺寸为(28 0 - 5) / 1 1 24所以卷积后得到 24x24 的特征图。4.3 PyTorch 卷积层示例import torch import torch.nn as nn # 输入1通道输出6通道卷积核5x5 conv_layer nn.Conv2d(in_channels1, out_channels6, kernel_size5, stride1, padding0) # 模拟一张 1x28x28 的输入batch_size1 x torch.randn(1, 1, 28, 28) out conv_layer(x) print(out.shape) # 输出torch.Size([1, 6, 24, 24])注意卷积层本身不改变输入通道数而是把输入每个通道分别与对应卷积核做卷积后相加最终生成 out_channels 个特征图。5. 池化层降低分辨率保留关键信息5.1 池化的作用池化层也叫下采样层。它不学习参数而是对特征图做局部聚合。最常见的两种池化是最大池化MaxPooling和平均池化AveragePooling。最大池化取窗口内的最大值平均池化取窗口内平均值。池化有三个好处减小特征图尺寸降低计算量。扩大感受野让后面的卷积层看到更大范围的输入信息。提供一定的平移不变性即目标在图像内小幅移动时池化结果变化不大。5.2 PyTorch 池化层示例import torch.nn as nn # 2x2 最大池化步长默认等于 kernel_size maxpool nn.MaxPool2d(kernel_size2, stride2) x torch.randn(1, 6, 24, 24) out maxpool(x) print(out.shape) # 输出torch.Size([1, 6, 12, 12])平均池化示例avgpool nn.AvgPool2d(kernel_size2, stride2) out_avg avgpool(x) print(out_avg.shape) # 输出torch.Size([1, 6, 12, 12])在 LeNet-5 中每个卷积层后面都跟着一个平均池化层而在现代 CNN 中最大池化更常见。6. 激活函数与全连接层6.1 激活函数的必要性卷积操作本质上是线性运算多层线性运算叠加后还是线性变换。为了拟合复杂的非线性关系必须在每一层后面加入非线性激活函数。早期常用 Sigmoid 和 Tanh但它们在深层网络中容易引发梯度消失。AlexNet 等现代模型普遍使用 ReLU。ReLU 公式非常简单。f(x) max(0, x)ReLU 计算快并且能缓解梯度消失问题。PyTorch 中使用方式如下。import torch.nn.functional as F # 对特征图施加 ReLU out F.relu(out)6.2 全连接层的作用全连接层通常出现在网络的最后负责把前面提取到的特征映射到最终的类别概率上。它的输入是一个一维向量每个神经元与上一层的所有神经元都连接因此叫“全连接”。以 10 分类任务为例最后一层全连接层的输出节点数是 10每个节点对应一个类别得分。之后接一个 Softmax 函数把得分转成概率分布。import torch.nn as nn # 输入特征长度 120输出类别数 10 fc nn.Linear(in_features120, out_features10) x torch.randn(1, 120) out fc(x) # 对 10 个类别得分做 Softmax probs F.softmax(out, dim1) print(probs)全连接层的参数量通常很大因为每个输入神经元都要和每个输出神经元相连。这也是为什么现代 CNN 倾向于用全局平均池化替代全连接层来减少参数量但 LeNet-5 和 AlexNet 都使用了全连接层。7. LeNet-5 网络结构逐层拆解LeNet-5 是 Yann LeCun 等人在 1998 年提出的卷积神经网络主要用于手写数字识别。它的结构非常规整是理解 CNN 的最佳起点。7.1 输入与输出LeNet-5 输入是 32x32 的灰度图像MNIST 原始图像是 28x28所以在使用 PyTorch 实现时通常把图像 resize 到 32x32或者直接微调结构接受 28x28。为忠实还原原始结构这里按 32x32 描述。输出是 10 个类别对应数字 0-9。7.2 逐层参数表层序号层类型核大小/参数输入尺寸输出尺寸1卷积层 C16 个 5x5 卷积核32x32x128x28x62池化层 S2平均池化2x228x28x614x14x63卷积层 C316 个 5x5 卷积核14x14x610x10x164池化层 S4平均池化2x210x10x165x5x165卷积层 C5120 个 5x5 卷积核5x5x161x1x1206全连接层 F684 个神经元120847全连接层 OUTPUT10 个神经元8410注意C5 这一层使用的卷积核对 5x5 的输入做卷积输出尺寸是 1x1实际上等价于一个全连接层。在 PyTorch 实现时可以直接用nn.Conv2d(16, 120, kernel_size5)输出后展平为 120 维向量。7.3 LeNet-5 特点参数量小适合快速实验。池化层使用平均池化而非最大池化。激活函数在原始论文中使用 Sigmoid 或 Tanh现代复现中常用 ReLU。结构简单适合作为教学模型。8. AlexNet 网络结构逐层拆解AlexNet 是 2012 年 ImageNet 竞赛的冠军模型它的成功让卷积神经网络重新成为视觉任务的主流。与 LeNet-5 相比AlexNet 更深、更宽同时引入了几项关键技术。8.1 输入与关键技巧AlexNet 输入为 227x227x3 的 RGB 图像输出为 1000 个类别。它的核心创新包括使用 ReLU 激活函数加速收敛。使用 Dropout 随机丢弃部分神经元减少过拟合。使用重叠最大池化即池化窗口大于步长提高特征多样性。通过数据增强随机裁剪、翻转扩充训练样本。使用两块 GPU 并行训练当年硬件限制。8.2 逐层参数表层序号层类型核大小/步长/填充输出尺寸1卷积层96 个 11x11 卷积核stride455x55x962最大池化3x3stride227x27x963卷积层256 个 5x5 卷积核padding227x27x2564最大池化3x3stride213x13x2565卷积层384 个 3x3 卷积核padding113x13x3846卷积层384 个 3x3 卷积核padding113x13x3847卷积层256 个 3x3 卷积核padding113x13x2568最大池化3x3stride26x6x2569全连接层4096 个神经元409610Dropout丢弃比例 0.5409611全连接层4096 个神经元409612Dropout丢弃比例 0.5409613全连接层1000 个神经元100014Softmax1000 类概率1000这里把卷积和池化交替排列逐步降低空间尺寸、增加通道数最终通过全连接层输出分类结果。现代 CNN 的“卷积下采样 - 通道增加 - 全连接分类”范式就是由 AlexNet 奠定的。8.3 LeNet-5 与 AlexNet 对比对比项LeNet-5AlexNet提出时间19982012输入尺寸32x32 灰度227x227 RGB深度7 层含池化8 层不计池化激活函数Sigmoid/TanhReLU池化方式平均池化最大池化防止过拟合较少Dropout 数据增强参数量约 6 万约 6000 万适合数据集MNIST 小图像ImageNet 大规模图像从对比可以看出CNN 的发展主线是把网络做得更深、更大并不断改进激活函数和正则化方法。9. 用 PyTorch 实现 CNN 并训练 MNIST现在把前面所有概念串起来写一个完整的可运行示例。这个模型结构参考 LeNet-5但为了适配 28x28 输入做了一点简化可以直接在 CPU 上训练。9.1 定义网络结构import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms class SimplifiedLeNet5(nn.Module): def __init__(self, num_classes10): super(SimplifiedLeNet5, self).__init__() self.conv1 nn.Conv2d(1, 6, kernel_size5, padding2) self.pool nn.MaxPool2d(kernel_size2, stride2) self.conv2 nn.Conv2d(6, 16, kernel_size5) self.fc1 nn.Linear(16 * 5 * 5, 120) self.fc2 nn.Linear(120, 84) self.fc3 nn.Linear(84, num_classes) def forward(self, x): # 输入 28x28 x F.relu(self.conv1(x)) # 28x28 - 28x28 (padding2) x self.pool(x) # 14x14 x F.relu(self.conv2(x)) # 10x10 x self.pool(x) # 5x5 x x.view(x.size(0), -1) # 展平 x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) x self.fc3(x) return x注意conv1使用了padding2让 28x28 输入卷积后仍保持 28x28。conv2卷积核 5x5输入 14x14输出 10x10池化后变成 5x5因此展平后长度是16 * 5 * 5 400。9.2 加载 MNIST 数据transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset datasets.MNIST(root./data, trainTrue, downloadTrue, transformtransform) test_dataset datasets.MNIST(root./data, trainFalse, downloadTrue, transformtransform) train_loader DataLoader(datasettrain_dataset, batch_size64, shuffleTrue) test_loader DataLoader(datasettest_dataset, batch_size256, shuffleFalse)9.3 定义训练函数device torch.device(cuda if torch.cuda.is_available() else cpu) model SimplifiedLeNet5().to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) def train(epoch): model.train() total_loss 0 for batch_idx, (data, target) in enumerate(train_loader): data, target data.to(device), target.to(device) optimizer.zero_grad() output model(data) loss criterion(output, target) loss.backward() optimizer.step() total_loss loss.item() if batch_idx % 200 0: print(fEpoch {epoch} Batch {batch_idx} Loss {loss.item():.4f}) def test(): model.eval() correct 0 total 0 with torch.no_grad(): for data, target in test_loader: data, target data.to(device), target.to(device) output model(data) pred output.argmax(dim1) correct pred.eq(target).sum().item() total target.size(0) accuracy 100.0 * correct / total print(fTest Accuracy: {accuracy:.2f}%) return accuracy9.4 执行训练if __name__ __main__: for epoch in range(1, 6): train(epoch) test()这里只训练 5 个 epochMNIST 上通常可以达到 99% 以上的测试准确率。想要更高精度可以增加 epoch 数或调整学习率。9.5 运行结果解读训练过程中会看到 Loss 逐步下降。每个 epoch 结束后测试准确率会上升。如果训练集准确率高但测试准确率低说明模型过拟合如果两者都低说明模型欠拟合需要增加模型容量或训练轮数。10. 训练效果验证与性能观察10.1 如何判断模型训练成功判断标准有两个损失值是否下降测试准确率是否达到预期。MNIST 是一个很简单的数据集LeNet-5 简化版达到 98% 以上是正常水平。如果低于 90%优先检查数据预处理是否归一化以及网络输出层维度是否正确。10.2 观察资源和训练速度在 CPU 上训练 MNIST每个 epoch 大约几十秒到几分钟不等取决于机器性能。使用 GPU 时会明显更快。如果训练时显存不足可以减小 batch_size比如从 64 改为 32。如果发现 CPU 占用率很高可以适当减少 DataLoader 的num_workers。train_loader DataLoader(datasettrain_dataset, batch_size64, shuffleTrue, num_workers2)在 Jupyter Notebook 中可以用!nvidia-smi观察 GPU 显存占用在纯 CPU 环境下用系统任务管理器即可。10.3 不同参数对训练的影响学习率过大Loss 震荡不下降。学习率过小训练速度极慢。batch_size 过大占用内存高收敛可能变慢。卷积核数量少特征提取能力不足。网络层数深但数据少容易过拟合。建议先固定一个配置训练 5 个 epoch再逐步调整单一变量观察准确率变化。11. 常见问题与排查方法问题现象可能原因排查方式解决方案安装 PyTorch 失败Python 版本与安装源不兼容或缺少依赖查看报错信息确认 Python 版本使用 Anaconda 创建 Python 3.9 环境或按官网命令安装下载 MNIST 数据集失败网络无法访问原始下载地址检查网络或查看本地是否已有数据文件手动下载数据放入./data目录或配置镜像源训练 Loss 不下降学习率设置不当或数据未归一化打印前几个 batch 的输出检查学习率调低学习率使用transforms.Normalize归一化测试准确率低网络结构错误、数据标签错位、过拟合用少量样本打印预测值与真实值核对数据集标签减少网络层数或增加 Dropout显存不足batch_size 过大或模型过大查看torch.cuda.mem_get_info()减小 batch_size使用更小的卷积核训练速度极慢CPU 推理本身较慢或 DataLoader 多进程配置不当查看任务管理器资源占用减少num_workers或改用 GPU 训练模型输出全是同一类别最后一层维度没写对或标签索引从 1 开始打印output.shape和target的唯一值确保输出节点数等于类别数MNIST 标签范围是 0-9反序列化模型报错保存和加载时类定义不一致检查网络类是否在加载脚本中重新定义使用model.state_dict()保存权重加载时先实例化模型12. 最佳实践与学习建议12.1 动手前先画结构图读 LeNet-5 或 AlexNet 时不要只是看文字。准备一张纸把输入尺寸、卷积核大小、步长、填充、输出尺寸一层一层写下来。能够独立推导出每一层输出形状才算真正理解了网络结构。12.2 从最小网络开始实验第一次跑训练不要一上来就复制 AlexNet。先用一个只有两层卷积和一层全连接的小网络跑通 MNIST确认数据流水线没问题再逐步加深网络。保持训练、测试、日志记录这套流程不变后面换模型只是替换网络类。12.3 用固定随机种子保证可复现训练结果会有一定随机性建议在最开始设置随机种子。import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)12.4 模型、日志、结果分目录管理建议按如下结构组织实验目录。cnn_tutorial/ ├── data/ # 原始数据集 ├── models/ # 保存模型权重 ├── logs/ # 训练日志 ├── outputs/ # 预测结果图 └── train.py # 训练脚本这样有利于后续对比不同实验效果。12.5 注意数据版权与使用边界如果你后续把 CNN 应用到真实业务比如人脸识别、医学图像、监控视频需要特别注意数据来源是否合法、是否获得授权。CNN 只是工具数据合规和隐私保护是更高的前置约束。13. 总结与下一步这次我们把卷积神经网络的核心链路完整过了一遍卷积层提取特征池化层压缩特征图激活函数引入非线性全连接层完成分类LeNet-5 和 AlexNet 则是这一思路的经典落地方案。你应该已经清楚每一层的输入输出尺寸怎么算也拿到了一份可以在本机运行的 PyTorch 训练代码。下一步建议按顺序做三件事先把文章中的SimplifiedLeNet5代码跑通确认训练与测试流程没问题。修改网络结构试一下把第一个卷积层从 6 个卷积核改成 16 个观察准确率变化。尝试把 MNIST 换成 CIFAR-10 数据集并使用 AlexNet 或简化版 AlexNet 训练感受图像分类任务从简单到复杂的差异。最容易踩的坑是数据尺寸不匹配。CIFAR-10 图像是 32x32x3而我们的简化 LeNet-5 输入是 1 通道 28x28直接用于 CIFAR-10 会报错。遇到这种情况别慌根据报错信息调整输入通道数和全连接层输入维度即可。CNN 的学习价值不仅在于一个模型能跑多准更在于它让你理解深度学习网络到底是怎么处理数据的。把这篇文章里的结构吃透再去学 ResNet、YOLO、Transformer 视觉模型会顺畅很多。建议收藏备用动手把代码跑起来。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门