本文基于d2l项目内容整理,介绍深度学习历史上具有里程碑意义的AlexNet网络,包括其创新的架构设计、关键技术突破和实现方法。

1. AlexNet 的历史意义

深度学习领域的观点认为,深度学习模型应该能自动学习到数据的特征,而不依赖于传统机器学习中的手工特征提取。这些特征由多个神经网络层共同学习到:

分层特征学习机制:

  • 浅层特征:靠近输入层的特征表示通常用于检测图像的低级特征(边缘、颜色和纹理等)
  • 深层特征:位于网络更深层次、靠近输出层的特征表示,由多个低级特征抽象而来,用于表示形状结构、物体部件和语义信息等
  • 综合判别:通过隐藏的神经元表示图像的综合信息,实现分类与判别
AlexNet第一层学到的特征抽取器
AlexNet第一层学到的特征抽取器

AlexNet 的历史地位:

2012年提出的 AlexNet 使用8层卷积神经网络以巨大优势赢得了当年的 ImageNet 图像识别挑战赛,首次证明了模型能自动学习特征的能力,改变了计算机视觉研究的格局。

1.1 现代CNN网络发展概览

CNN网络架构的演进历程

本章节将介绍多个现代卷积神经网络和启发式概念,以培养深度学习领域的”直觉”:

AlexNet:首个击败传统计算机视觉模型的大型神经网络

VGG:使用重复的神经网络块构建更深的网络

NiN:重复使用卷积层,并用 1×1 卷积层替代全连接层

GoogLeNet:通过不同窗口大小的卷积层和最大池化层并行连结抽取信息

ResNet:通过残差块构建跨层的数据通道

DenseNet:以计算成本换取更好效果的稠密连接架构

以上网络架构都从 ImageNet 竞赛中脱颖而出。

深度网络设计的核心理念:

网络深度:堆叠更多的卷积层,学习更加抽象和高层次的特征

小卷积核:能有效增加网络深度,而不会显著增加计算负担

短路连接:如残差连接,使网络训练更加稳定,避免退化问题

批归一化:有助于更快的收敛,使得网络能够更深更稳定

合理的激活函数:ReLU 及其变种可加速训练并提高深层网络的表现

正则化手段:防止网络在训练集上的过拟合,提高泛化能力

层次化设计:逐步学习从低级到高级的特征,提高模型表达能力

数据增强:有效提高模型对不同场景和样本的适应能力

迁移学习:减少训练时间并提高模型在新任务上的性能


2. AlexNet 网络架构

AlexNet网络架构图
AlexNet网络架构图

AlexNet 的设计理念和网络架构与 LeNet 类似,但也有许多重要的不同:

2.1 架构特点对比

更深的网络结构、更大的通道数

  • AlexNet 由 5 个卷积层、2 个全连接隐藏层和 1 个全连接输出层组成
  • ImageNet 图像像素分辨率是 Fashion-MNIST 数据集的 10 倍多
  • 需要更大的 (11×11) 卷积窗口捕获目标特征

双数据流设计

  • 连接最后一个卷积层的全连接层共有 4096 个输出
  • 当时由于 GPU 显存的限制,需要采用双数据流的方式
  • 每个 GPU 只能计算一半的参数
  • 现在的 GPU 显存充裕,很少需要跨 GPU 分解模型

ReLU 激活函数

使用非饱和激活函数 ReLU 而不是 Sigmoid:

  • ReLU 函数的计算更简单,不需要复杂的求幂运算
  • ReLU 在正区间的梯度总是 1,避免梯度消失/爆炸问题
  • 使模型即使没有很好地初始化,也能有效地完成训练

多重正则化手段

  • 在权重衰减的基础上使用 Dropout 技术控制全连接层的模型复杂度
  • 通过翻转、裁切和变色对数据集进行图像的数据增强
  • 更大的样本量进一步减少了过拟合的问题
AlexNet详细架构图
AlexNet详细架构图

2.2 PyTorch 实现

数据集选择说明:
即使是现代 GPU,训练 ImageNet 的模型也需要数小时甚至数天的时间。因此,这里仍然使用 Fashion-MNIST 数据集,只是将像素直接增强 resize 至 224×224。

AlexNet 的网络架构用 PyTorch 框架实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import torch
from torch import nn, Tensor

class AlexNet(nn.Module):
def __init__(self, num_classes=10):
super(AlexNet, self).__init__()

# 特征提取部分 (卷积层)
self.features = nn.Sequential(
# 第一层卷积 + ReLU + 池化
nn.Conv2d(in_channels=1, out_channels=96, kernel_size=11, stride=4, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),

# 第二层卷积 + ReLU + 池化
nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),

# 第三层卷积 + ReLU
nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),

# 第四层卷积 + ReLU
nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),

