AlexNet论文+复现

在2012年时候,由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton在ImageNet LSVRC-2010图像分类竞赛中提出的一种经典的卷积神经网络,由于他的出现和在 ImageNet 大规模视觉识别竞赛中取得了优异的成绩,把深度学习模型在比赛中的正确率提升到一个前所未有的高度,他的结果在测试集上取得 top-1错误率37.5%top-5错误率17.0%,显著优于此前最优结果。ILSVRC-2012版本模型进一步提升至 top-5错误率15.3%(对比第二名26.2%)。比第二名整整高了近11个百分点。论文我将从结构剖析、创新点分析以及根据pytorch进行代码复现

一、架构剖析

首先,输入的图片是RGB三通道,我们可以先不管输入图像的大小规格是怎样的,因为输入之后会对图像进行裁剪,给定一个矩形图像,我们首先将图像缩放,使得较短的边长为256,然后从结果图像中裁剪出中心的256×256区域将图像下采样(PS:高斯金字塔,下采样方向向下,关注的数据量减少,特征提取压缩降噪)到固定的256 × 256 × 3。下图就是截取的AlexNet的一个架构图。

AlexNet架构

1、前五层

AlexNet共有五个卷积层,每个卷积层都包含卷积核、偏置项、ReLU激活函数和局部响应归一化(LRN)模块。

  • 卷积层C1:使用96个尺寸为11 × 11 × 3的卷积核对224 × 224 × 3的输入图像进行滤波,步长为4个像素(这是卷积核图中相邻神经元感受野中心之间的距离)
  • 卷积层C2:以第一个卷积层的(响应归一化和池化)输出作为输入,并使用256个尺寸为5 × 5 × 48的卷积核对其进行滤波
  • 卷积层C3:有384个核,核大小为3 × 3 × 256,与卷积层C2的输出(归一化的,池化的)相连。
  • 卷积层C4:有384个核,核大小为3 × 3 × 192。
  • 卷积层C5:有256个核,核大小为3 × 3 × 192。卷积层C5与C3、C4层相比多了个池化,池化核size同样为3×3,stride为2。

第三、第四和第五个卷积层彼此连接,没有任何中间的池化或归一化层。

我们可看到C2到C3层、C5d到F6和F6到F7之间他们是交叉的。具体原因创新点的时候细说~

2、全连接层

全连接层F6、F7将所有的特征向量拉成一排生成4096个卷积核,加上最后一个1000-way softmax 进行分类,产生1000个类别预测的值。

二、创新点

1、双GPU训练

作者分成了两块 GTX 580 3GB GPU进行训练,值得注意的是,卷积层中只有C2到C3进行了交叉通讯信息共享,其他的卷积层都是独立的GPU在各自训练。但是到后期的时候,作者发现两个GPU所表现出的专业化分工。GPU 1上的卷积核在很大程度上与颜色无关,而GPU 2上的卷积核在很大程度上是颜色特定的。这种专业化分工在每次运行中都会发生,并且独立于任何特定的随机权重初始化(模GPU的重新编号)。

2、Dropout和数据增强

为了防止过拟合,AlexNet 引入了数据增强和 Dropout 技术。

数据增强分成了一下两种:

  • 从图像大小256上,以中心区域扣224大小的图,然后进行对图像进行旋转、翻转、裁剪等变换,增加训练数据的多样性,提高模型的泛化能力。对于AlexNet进行实现来说,数据增强是免费的,因为变换后的图片不需要存储与磁盘中,他们生成于CPU上,而此刻的GPU正在训练,这样同步进行
  • 第二种是PCB降维处理,改变训练图像中RGB通道的强度

Dropout 则是在训练过程中随机删除一定比例的神经元,强制网络学习多个互不相同的子网络,从而提高网络的泛化能力。Dropout简单来说就是在前向传播的时候,让某个神经元的激活值以一定的概率p停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征。

3、ReLU激活函数的使用

AlexNet 首次使用了修正线性单元(ReLU:Rectified Linear Units)这一非线性激活函数。相比于传统的 sigmoid 和 tanh 函数,ReLU 能够在保持计算速度的同时,有效地解决了梯度消失问题,从而使得训练更加高效。

ReLU

4、重叠最大池化

在CNN中使用重叠的最大池化。此前CNN中普遍使用平均池化,AlexNet全部使用最大池化,避免平均池化的模糊化效果。并且AlexNet中提出让步长比池化核的尺寸小,这样池化层的输出之间会有重叠和覆盖,提升了特征的丰富性。

5、局部响应归一化

局部响应归一化(Local Respone Normalization),对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。LRN通过在相邻卷积核生成的feature map之间引入竞争,从而有些本来在feature map中显著的特征在A中更显著,而在相邻的其他feature map中被抑制,这样让不同卷积核产生的feature map之间的相关性变小。

三、代码复现

1、定义网络模型

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
import torch
import torch.nn as nn


class AlexNet(nn.Module):
def __init__(self, config):
super(AlexNet, self).__init__()
self._config = config
# 定义卷积层和池化层
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.avgpool = nn.AdaptiveAvgPool2d((6, 6))
# 定义全连接层
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, self._config['num_classes']),
)

def forward(self, x):
x = self.features(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x

# 2、模型保存与模型加载
def save_model(self):
torch.save(self.state_dict(), self._config['model_name'])

# map_location 参数用于指定加载模型时使用的设备(如 CPU 或特定的 GPU)
def load_model(self, map_location):
state_dict = torch.load(self._config['model_name'], map_location=map_location)
self.load_state_dict(state_dict, strict=False)

2、数据集预处理

我们选择的CIFAR-10数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader


# 定义构造数据加载器的函数
def Construct_DataLoader(dataset, batch_size):
return DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True)


