☰
PyTorch实现AlexNet:逐行注释与实战指南
2026/10/26 2:09:58 网站建设 项目流程

简介:这份资源面向深度学习初学者与计算机视觉入门者,提供基于PyTorch实现的AlexNet完整代码与配套数据集,帮助读者从零理解这一ImageNet里程碑模型的网络结构与训练流程。压缩包共约2000个文件,以两万余张jpg图像数据为主体,辅以4个py脚本分别承担数据划分、模型定义、训练与预测任务,另含xml标注、pth权重及项目配置等文件,整体约975MB,目录划分清晰便于按模块查阅。已有3798人学习下载,说明其作为入门实战素材具备一定参考价值。代码中卷积层、池化层、全连接层的参数配置与ReLU、局部响应归一化等细节均有超详细注释,读者可据此掌握数据增强、优化器与损失函数设置、训练循环组织及预训练模型加载预测的完整链路,并顺带理解VGG、ResNet等后续网络的共通设计思路。

1. 从一份带注释的 AlexNet 源码说起:为什么它值得你花一个下午跑通

很多人学卷积神经网络,第一段能跑通的代码不是 LeNet 就是 AlexNet。但真正把 AlexNet 从头到尾写一遍、并且每一行都写清楚注释的项目并不多。这份资源就是干这个的:一份 PyTorch 实现的 AlexNet 完整代码,注释细到每个卷积层的输入输出尺寸、每个池化窗口为什么这么设、全连接层维度怎么算出来的,还附带了一份可以直接加载训练的数据集。它不是让你复制粘贴跑个 demo 就完事,而是让你在读懂每一行的基础上,能自己改结构、换数据、调超参。适合两类人:刚学完 CNN 理论想找一份能逐行对照的代码的新手,以及想拿 AlexNet 当 backbone 做迁移学习、需要一个干净起点的熟手。下面我按「这项目长什么样 → 怎么跑起来 → 核心代码怎么读 → 怎么改」的顺序拆一遍。

2. 项目结构与技术选型:一个能逐行对照的 PyTorch 工程

2.1 目录组织与文件职责

这份资源的目录结构不复杂,但每个文件的分工很明确,拿到手先扫一眼就知道从哪进。常见做法是下面这种布局:

alexnet-project/ ├── model.py # AlexNet 网络结构定义,逐层带注释 ├── train.py # 训练主循环:数据加载、前向、反向、保存 ├── predict.py # 单张/批量图片推理脚本 ├── dataset/ # 数据集目录,按类别分子文件夹 │ ├── class_a/ │ └── class_b/ ├── utils.py # 数据预处理、尺寸变换、可视化辅助 └── requirements.txt # 依赖清单

model.py是整个项目的核心,网络结构全在这里,注释密度最高。train.py负责把数据喂进去、把梯度传回来,是理解训练流程的入口。predict.py单独拎出来,方便你训练完直接拿一张图验证效果。dataset/按类别分文件夹,配合torchvision.datasets.ImageFolder就能直接读,不用自己写 Dataset 类。utils.py放的是那些每个脚本都要用、但又不值得单独成文件的函数,比如统一图片尺寸的 transform 构造。

提示:如果你拿到的压缩包里dataset/是空的或者只有说明文件,先确认数据是否需要单独解压到该目录,别急着跑train.py,否则会在 DataLoader 阶段报找不到文件。

2.2 技术栈与依赖版本

技术栈很纯粹:Python + PyTorch + torchvision,没有引入额外的训练框架。requirements.txt里通常就三行左右:

torch>=1.10 torchvision>=0.11 Pillow>=8.0

版本不用卡太死,AlexNet 结构简单,PyTorch 1.x 到 2.x 都能跑。但有一个点要注意:如果你用的是比较新的 torchvision,transforms.Normalize的均值和方差参数还是老样子,别被网上一些改过的教程带偏。我一般会先pip install -r requirements.txt,然后python -c "import torch; print(torch.__version__)"确认装上了,再往下走。

2.3 数据集的格式约定

数据集这块,项目默认走的是 ImageFolder 的约定:每个类别一个子文件夹,文件夹名就是类别名。这种格式的好处是不用写任何标注文件,ImageFolder会自动扫描目录、给每个类别分配一个索引。常见做法是在train.py里这样构造:

