AI入门教程03.02:LeNet卷积神经网络

本文介绍如何写一个LeNet卷积神经网络。网络结构为

cnn

使用MNIST数据集,内容为手写数字,1通道灰度图。

模型流程为

  • 将1通道32x32图片卷积为6通道28x28图片
  • 使用池化缩小图片
  • 将6通道14x14图片卷积为16通道10x10图片
  • 使用池化缩小图片
  • 将16通道5x5图片缩小为1通道宽1高120图片
  • 将120图片限制为1通道宽1高84图片
  • 将84图片输出对应0-9这10个结果

模型开发

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

硬件选择

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

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
# 定义神经网络模型
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 输入1通道,输出6通道,卷积核为5x5
self.conv1 = nn.Conv2d(1,6,5)
self.conv2 = nn.Conv2d(6,16,5)
self.conv3 = nn.Conv2d(16,120,4)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84,10)

def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = F.max_pool2d(x,(2,2))

# 精简代码
x = F.max_pool2d(F.relu(self.conv2(x)),2)
x = F.relu(self.conv3(x))

x = torch.flatten(x,1) #扁平化
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x

全连接使用Linear函数,只要是784->256->10

使用view函数将二维图片转换为一维数组。

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

conv2d 输出通道一般为32 64 128 256

池化

池化,也称为下采样(Subsampling),是一种降低数据维度的技术。在深度学习中,池化操作通常应用于卷积神经网络的卷积层之后,用于减小特征图的尺寸,从而降低模型的复杂度,减少计算量,并防止过拟合。池化操作的基本思想是将特征图划分为若干个子区域(一般为矩形),并对每个子区域进行统计汇总。

常用的有:

  • 最大池化(Max Pooling):在每个子区域内选择最大值作为该区域的输出。能够保留图像中的纹理特征,对边缘和角点等特征信息较为敏感。常用于图像分类、物体检测等任务中。
  • 平均池化(Average Pooling):计算每个子区域内所有值的平均值作为该区域的输出。能够保留图像的背景信息,对图像的整体特征进行平滑处理。在某些情况下,平均池化能够提供更好的性能,尤其是在需要保留图像整体信息时。
  • 全局池化(Global Pooling):包括全局最大池化(Global Max Pooling)和全局平均池化(Global Average Pooling)。全局最大池化将特征图中的每个通道的所有值取最大值,得到一个与通道数相同的向量;全局平均池化则将特征图中所有元素取平均值,得到一个单一的数值。全局池化能够进一步降低特征图的维度,同时保留关键信息。在图像分类任务中,全局池化层通常位于卷积层之后,用于将特征图转换为固定长度的特征向量,以便进行后续的分类操作。

作用与优势:

  • 降维:减小特征图的尺寸,降低计算复杂度和内存消耗,加快模型训练速度。
  • 保留重要信息:尽管池化会减小数据尺寸,但它会尽量保留重要的特征信息。
  • 平移不变性:池化操作能提供一定程度的平移不变性,即图像在小范围内的平移变化不会影响到池化后的结果。
  • 防止过拟合:通过减小特征图的尺寸和降低模型的复杂度,池化操作有助于防止模型在训练集上的过拟合现象。

一般先激活,然后池化。

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

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
# 加载数据并训练
for epoch in range(100):
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}')

预测

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Epoch [99], Setp [917/938],Loss: 0.1260
Epoch [99], Setp [918/938],Loss: 0.1750
Epoch [99], Setp [919/938],Loss: 0.1906
Epoch [99], Setp [920/938],Loss: 0.1170
Epoch [99], Setp [921/938],Loss: 0.1094
Epoch [99], Setp [922/938],Loss: 0.1026
Epoch [99], Setp [923/938],Loss: 0.0564
Epoch [99], Setp [924/938],Loss: 0.0766
Epoch [99], Setp [925/938],Loss: 0.1219
Epoch [99], Setp [926/938],Loss: 0.1193
Epoch [99], Setp [927/938],Loss: 0.1568
Epoch [99], Setp [928/938],Loss: 0.1768
Epoch [99], Setp [929/938],Loss: 0.1075
Epoch [99], Setp [930/938],Loss: 0.2160
Epoch [99], Setp [931/938],Loss: 0.1079
Epoch [99], Setp [932/938],Loss: 0.1955
Epoch [99], Setp [933/938],Loss: 0.2639
Epoch [99], Setp [934/938],Loss: 0.1002
Epoch [99], Setp [935/938],Loss: 0.0420
Epoch [99], Setp [936/938],Loss: 0.0846
Epoch [99], Setp [937/938],Loss: 0.0923
Epoch [99], Setp [938/938],Loss: 0.1290
Accuracy: 89 %

源码

完整代码为

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
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__()
# 输入1通道,输出6通道,卷积核为5x5
self.conv1 = nn.Conv2d(1,6,5)
self.conv2 = nn.Conv2d(6,16,5)
self.conv3 = nn.Conv2d(16,120,4)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84,10)

def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = F.max_pool2d(x,(2,2))

# 精简代码
x = F.max_pool2d(F.relu(self.conv2(x)),2)
x = F.relu(self.conv3(x))

x = torch.flatten(x,1) #扁平化
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x


# 加载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.01)

# 加载数据并训练
for epoch in range(100):
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.02:LeNet卷积神经网络
https://blog.jackeylea.com/ai/how-to-build-a-lenet-nn/
作者
JackeyLea
发布于
2024年6月24日
许可协议