# 第五层卷积 + ReLU + 池化
nn.Conv2d(in_channels=384, out_channels=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(in_features=6400, out_features=4096),
nn.ReLU(inplace=True),

nn.Dropout(p=0.5),
nn.Linear(in_features=4096, out_features=4096),
nn.ReLU(inplace=True),

nn.Linear(in_features=4096, out_features=num_classes)
)

def forward(self, x: Tensor) -> Tensor:
x = self.features(x)
x = torch.flatten(x, 1) # 展平操作
x = self.classifier(x)
return x

2.3 网络结构分析

让我们测试各层输出的维度变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def test_alexnet_architecture():
"""测试AlexNet各层的输出维度"""
# 创建模型和测试数据
model = AlexNet(num_classes=10)
test_input = torch.randn(1, 1, 224, 224)

print(f"输入形状: {test_input.shape}")
print("\n=== 特征提取部分 ===")

x = test_input
for i, layer in enumerate(model.features):
x = layer(x)
print(f"{type(layer).__name__} 输出形状: {tuple(x.shape)}")

# 展平操作
x = torch.flatten(x, 1)
print(f"\n展平后形状: {x.shape}")

print("\n=== 分类器部分 ===")
for i, layer in enumerate(model.classifier):
x = layer(x)
print(f"{type(layer).__name__} 输出形状: {tuple(x.shape)}")

# 运行测试
test_alexnet_architecture()
查看输出结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
输入形状: torch.Size([1, 1, 224, 224])

=== 特征提取部分 ===
Conv2d 输出形状: (1, 96, 54, 54)
ReLU 输出形状: (1, 96, 54, 54)
MaxPool2d 输出形状: (1, 96, 26, 26)
Conv2d 输出形状: (1, 256, 26, 26)
ReLU 输出形状: (1, 256, 26, 26)
MaxPool2d 输出形状: (1, 256, 12, 12)
Conv2d 输出形状: (1, 384, 12, 12)
ReLU 输出形状: (1, 384, 12, 12)
Conv2d 输出形状: (1, 384, 12, 12)
ReLU 输出形状: (1, 384, 12, 12)
Conv2d 输出形状: (1, 256, 12, 12)
ReLU 输出形状: (1, 256, 12, 12)
MaxPool2d 输出形状: (1, 256, 5, 5)

展平后形状: torch.Size([1, 6400])

=== 分类器部分 ===
Dropout 输出形状: (1, 6400)
Linear 输出形状: (1, 4096)
ReLU 输出形状: (1, 4096)
Dropout 输出形状: (1, 4096)
Linear 输出形状: (1, 4096)
ReLU 输出形状: (1, 4096)
Linear 输出形状: (1, 10)

网络结构特点分析:

  • 特征提取:通过5个卷积层逐步提取特征,通道数从1增加到最大384,最后降至256
  • 空间压缩:输入224×224经过卷积和池化操作压缩至5×5
  • 特征向量:最终特征图展平为6400维的特征向量
  • 分类决策:通过3个全连接层(4096→4096→10)完成最终分类

3. 模型训练与评估

3.1 训练工具集成

为了便于复用,我们将数据加载函数和训练器类组织到 training_tools.py 中:

查看完整的训练工具代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import os
from typing import Literal, Tuple
import torch
import torchvision
from torch import Tensor, device, nn, optim
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

def fashionMNIST_loader(batch_size, *, resize=None) -> Tuple[DataLoader, DataLoader]:
"""Fashion-MNIST数据加载器"""
transform_list = [
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
]
if resize:
transform_list.insert(0, transforms.Resize(size=resize))

transform_pipe = transforms.Compose(transform_list)

train_dataset = torchvision.datasets.FashionMNIST(
root='~/DataSets', train=True, transform=transform_pipe, download=True)
test_dataset = torchvision.datasets.FashionMNIST(
root='~/DataSets', train=False, transform=transform_pipe, download=True)

train_loader = DataLoader(train_dataset, batch_size, shuffle=True, num_workers=4)
test_loader = DataLoader(test_dataset, batch_size, shuffle=False, num_workers=4)

return train_loader, test_loader

class Trainer:
"""通用训练器类"""
def __init__(self, model, train_loader, test_loader, criterion, optimizer, device):
self.model = model.to(device)
self.train_loader = train_loader
self.test_loader = test_loader
self.criterion = criterion
self.optimizer = optimizer
self.device = device
self.writer = SummaryWriter()

def _run_epoch(self, data_loader, mode='train'):
is_train = mode == 'train'
self.model.train() if is_train else self.model.eval()

total_loss = correct_count = total_count = 0

with torch.set_grad_enabled(is_train):
for features, labels in data_loader:
features = features.to(self.device)
labels = labels.to(self.device)

outputs = self.model(features)
loss = self.criterion(outputs, labels)

if is_train:
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()

total_loss += loss.item()
_, predicted = outputs.max(1)
total_count += labels.size(0)
correct_count += predicted.eq(labels).sum().item()

