AI入门教程03.03:实现AlexNet

本文实现复杂一点的AlexNet卷积神经网络。网络结构为

cnn

模型开发

将神经网络模型转换为对应代码的基本流程。

硬件选择

根据硬件情况选择加速方式

1
2
3
4
if torch.cuda.is_available():
device = torch.device("cuda") # 如果GPU可用,则使用CUDA
else:
device = torch.device("cpu") # 如果GPU不可用,则使用CPU

加载数据集并进行预处理

缩放、裁剪、归一化数据集中的数据。

MNIST数据集图片大小相同,这一步跳过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 加载MNIST数据集的训练集
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=transforms.ToTensor(),
)
# 加载MNIST数据集的测试集
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=transforms.ToTensor(),
)

# batch大小
batch_size = 64

# 创建dataloader
train_dataloader = torch.utils.data.DataLoader(training_data, batch_size=batch_size)
test_dataloader = torch.utils.data.DataLoader(test_data, batch_size=batch_size)

如果指定位置没有数据集就自动下载。

定义模型

根据神经网络模型定义编写代码

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
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Flatten(),
nn.Linear(256*6*6, 4096),
#nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
#nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10),
)

def forward(self, img):
feature = self.conv(img)
#output = self.fc(feature.view(img.shape[0], -1))
#x = nn.flatten(feature)
output = self.fc(feature)
return output

全连接使用Linear函数,只要是256* 6* 6->4096->2048->10

全连接后使用激活函数获得数据。

初始化模型、损失函数和优化器

1
2
3
4
5
# 实例化模型并定义损失函数和优化器
net = Net().to(device)
criterion = nn.CrossEntropyLoss() # 交叉熵损失函数适合分类任务
optimizer = torch.optim.SGD(net.parameters(), lr=0.01)
#optimizer = optim.Adam(model.parameters(), lr=learning_rate)

训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for epoch in range(10):
index = 0
for i,inputs, labels in train_dataloader:
inputs, labels = inputs.to(device), labels.to(device)

# 前向传播
outputs = net(inputs)
loss = criterion(outputs, labels)

# 反向传播和优化
optimizer.zero_grad() #梯度归零
loss.backward() #反向传播
optimizer.step() #优化
index+=1

print(f'Epoch [{epoch}], Setp [{index}/{len(train_dataloader)}], Loss: {loss.item():.4f}')

预测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 加载模型并测试
correct = 0
total = 0
net.eval()
with torch.no_grad():
for inputs, labels in test_dataloader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = net(inputs)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()

# 结果评估
print('Accuracy: %d %%' % (100 * correct / total))

运行

运行输出为

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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms

if torch.cuda.is_available():
device = torch.device("cuda") # 如果GPU可用,则使用CUDA
else:
device = torch.device("cpu") # 如果GPU不可用,则使用CPU

# 定义神经网络模型
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Flatten(),
nn.Linear(256*6*6, 4096),
#nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
#nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10),
)

def forward(self, img):
feature = self.conv(img)
#output = self.fc(feature.view(img.shape[0], -1))
#x = nn.flatten(feature)
output = self.fc(feature)
return output

# 加载MNIST数据集的训练集
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=transforms.ToTensor(),
)
# 加载MNIST数据集的测试集
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=transforms.ToTensor(),
)

# batch大小
batch_size = 64

# 创建dataloader
train_dataloader = torch.utils.data.DataLoader(training_data, batch_size=batch_size)
test_dataloader = torch.utils.data.DataLoader(test_data, batch_size=batch_size)

# 实例化模型并定义损失函数和优化器
net = Net().to(device)
#print(net)
#params = list(net.parameters())
#print(len(params))
#print(params[0].size())#conv1的权重参数
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)

# 加载数据并训练
for epoch in range(20):
index=0
for inputs, labels in train_dataloader:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
index+=1
print(f'Epoch [{epoch}], Setp [{index}/{len(train_dataloader)}],Loss: {loss.item():.4f}')

# 加载模型并测试
net.eval()
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in test_dataloader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = net(inputs)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()

# 结果评估
print('Accuracy: %d %%' % (100 * correct / total))

AI入门教程03.03:实现AlexNet
https://blog.jackeylea.com/ai/how-to-build-alexnet-nn/
作者
JackeyLea
发布于
2025年1月7日
许可协议