深度学习实践之使用CNN在CIFAR-10数据集上进行识别

利用CIFAR-10数据(彩色图像)集实现卷积神经网络(CNN)进行图像分类

1. 数据集准备

CIFAR-10 数据集包括 \(60,000\)\(32 \times 32\) 的彩色图像,分成 \(10\) 类,每类 \(6,000\)张。其中 \(50,000\) 张用于训练,\(10,000\) 张用于测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 为训练和测试集定义数据变换
transform = transforms.Compose([
transforms.ToTensor(),
# transforms:包含图像变换方法,例如归一化、调整尺寸等。这里我们将图像归一化到均值为 0.5,标准差为 0.5。
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# 加载CIFAR-10训练和测试数据集
# trainloader和testloader:用于批量加载 CIFAR-10 数据集,提高效率
trainset = torchvision.datasets.CIFAR10(root="./data", train=True, download=False, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=100, shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root="./data", train=False, download=False, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=100, shuffle=False, num_workers=2)
# CIFAR-10的类名
classes = ('飞机', '汽车', '鸟', '猫', '鹿',
'狗', '青蛙', '马', '船', '卡车')

可视化数据集

pkoEqYT.jpg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 显示一批图像的函数
def imshow(img):
img = img / 2 + 0.5 # 反归一化
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()

# 显示训练集中的前16个图像
dataiter = iter(trainloader)
images, labels = next(dataiter)

# 只显示前16个图像和标签
imshow(torchvision.utils.make_grid(images[:16]))
print(' '.join(f'{classes[labels[j]]:5s}' for j in range(16)))
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
def train_and_evaluate(model, train_loader, val_loader, epochs):
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
model.train()
for epoch in range(epochs):
running_loss = 0.0
for i, (data, target) in enumerate(train_loader):
# data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
output = output.squeeze()
target = target.squeeze()
loss = criterion(output, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
if i % 100 == 99: # 每100个小批量打印一次
print(f'[Epoch {epoch + 1}, Batch {i + 1}] loss: {running_loss / 100:.3f}')
running_loss = 0.0

# 评估模型性能
model.eval()
with torch.no_grad():
correct = 0
total = 0
for data, target in val_loader:
# data, target = data.to(device), target.to(device)
output = model(data)
_, predicted = torch.max(output, 1)
total += target.size(0)
correct += (predicted == target).sum().item()
accuracy = correct / total
print(f'在10,000张测试图像上的准确率:{100 * accuracy:.2f}%')
return accuracy

3. 构建CNN模型

卷积神经网络由卷积层池化层全连接层构成。

法一:自定义构建法

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
# 模型定义
class CNN(nn.Module):
# 定义模型属性
def __init__(self, in_channels):
super(CNN, self).__init__()
# 输入到隐层 1
# in_channels: 输入通道数。这是输入数据的通道数,例如,对于彩色图像,输入通道数通常是 3(RGB)。
# out_channels: 输出通道数。这是卷积层输出的通道数。通常,输出通道数可以增加以提取更复杂的特征。
# kernel_size: 卷积核大小。这是一个整数或元组,表示卷积核的高度和宽度。例如,(3, 3) 表示卷积核是一个 3x3 的正方形。
# padding: 填充。这是一个整数或元组,表示在输入图像的边界周围添加的填充。填充可以是零填充,也可以是反射填充等。填充可以用于控制输出特征图的尺寸。
# stride: 步长。这是一个整数或元组,表示卷积操作在每个方向上的步长。步长为 1 表示卷积核每次移动一个像素。步长增加可以减少输出特征图的尺寸。
# bias: 偏置项。这是一个布尔值,表示是否在卷积层中添加偏置项。默认为 True。
self.hidden1 = Conv2d(in_channels, out_channels = 32, kernel_size = (3,3),padding=1)
# kaiming_uniform_这种初始化方法特别适用于使用ReLU或Leaky ReLU激活函数的网络
# kaiming_uniform_ 被用于初始化卷积神经网络(CNN)中卷积层和全连接层的权重
# kaiming_uniform_(self.hidden1.weight, nonlinearity='relu')
self.act1 = ReLU()
# 池化层 1
# kernel_size: 池化窗口的大小。这是一个整数或者一对整数 (kH, kW),分别表示高度和宽度。例如,(2, 2) 表示一个 2x2 的池化窗口。
# stride: 池化窗口滑动的步长。这决定了相邻窗口的水平和垂直间距。步长也是一个整数或者一对整数 (sH, sW)。如果未指定,它默认与 kernel_size 相同
# padding: 输入特征图的边界周围添加的填充。这可以是一个整数或者一对整数 (padH, padW),表示在高度和宽度方向上的填充大小。填充通常用于保持输出特征图的尺寸不变。
# dilation: 池化窗口中元素之间的间距。这可以用于控制池化操作的“感受野”大小。默认值为 1。
# ceil_mode: 一个布尔值,当设置为 True 时,它会使用 ceil 函数而不是 floor 函数来计算输出尺寸。这意味着当输入尺寸不能被步长整除时,输出尺寸会向上取整。
# return_indices: 在某些情况下,可能需要知道池化过程中每个输出值来自输入中的哪个位置。如果设置为 True,则在进行最大池化时返回最大值的索引。
self.pool1 = MaxPool2d(kernel_size = (2,2))

# 隐层 2
self.hidden2 = Conv2d(32, out_channels = 64, kernel_size = (3,3),padding=1)
# kaiming_uniform_(self.hidden2.weight, nonlinearity='relu')
self.act2 = ReLU()
# 池化层 2
self.pool2 = MaxPool2d(kernel_size = (2,2))

# 隐层 3
self.hidden3 = Conv2d(64, out_channels = 128, kernel_size = (3,3),padding=1)
# kaiming_uniform_(self.hidden3.weight, nonlinearity='relu')
self.act3 = ReLU()
# 池化层 3
self.pool3 = MaxPool2d(kernel_size = (2,2))

# 全连接层
self.hidden4 = Linear(128 * 4 * 4, 512)
# kaiming_uniform_(self.hidden3.weight, nonlinearity='relu')
self.act4 = ReLU()

# 全连接层
self.hidden5 = Linear(512, 256)
# kaiming_uniform_(self.hidden5.weight, nonlinearity='relu')
self.act5 = ReLU()

# 输出层
self.hidden6 = Linear(256, 10)
# xavier_uniform_(self.hidden6.weight)
# self.act6 = Softmax(dim=1)

# 前向传播
def forward(self, X):
# 输入到隐层 1
X = self.hidden1(X)
X = self.act1(X)
X = self.pool1(X)
# 隐层 2
X = self.hidden2(X)
X = self.act2(X)
X = self.pool2(X)
# 隐层 3
X = self.hidden3(X)
X = self.act3(X)
X = self.pool3(X)
# 扁平化
X = X.view(-1, 128*4*4)
# 隐层 4
X = self.hidden4(X)
X = self.act4(X)
# 隐层 5
X = self.hidden5(X)
X = self.act5(X)
# 隐层 6(输出)
X = self.hidden6(X)
# X = self.act6(X)
return X

# 实例化模型
# 如果你的输入是灰度图像,你可以这样实例化模型
# model = CNN(in_channels=1)
# 如果你的输入是彩色图像,你可以这样实例化模型
# model = CNN(in_channels=3)
# net.to(device)
net1 = CNN(in_channels=3)
print(net1)

pkoEjl4.jpg

将模型进行训练:

1
accuracy = train_and_evaluate(net1, trainloader, testloader, 10)

在上述代码片段中,对定义的卷积神经网络各个层的详细解释:

  • 卷积层 (Conv2d): 每个卷积层都使用一个3x3的卷积核,输入通道数为3(对于彩色图像),输出通道数分别为32、64和128。padding=1 确保输出特征图的尺寸与输入相同。

    • 卷积层的输出维度可以通过以下公式计算:

      • \(\text{Output Height}=\left[\frac{\text{Input Height}+2\times\text{Padding}-\text{Kernel Size}}{\text{Stride}}+ 1 \right]\)

      • \(\text{Output Width}=\left[\frac{\text{Input Width}+2\times\text{Padding}-\text{Kernel Size}}{\text{Stride}}+1\right]\)

    • 在代码中,卷积层的 padding=1 和 stride=1(默认值),因此输出尺寸大致与输入尺寸相同(实际会稍有不同,取决于卷积核的覆盖范围)。

  • 激活层 (ReLU): 使用 ReLU 激活函数。

  • 池化层 (MaxPool2d): 使用 2x2 的最大池化,步长为2。

    • 池化层会进一步减小特征图的尺寸。在代码中,池化层的 kernel_size=2 和 stride=2,这意味着特征图的尺寸会减半。
  • 扁平化层 (Flatten): 将多维特征图展平成一维向量,以便输入到全连接层。

  • 全连接层 (Linear): 包含两个全连接层,第一个将特征图的维度从 128 * 4 * 4 减少到512,第二个将维度从512减少到256。

  • 输出层 (Linear): 最后一个全连接层将特征从256减少到10,对应于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
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
[Epoch 1, Batch 100] loss: 1.899
[Epoch 1, Batch 200] loss: 1.511
[Epoch 1, Batch 300] loss: 1.392
[Epoch 1, Batch 400] loss: 1.288
[Epoch 1, Batch 500] loss: 1.197
[Epoch 2, Batch 100] loss: 1.094
[Epoch 2, Batch 200] loss: 1.036
[Epoch 2, Batch 300] loss: 1.000
[Epoch 2, Batch 400] loss: 0.962
[Epoch 2, Batch 500] loss: 0.915
[Epoch 3, Batch 100] loss: 0.832
[Epoch 3, Batch 200] loss: 0.837
[Epoch 3, Batch 300] loss: 0.799
[Epoch 3, Batch 400] loss: 0.779
[Epoch 3, Batch 500] loss: 0.755
[Epoch 4, Batch 100] loss: 0.647
[Epoch 4, Batch 200] loss: 0.658
[Epoch 4, Batch 300] loss: 0.662
[Epoch 4, Batch 400] loss: 0.658
[Epoch 4, Batch 500] loss: 0.659
[Epoch 5, Batch 100] loss: 0.523
[Epoch 5, Batch 200] loss: 0.546
[Epoch 5, Batch 300] loss: 0.546
[Epoch 5, Batch 400] loss: 0.552
[Epoch 5, Batch 500] loss: 0.551
[Epoch 6, Batch 100] loss: 0.411
[Epoch 6, Batch 200] loss: 0.431
[Epoch 6, Batch 300] loss: 0.445
[Epoch 6, Batch 400] loss: 0.439
[Epoch 6, Batch 500] loss: 0.452
[Epoch 7, Batch 100] loss: 0.317
[Epoch 7, Batch 200] loss: 0.317
[Epoch 7, Batch 300] loss: 0.345
[Epoch 7, Batch 400] loss: 0.363
[Epoch 7, Batch 500] loss: 0.385
[Epoch 8, Batch 100] loss: 0.231
[Epoch 8, Batch 200] loss: 0.246
[Epoch 8, Batch 300] loss: 0.256
[Epoch 8, Batch 400] loss: 0.277
[Epoch 8, Batch 500] loss: 0.275
[Epoch 9, Batch 100] loss: 0.157
[Epoch 9, Batch 200] loss: 0.189
[Epoch 9, Batch 300] loss: 0.195
[Epoch 9, Batch 400] loss: 0.215
[Epoch 9, Batch 500] loss: 0.208
[Epoch 10, Batch 100] loss: 0.107
[Epoch 10, Batch 200] loss: 0.138
[Epoch 10, Batch 300] loss: 0.146
[Epoch 10, Batch 400] loss: 0.159
[Epoch 10, Batch 500] loss: 0.189
10,000张测试图像上的准确率:76.05%

法二:快速搭建法

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
# easy and fast way to build your network
net2 = torch.nn.Sequential(
torch.nn.Conv2d(3, 32, 3, padding=1),
torch.nn.ReLU(),
torch.nn.MaxPool2d(2, 2),

torch.nn.Conv2d(32, 64, 3, padding=1),
torch.nn.ReLU(),
torch.nn.MaxPool2d(2, 2),

torch.nn.Conv2d(64, 128, 3, padding=1),
torch.nn.ReLU(),
torch.nn.MaxPool2d(2, 2),

torch.nn.Flatten(), # 扁平化层

torch.nn.Linear(128 * 4 * 4, 512),
torch.nn.ReLU(),

torch.nn.Linear(512, 256),
torch.nn.ReLU(),

torch.nn.Linear(256, 10),
)
print(net2)

pkoEv6J.jpg

将模型进行训练:

1
accuracy = train_and_evaluate(net2, trainloader, testloader, 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
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
[Epoch 1, Batch 100] loss: 1.871
[Epoch 1, Batch 200] loss: 1.527
[Epoch 1, Batch 300] loss: 1.379
[Epoch 1, Batch 400] loss: 1.257
[Epoch 1, Batch 500] loss: 1.189
[Epoch 2, Batch 100] loss: 1.076
[Epoch 2, Batch 200] loss: 1.033
[Epoch 2, Batch 300] loss: 0.991
[Epoch 2, Batch 400] loss: 0.961
[Epoch 2, Batch 500] loss: 0.927
[Epoch 3, Batch 100] loss: 0.822
[Epoch 3, Batch 200] loss: 0.817
[Epoch 3, Batch 300] loss: 0.786
[Epoch 3, Batch 400] loss: 0.783
[Epoch 3, Batch 500] loss: 0.756
[Epoch 4, Batch 100] loss: 0.663
[Epoch 4, Batch 200] loss: 0.655
[Epoch 4, Batch 300] loss: 0.642
[Epoch 4, Batch 400] loss: 0.631
[Epoch 4, Batch 500] loss: 0.639
[Epoch 5, Batch 100] loss: 0.516
[Epoch 5, Batch 200] loss: 0.507
[Epoch 5, Batch 300] loss: 0.521
[Epoch 5, Batch 400] loss: 0.530
[Epoch 5, Batch 500] loss: 0.553
[Epoch 6, Batch 100] loss: 0.389
[Epoch 6, Batch 200] loss: 0.415
[Epoch 6, Batch 300] loss: 0.419
[Epoch 6, Batch 400] loss: 0.428
[Epoch 6, Batch 500] loss: 0.425
[Epoch 7, Batch 100] loss: 0.290
[Epoch 7, Batch 200] loss: 0.288
[Epoch 7, Batch 300] loss: 0.326
[Epoch 7, Batch 400] loss: 0.344
[Epoch 7, Batch 500] loss: 0.358
[Epoch 8, Batch 100] loss: 0.198
[Epoch 8, Batch 200] loss: 0.213
[Epoch 8, Batch 300] loss: 0.259
[Epoch 8, Batch 400] loss: 0.253
[Epoch 8, Batch 500] loss: 0.267
[Epoch 9, Batch 100] loss: 0.135
[Epoch 9, Batch 200] loss: 0.155
[Epoch 9, Batch 300] loss: 0.177
[Epoch 9, Batch 400] loss: 0.215
[Epoch 9, Batch 500] loss: 0.190
[Epoch 10, Batch 100] loss: 0.102
[Epoch 10, Batch 200] loss: 0.125
[Epoch 10, Batch 300] loss: 0.148
[Epoch 10, Batch 400] loss: 0.146
[Epoch 10, Batch 500] loss: 0.168
10,000张测试图像上的准确率:75.35%

法三:自定义构建法的另一种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
self.conv3 = nn.Conv2d(64, 128, 3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(128 * 4 * 4, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, 10)

def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = self.pool(F.relu(self.conv3(x)))
x = x.view(-1, 128 * 4 * 4)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net3 = Net()
print(net3)

pkoExX9.jpg

将模型进行训练:

1
accuracy = train_and_evaluate(net3, trainloader, testloader, 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
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
[Epoch 1, Batch 100] loss: 1.896
[Epoch 1, Batch 200] loss: 1.507
[Epoch 1, Batch 300] loss: 1.385
[Epoch 1, Batch 400] loss: 1.277
[Epoch 1, Batch 500] loss: 1.215
[Epoch 2, Batch 100] loss: 1.078
[Epoch 2, Batch 200] loss: 1.047
[Epoch 2, Batch 300] loss: 0.995
[Epoch 2, Batch 400] loss: 0.970
[Epoch 2, Batch 500] loss: 0.915
[Epoch 3, Batch 100] loss: 0.811
[Epoch 3, Batch 200] loss: 0.786
[Epoch 3, Batch 300] loss: 0.785
[Epoch 3, Batch 400] loss: 0.771
[Epoch 3, Batch 500] loss: 0.774
[Epoch 4, Batch 100] loss: 0.650
[Epoch 4, Batch 200] loss: 0.637
[Epoch 4, Batch 300] loss: 0.644
[Epoch 4, Batch 400] loss: 0.635
[Epoch 4, Batch 500] loss: 0.643
[Epoch 5, Batch 100] loss: 0.516
[Epoch 5, Batch 200] loss: 0.521
[Epoch 5, Batch 300] loss: 0.524
[Epoch 5, Batch 400] loss: 0.528
[Epoch 5, Batch 500] loss: 0.512
[Epoch 6, Batch 100] loss: 0.399
[Epoch 6, Batch 200] loss: 0.412
[Epoch 6, Batch 300] loss: 0.428
[Epoch 6, Batch 400] loss: 0.439
[Epoch 6, Batch 500] loss: 0.424
[Epoch 7, Batch 100] loss: 0.288
[Epoch 7, Batch 200] loss: 0.314
[Epoch 7, Batch 300] loss: 0.325
[Epoch 7, Batch 400] loss: 0.348
[Epoch 7, Batch 500] loss: 0.351
[Epoch 8, Batch 100] loss: 0.222
[Epoch 8, Batch 200] loss: 0.233
[Epoch 8, Batch 300] loss: 0.263
[Epoch 8, Batch 400] loss: 0.272
[Epoch 8, Batch 500] loss: 0.273
[Epoch 9, Batch 100] loss: 0.147
[Epoch 9, Batch 200] loss: 0.180
[Epoch 9, Batch 300] loss: 0.194
[Epoch 9, Batch 400] loss: 0.213
[Epoch 9, Batch 500] loss: 0.235
[Epoch 10, Batch 100] loss: 0.125
[Epoch 10, Batch 200] loss: 0.120
[Epoch 10, Batch 300] loss: 0.145
[Epoch 10, Batch 400] loss: 0.166
[Epoch 10, Batch 500] loss: 0.181
10,000张测试图像上的准确率:76.03%

测试模型预测效果

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 numpy as np
import matplotlib.pyplot as plt
# 显示一批图像的函数
def imshow(img):
img = img / 2 + 0.5 # 反归一化
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()

# 显示训练集中的前16个图像
dataiter = iter(trainloader)
images, labels = next(dataiter)

# 只显示前16个图像和标签
imshow(torchvision.utils.make_grid(images[:16]))
print(' '.join(f'{classes[labels[j]]:5s}' for j in range(16)))

# 可视化预测结果
dataiter = iter(testloader)
images, labels = next(dataiter)
# images, labels = images.to(device), labels.to(device)
outputs = net1(images)
_, predicted = torch.max(outputs, 1)

newimage = images.cpu().detach()

imshow(torchvision.utils.make_grid(newimage))
print('真实标签: ', ' '.join(f'{classes[labels[j]]:5s}' for j in range(10)))
print('预测标签: ', ' '.join(f'{classes[predicted[j]]:5s}' for j in range(10)))

得到预测结果如下图:

pkoVSmR.jpg

注:输出通道

在卷积神经网络(CNN)中,输出通道数(out_channels)是一个超参数,通常由网络设计者根据需要人为设置。它不是通过计算公式自动得到的,而是基于经验、实验或特定任务的需求来选择的。

输出通道数的作用

  • 特征检测:每个输出通道对应一种不同的特征检测器。增加输出通道数可以帮助网络学习到更丰富的特征。
  • 网络深度:增加输出通道数可以增加网络的深度,有助于提取更复杂的特征,但同时也会增加计算复杂度。

如何选择输出通道数

  • 经验法则:通常从较小的输出通道数开始,例如16、32、64等,然后根据任务的复杂性逐步增加。
  • 实验:通过实验不同的输出通道数,观察对模型性能的影响,选择最优的配置。
  • 任务需求:对于更复杂的任务,可能需要更多的输出通道来捕捉更多的特征。

深度学习实践之使用CNN在CIFAR-10数据集上进行识别
https://fu-jingqi.github.io/2024/07/17/深度学习实践之使用CNN在CIFAR-10数据集上进行识别/
作者
coderfjq
发布于
2024年7月17日
许可协议