# 图像预处理
transform = transforms.Compose([
transforms.Resize(96),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])


# 加载数据集函数
def LoadCIFAR10(download=False):
# 加载数据集
train_dataset = torchvision.datasets.CIFAR10(root='../data', train=True, transform=transform, download=True)
test_dataset = torchvision.datasets.CIFAR10(root='../data', train=False, transform=transform)
return train_dataset, test_dataset

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
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
from torch.autograd import Variable


class Train(object):
# 初始化函数、配置参数、优化器和损失函数
def __init__(self, model, config):
self._model = model
self._config = config
self._optimizer = torch.optim.SGD(self._model.parameters(), lr=self._config['lr'],
momentum=self._config['momentum'], weight_decay=self._config['weight_decay'])
# 定义并初始化一个损失函数,用于在训练过程中计算模型的预测输出与真实标签之间的差异。
self.loss_func = nn.CrossEntropyLoss()

def _train_single_batch(self, images, labels):
y_predict = self._model(images)

loss = self.loss_func(y_predict, labels)
# 先将梯度清零,如果不清零,那么这个梯度就和上一个mini-batch有关
self._optimizer.zero_grad()
# 反向传播计算梯度
loss.backward()
# 梯度下降等优化器 更新参数
self._optimizer.step()
# 将loss的值提取成python的float类型
loss = loss.item()

# 计算训练精确度
# 这里的y_predict是一个多个分类输出,将dim指定为1,即返回每一个分类输出最大的值以及下标
_, predicted = torch.max(y_predict.data, dim=1)
return loss, predicted

def _train_an_epoch(self, train_loader, epoch):
"""
训练一个Epoch,即将训练集中的所有样本全部都过一遍
"""
# 设置模型为训练模式,启用dropout以及batch normalization
self._model.train()
total = 0
correct = 0

# 从DataLoader中获取小批量的num以及数据
for batch, (images, labels) in enumerate(train_loader):
images = Variable(images)
labels = Variable(labels)
if self._config['use_cuda'] is True:
images, labels = images.cuda(), labels.cuda()

loss, predicted = self._train_single_batch(images, labels)

# 计算训练精确度
total += labels.size(0)
correct += (predicted == labels.data).sum()

# print('[Training Epoch: {}] Batch: {}, Loss: {}'.format(epoch_num, batch, loss))
print('Training Epoch: {}, accuracy rate: {}%%'.format(epoch, correct / total * 100.0))

def train(self, train_dataset):
# 是否使用GPU加速
self.use_cuda()
for epoch in range(self._config['epoch']):
print('-' * 20 + ' Epoch {} starts '.format(epoch) + '-' * 20)
# 构造DataLoader
data_loader = DataLoader(dataset=train_dataset, batch_size=self._config['batch_size'], shuffle=True)
# 训练一个轮次
self._train_an_epoch(data_loader, epoch=epoch)

# 用于将模型和数据迁移到GPU上进行计算,如果CUDA不可用则会抛出异常
def use_cuda(self):
if self._config['use_cuda'] is True:
assert torch.cuda.is_available(), 'CUDA is not available'
torch.cuda.set_device(self._config['device_id'])
self._model.cuda()

# 保存训练好的模型
def save(self):
self._model.save_model()

4、模型训练

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
# 定义参数配置信息
alexnet_config = {
'epoch': 40, # 训练轮次数
'batch_size': 128, # 每个小批量训练的样本数量
'lr': 1e-3, # 学习率
'num_classes': 10, # 分类的类别数目
'device_id': 0, # 使用的GPU设备的ID号
'use_cuda': True, # 是否使用CUDA加速
'momentum': 0.9, # 动量
'weight_decay': 1e-4, # 权重衰减
'model_name': './AlexNet.model' # 保存模型的文件名
}

if __name__ == "__main__":
####################################################################################
# AlexNet 模型
####################################################################################
train_dataset, test_dataset = LoadCIFAR10(True)
# define AlexNet model
alexNet = AlexNet(alexnet_config)

####################################################################################
# 模型训练阶段
####################################################################################
# # 实例化模型训练器
trainer = Train(model=alexNet, config=alexnet_config)
# # 训练
trainer.train(train_dataset)
# # 保存模型
trainer.save()

####################################################################################
# 模型测试阶段
####################################################################################
alexNet.eval()
alexNet.load_model(map_location=torch.device('cpu'))
if alexnet_config['use_cuda']:
alexNet = alexNet.cuda()

correct = 0
total = 0
# 对测试集中的每个样本进行预测,并计算出预测的精度
for images, labels in Construct_DataLoader(test_dataset, alexnet_config['batch_size']):
images = Variable(images)
labels = Variable(labels)
if alexnet_config['use_cuda']:
images = images.cuda()
labels = labels.cuda()

y_pred = alexNet(images)
_, predicted = torch.max(y_pred.data, 1)
total += labels.size(0)
temp = (predicted == labels.data).sum()
correct += temp
print('Accuracy of the model on the test images: %.2f%%' % (100.0 * correct / total))

运行结果:

image-20250626210848187

鸣谢

很多参考这位博主:卷积神经网络经典回顾之AlexNet - 知乎


AlexNet论文+复现
http://example.com/2025/06/26/AlexNet论文/
作者
Alaskaboo
发布于
2025年6月26日
更新于
2025年7月25日
许可协议