from torchvision import datasets, transforms transform = transforms.Compose([ transforms.Resize((224, 224)), # AlexNet 原生输入 224x224 transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_dataset = datasets.ImageFolder(root='dataset/train', transform=transform) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=32, shuffle=True)

Resize((224, 224))是硬要求,AlexNet 第一层卷积的输入尺寸就是按 224 设计的,尺寸不对后面全连接层的维度会直接对不上。Normalize用的是 ImageNet 的统计值,这是迁移学习场景下的标准操作,即使你的数据集不是 ImageNet,用这组值也不会出大问题。batch_size=32是个保守值,显存够可以往上加,不够就降到 16 或 8。

3. 把代码跑起来:从环境到第一个 epoch 的完整步骤

3.1 环境准备与依赖安装

先建虚拟环境,别在系统 Python 里直接装,这是血泪经验,版本冲突排查起来很痛苦:

python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt

装完之后验证一下 CUDA 是否可用,因为 AlexNet 虽然不大,但用 CPU 训练还是慢得让人想放弃:

import torch print(torch.cuda.is_available()) # True 表示能用 GPU print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU only")

如果输出False,先别怀疑代码,大概率是 PyTorch 装成了 CPU 版本。去官网对照你的 CUDA 版本重新装一次,别硬扛。

3.2 训练脚本的关键参数

train.py里通常有几个可以直接在命令行改的参数,常见做法是用argparse暴露出来:

import argparse parser = argparse.ArgumentParser() parser.add_argument('--epochs', type=int, default=10) parser.add_argument('--batch-size', type=int, default=32) parser.add_argument('--lr', type=float, default=0.001) parser.add_argument('--data-dir', type=str, default='dataset/train') args = parser.parse_args()

epochs默认 10 是个合理的起点,数据集小的话 5 个 epoch 就能看出趋势。lr=0.001配 Adam 优化器是常见组合,如果你换成 SGD,可能要调到 0.01 并加 momentum。>python train.py --epochs 10 --batch-size 32 --lr 0.001

跑起来之后终端会打印每个 epoch 的 loss 和 accuracy,看到 loss 在下降就说明链路通了。

3.3 推理验证:拿一张图看结果

训练完会保存一个.pth权重文件,predict.py负责加载它做推理。核心逻辑是加载模型、加载权重、切到 eval 模式、前向一次:

import torch from model import AlexNet from PIL import Image from torchvision import transforms model = AlexNet(num_classes=2) model.load_state_dict(torch.load('alexnet.pth', map_location='cpu')) model.eval() img = Image.open('test.jpg').convert('RGB') transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) input_tensor = transform(img).unsqueeze(0) # 加 batch 维度 with torch.no_grad(): output = model(input_tensor) pred = output.argmax(dim=1).item() print(f'预测类别索引: {pred}')

num_classes必须和训练时一致,这是最容易翻车的地方,训练用 2 类、推理写 10 类,加载权重时直接报维度不匹配。unsqueeze(0)是给单张图补一个 batch 维度,因为模型期望输入是[N, C, H, W]。torch.no_grad()关掉梯度计算,推理时省显存也更快。

4. 核心模块拆解:AlexNet 每一层到底在干什么

4.1 卷积层堆叠与尺寸变化

model.py里最值得逐行读的就是特征提取部分。AlexNet 的结构是 5 个卷积层 + 3 个全连接层,注释详细的项目会把每层的输入输出尺寸标出来。典型实现长这样:

import torch.nn as nn class AlexNet(nn.Module): def __init__(self, num_classes=1000): super(AlexNet, self).__init__() self.features = nn.Sequential( # 输入 [3, 224, 224] -> 输出 [64, 55, 55] nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2), nn.ReLU(inplace=True), # [64, 55, 55] -> [64, 27, 27] nn.MaxPool2d(kernel_size=3, stride=2), # [64, 27, 27] -> [192, 27, 27] nn.Conv2d(64, 192, kernel_size=5, padding=2), nn.ReLU(inplace=True), # [192, 27, 27] -> [192, 13, 13] 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, num_classes), )

第一层Conv2d(3, 64, kernel_size=11, stride=4, padding=2)是大卷积核加大步长,目的是快速把 224 的空间尺寸压到 55,同时提取低级特征。stride=4意味着每次滑 4 个像素,计算量比 stride=1 小很多。padding=2是为了让尺寸计算对得上,公式是(224 - 11 + 2*2) / 4 + 1 = 55,注释里标尺寸就是为了让你能自己验算。inplace=True的 ReLU 是省内存的常见做法,但如果你要做梯度可视化,得把它关掉,否则原始输入被覆盖了。

4.2 全连接层维度是怎么算出来的

nn.Linear(256 * 6 * 6, 4096)里的256 * 6 * 6不是拍脑袋写的,是前面卷积层输出展平后的结果。最后一个池化层输出[256, 6, 6],展平就是 9216 维。这个数字如果对不上,说明你改了前面的卷积层但没同步改这里,运行时会报mat1 and mat2 shapes cannot be multiplied。我一般会在forward里加一句打印:

def forward(self, x): x = self.features(x) # print(x.shape) # 调试时打开,确认展平前尺寸 x = x.view(x.size(0), -1) # 保留 batch 维度,其余展平 x = self.classifier(x) return x

x.view(x.size(0), -1)是标准写法,x.size(0)是 batch 大小,-1让 PyTorch 自动算剩余维度。别用x.view(-1),那样会把 batch 也展平,后面全连接层直接崩。

4.3 Dropout 与训练/推理模式切换

classifier里两个nn.Dropout()默认丢弃率 0.5,作用是防止全连接层过拟合。但这里有个容易忽略的点:Dropout 在训练时生效、推理时自动关闭,前提是你正确调用了model.train()和model.eval()。训练循环里忘了写model.train(),Dropout 不生效,模型可能过拟合;推理时忘了model.eval(),结果会随机波动,同一个输入两次预测不一样。这是新手最常踩的坑之一,排查时先看这两行有没有写。

5. 避坑与排查:跑 AlexNet 时最容易翻车的几个地方

5.1 输入尺寸不匹配导致全连接层报错

现象:运行时报RuntimeError: mat1 and mat2 shapes cannot be multiplied。原因:图片没有 Resize 到 224x224,或者你改了卷积层但没改Linear的输入维度。解决:先在forward里打印x.shape,确认展平前是[N, 256, 6, 6],不是的话回头检查 transform 和卷积层参数。

5.2 类别数不一致导致加载权重失败

现象:load_state_dict报size mismatch for classifier.3.weight。原因:训练时num_classes=2,推理时AlexNet(num_classes=10)。解决:把推理脚本里的num_classes改成和训练一致,或者从权重文件里反推类别数。

5.3 显存不足导致训练中断

现象:跑到一半报CUDA out of memory。原因:batch_size太大,或者没及时释放中间变量。解决:先把batch_size减半试,还不行就减到 8;训练循环里确认没有把 loss 或输出累积在列表里不释放。

5.4 数据路径写死导致换机器跑不了

现象:换台机器或换个目录,ImageFolder报FileNotFoundError。原因:路径写成了绝对路径。解决:统一用相对路径,或者用argparse把--data-dir暴露出来,启动时传进去。

6. 二次开发:把 AlexNet 改成你自己的 backbone

6.1 换数据集与调整类别数

拿到这份代码后最常见的改造就是换自己的数据。步骤不复杂:把数据按类别分好文件夹放进dataset/,然后改num_classes。但有个细节要注意,如果你数据量很小(比如每类几十张),直接从头训练 AlexNet 会严重过拟合,这时候应该加载预训练权重做微调:

import torchvision.models as models model = models.alexnet(weights='IMAGENET1K_V1') # 替换最后一层,适配你的类别数 model.classifier[6] = nn.Linear(4096, your_num_classes)

这样前面的卷积层保留 ImageNet 学到的特征,只训练最后的分类头,小数据集上效果会好很多。我一般会先把features部分冻结,只训classifier,跑几个 epoch 看 loss 降不降,降得动再考虑解冻部分卷积层。

6.2 改网络结构的验证方法

如果你想改卷积层数量或通道数,改完一定要做一次前向 shape 验证,别直接开训。写个最小测试:

model = AlexNet(num_classes=5) dummy = torch.randn(1, 3, 224, 224) out = model(dummy) print(out.shape) # 期望 [1, 5]

这一步能在几秒内暴露维度错误,比训练到一半崩掉省时间。改结构时记住一个原则:每改一层卷积,就重新算一遍后面所有层的输入尺寸,全连接层的in_features跟着改。

6.3 一个实用的调试习惯

从那以后我每次改完网络结构,都强制先跑一遍 dummy 输入的前向,确认输出 shape 对了再碰数据。这个习惯帮我省掉了大量「训练到第 3 个 epoch 才报维度错」的时间。AlexNet 结构不深,但层与层之间的尺寸耦合很紧,一处改动往往牵连后面好几层,提前验证比事后排查划算得多。希望这份带注释的代码能帮你把 CNN 的每一层真正看明白,而不只是跑通一个 demo。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询