accuracy = 100. * correct_count / total_count
avg_loss = total_loss / len(data_loader)
return accuracy, avg_loss

def train(self, epochs):
for epoch in range(epochs):
train_acc, train_loss = self._run_epoch(self.train_loader, 'train')
test_acc, test_loss = self._run_epoch(self.test_loader, 'eval')

self.writer.add_scalar('Loss/Train', train_loss, epoch)
self.writer.add_scalar('Loss/Test', test_loss, epoch)
self.writer.add_scalar('Accuracy/Train', train_acc, epoch)
self.writer.add_scalar('Accuracy/Test', test_acc, epoch)

print(f'Epoch [{epoch+1:3d}/{epochs}] '
f'Train Loss: {train_loss:.4f}, Train Acc: {train_acc:5.2f}%, '
f'Test Loss: {test_loss:.4f}, Test Acc: {test_acc:5.2f}%')

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.writer.close()

3.2 训练示例

现在可以很方便地训练、评估 AlexNet 模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import torch
from torch import nn, optim
from training_tools import fashionMNIST_loader, Trainer

def train_alexnet():
"""训练AlexNet模型"""
# 超参数设置
BATCH_SIZE = 128
EPOCHS = 10 # 为了演示,减少训练轮数
LEARNING_RATE = 0.001

# 创建模型和数据加载器
model = AlexNet(num_classes=10)
train_loader, test_loader = fashionMNIST_loader(BATCH_SIZE, resize=224)

# 损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

print(f"使用设备: {device}")
print(f"模型参数数量: {sum(p.numel() for p in model.parameters()):,}")

# 开始训练
with Trainer(model, train_loader, test_loader, criterion, optimizer, device) as trainer:
trainer.train(EPOCHS)

if __name__ == '__main__':
train_alexnet()

3.3 训练结果分析

查看训练过程详细输出
1
2
3
4
5
6
7
8
9
10
11
12
13
使用设备: cuda
模型参数数量: 61,100,840

Epoch [ 1/10] Train Loss: 2.2980, Train Acc: 18.95%, Test Loss: 2.2814, Test Acc: 30.81%
Epoch [ 2/10] Train Loss: 1.3385, Train Acc: 51.70%, Test Loss: 0.7708, Test Acc: 71.34%
Epoch [ 3/10] Train Loss: 0.6948, Train Acc: 73.89%, Test Loss: 0.6682, Test Acc: 75.69%
Epoch [ 4/10] Train Loss: 0.6026, Train Acc: 77.29%, Test Loss: 0.5620, Test Acc: 78.50%
Epoch [ 5/10] Train Loss: 0.5429, Train Acc: 79.51%, Test Loss: 0.5525, Test Acc: 78.86%
Epoch [ 6/10] Train Loss: 0.5013, Train Acc: 81.22%, Test Loss: 0.4786, Test Acc: 82.20%
Epoch [ 7/10] Train Loss: 0.4650, Train Acc: 82.46%, Test Loss: 0.4660, Test Acc: 82.68%
Epoch [ 8/10] Train Loss: 0.4402, Train Acc: 83.53%, Test Loss: 0.4500, Test Acc: 83.57%
Epoch [ 9/10] Train Loss: 0.4170, Train Acc: 84.50%, Test Loss: 0.4054, Test Acc: 84.86%
Epoch [ 10/10] Train Loss: 0.3973, Train Acc: 85.22%, Test Loss: 0.3984, Test Acc: 85.24%
AlexNet训练过程损失和准确率曲线
AlexNet训练过程损失和准确率曲线

训练结果分析:

  • 收敛速度:AlexNet 在 Fashion-MNIST 上收敛很快,第2轮就达到了71%的测试准确率
  • 最终性能:10轮训练后达到约85%的测试准确率
  • 模型复杂度:AlexNet 拥有约6100万个参数,对 Fashion-MNIST 这样的简单数据集来说确实过于复杂
  • 过拟合风险:训练准确率和测试准确率之间存在一定差距,说明存在轻微过拟合

AlexNet 的强大表达能力使其能快速学习简单的分类任务

大型网络在小数据集上容易过拟合,需要合适的正则化策略

Dropout 和数据增强等技术有效缓解了过拟合问题


总结

本文详细介绍了深度学习历史上的里程碑网络 AlexNet:

  1. 历史意义:2012年首次证明深度神经网络在图像识别任务上的强大能力
  2. 关键创新:ReLU激活函数、Dropout正则化、数据增强、GPU并行计算
  3. 架构设计:5个卷积层 + 3个全连接层的经典深度网络结构
  4. 技术影响:开启了深度学习在计算机视觉领域的黄金时代

AlexNet 不仅在技术上具有重要创新,更重要的是它证明了深度学习的潜力,为后续更深更复杂的网络架构奠定了基础,是深度学习发展史上不可忽视的重要里程碑。