在 PyTorch 中实现 AlexNet 比想象中简单,就像用乐高积木按图纸搭建一样。我们可以分步骤来实现,从网络结构到训练过程,每一步都很直观。
一、先明确 AlexNet 的核心结构(复习)
AlexNet 的经典结构可以总结为:
输入(224×224彩色图) → 5个卷积层(带ReLU+池化) → 3个全连接层(带ReLU+Dropout) → 输出(1000类)
它的关键特点是:用 ReLU 激活函数、重叠池化、Dropout 防止过拟合,以及处理 3 通道彩色图。
二、PyTorch 实现 AlexNet 的步骤
步骤 1:导入必要的库
和实现 LeNet-5 类似,先准备好 PyTorch 的工具:
import torch # 核心库
import torch.nn as nn # 神经网络层
import torch.optim as optim # 优化器
from torch.utils.data import DataLoader # 数据加载器
from torchvision import datasets, transforms # 图像数据处理
步骤 2:定义 AlexNet 网络结构
在 PyTorch 中,我们创建一个继承nn.Module
的类,重写__init__
(定义层)和forward
(定义数据流向)方法。
class AlexNet(nn.Module):
def __init__(self, num_classes=1000):
super(AlexNet, self).__init__()
# 卷积部分:5个卷积层 + 池化层
self.features = nn.Sequential(
# 卷积层1:输入3通道(RGB),输出96通道,11×11卷积核,步长4
nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=2),
nn.ReLU(inplace=True), # ReLU激活函数
# 池化层1:3×3重叠池化,步长2(窗口重叠1像素)
nn.MaxPool2d(kernel_size=3, stride=2),
# 卷积层2:输入96,输出256,5×5卷积核,padding=2
nn.Conv2d(96, 256, kernel_size=5, padding=2),
nn.ReLU(inplace=True),
# 池化层2:3×3重叠池化,步长2
nn.MaxPool2d(kernel_size=3, stride=2),
# 卷积层3:输入256,输出384,3×3卷积核,padding=1
nn.Conv2d(256, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
# 卷积层4:输入384,输出384,3×3卷积核,padding=1
nn.Conv2d(384, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
# 卷积层5:输入384,输出256,3×3卷积核,padding=1
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
# 池化层3:3×3重叠池化,步长2
nn.MaxPool2d(kernel_size=3, stride=2),
)
# 全连接部分:3个全连接层 + Dropout
self.classifier = nn.Sequential(
# Dropout层:随机关闭50%神经元,防止过拟合
nn.Dropout(p=0.5),
# 全连接层1:输入是256×6×6(池化后的特征),输出4096
nn.Linear(256 * 6 * 6, 4096),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
# 全连接层2:输入4096,输出4096
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
# 输出层:输入4096,输出类别数(默认1000)
nn.Linear(4096, num_classes),
)
# 定义数据流向
def forward(self, x):
x = self.features(x) # 先经过卷积部分
x = x.view(x.size(0), 256 * 6 * 6) # 拉平特征图(batch_size, 256*6*6)
x = self.classifier(x) # 再经过全连接部分
return x
简单解释:
nn.Sequential
:按顺序组合各层,数据依次流过。- 卷积层参数:
kernel_size
是卷积核大小,stride
是步长,padding
是边缘填充(让输出尺寸更合理)。 ReLU(inplace=True)
:直接修改输入数据,节省内存。Dropout(p=0.5)
:训练时随机 “关掉” 50% 的神经元,防止过拟合。
步骤 3:准备数据(以 ImageNet 为例,这里用简化版 CIFAR-10 演示)
ImageNet 数据集太大(120 万张图),我们用小型的 CIFAR-10(10 类物体)来演示,流程是一样的:
# 数据预处理:转换为Tensor+标准化+数据增强
transform = transforms.Compose([
transforms.Resize(256), # 缩放为256×256
transforms.RandomCrop(224), # 随机裁剪成224×224(数据增强)
transforms.RandomHorizontalFlip(), # 随机水平翻转(数据增强)
transforms.ToTensor(), # 转为Tensor
# 标准化(CIFAR-10的均值和标准差)
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
# 加载CIFAR-10数据集
train_dataset = datasets.CIFAR10(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.CIFAR10(
root='./data', train=False, download=True, transform=transform
)
# 批量加载数据
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=4)
test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=4)
数据增强的作用:通过随机裁剪、翻转等操作 “造” 更多训练数据,让模型更通用。
步骤 4:初始化模型、损失函数和优化器
- 模型:我们定义的 AlexNet(注意 CIFAR-10 是 10 类,所以输出层设为 10)。
- 损失函数:交叉熵损失(适合分类问题)。
- 优化器:SGD + 动量(AlexNet 原文用的优化器)。
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 用GPU加速
model = AlexNet(num_classes=10).to(device) # 初始化模型,输出10类
criterion = nn.CrossEntropyLoss() # 交叉熵损失
# 优化器:学习率0.01,动量0.9,权重衰减0.0005(防止权重过大)
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)
步骤 5:训练模型
训练过程和 LeNet-5 类似,都是 “预测→算损失→调参数” 的循环,但 AlexNet 更大,需要训练更久:
def train(model, train_loader, criterion, optimizer, epoch):
model.train() # 训练模式
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device) # 数据放GPU/CPU
optimizer.zero_grad() # 清空梯度
output = model(data) # 模型预测
loss = criterion(output, target) # 计算损失
loss.backward() # 反向传播算梯度
optimizer.step() # 更新参数
# 打印进度
if batch_idx % 100 == 0:
print(f'Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.6f}')
步骤 6:测试模型效果
用测试集评估模型准确率:
def test(model, test_loader):
model.eval() # 评估模式(关闭Dropout)
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)
_, predicted = torch.max(output.data, 1) # 取概率最大的类别
total += target.size(0)
correct += (predicted == target).sum().item()
print(f'Test Accuracy: {100 * correct / total:.2f}%')
步骤 7:开始训练和测试
AlexNet 比较深,需要多训练几轮(这里示例训练 10 轮):
for epoch in range(1, 11):
train(model, train_loader, criterion, optimizer, epoch)
test(model, test_loader)
运行后,你会看到准确率逐渐上升,训练 10 轮后在 CIFAR-10 上通常能达到 70%-80%(完整训练更多轮次会更高)。
三、完整代码总结
把上面的步骤整合起来,就是可直接运行的 AlexNet 实现:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# 1. 定义AlexNet网络结构
class AlexNet(nn.Module):
def __init__(self, num_classes=1000):
super(AlexNet, self).__init__()
# 卷积部分
self.features = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(96, 256, kernel_size=5, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(256, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(384, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
)
# 全连接部分
self.classifier = nn.Sequential(
nn.Dropout(p=0.5),
nn.Linear(256 * 6 * 6, 4096),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Linear(4096, num_classes),
)
def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), 256 * 6 * 6) # 拉平特征图
x = self.classifier(x)
return x
# 2. 准备CIFAR-10数据
transform = transforms.Compose([
transforms.Resize(256),
transforms.RandomCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
train_dataset = datasets.CIFAR10(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.CIFAR10(
root='./data', train=False, download=True, transform=transform
)
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=4)
test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=4)
# 3. 初始化模型、损失函数和优化器
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = AlexNet(num_classes=10).to(device) # CIFAR-10是10类
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)
# 4. 训练函数
def train(model, train_loader, criterion, optimizer, epoch):
model.train()
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()
if batch_idx % 100 == 0:
print(f'Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.6f}')
# 5. 测试函数
def test(model, test_loader):
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)
_, predicted = torch.max(output.data, 1)
total += target.size(0)
correct += (predicted == target).sum().item()
print(f'Test Accuracy: {100 * correct / total:.2f}%')
# 6. 开始训练和测试
for epoch in range(1, 11):
train(model, train_loader, criterion, optimizer, epoch)
test(model, test_loader)
四、关键知识点回顾
- 网络设计:5 个卷积层负责提取从简单到复杂的特征(边缘→纹理→物体部件),3 个全连接层负责最终分类。
- 核心技巧:ReLU 解决梯度消失,Dropout 防止过拟合,重叠池化保留更多细节,数据增强扩充训练数据。
- 实现要点:注意输入尺寸(224×224)、通道数(3 通道彩色图),以及全连接层的输入维度(256×6×6)。
运行这段代码,你就能亲手实现这个 “深度学习引爆者” 模型,感受它如何从像素中学习识别物体的能力!