卷积神经网络(CNN,包含代码实现)

本文最后更新于 2025年3月23日 下午

一,卷积是在卷什么?

······卷积神经网络是在图像识别领域的霸主之一,它可以对图片或视频进行卷积操作来提取特征,卷积可以捕捉到图像中的局部特征而不受其位置的影响,从而进行预测或分类。
图片主要分两种:

  • 灰度图:灰度图可以量化为一个二维张量(矩阵),行列即为像素,值即为灰度值。
  • RGB颜色模型:RGB图可以量化为一个三维张量(矩阵),行列即为像素,每一层的值分别为红绿蓝的程度值。

由于灰度图无法识别颜色区别,所以在很多情况下使用RGB颜色模型。

二,什么是卷积?

在卷积神经网络中,卷积操作是指将一个可移动的小窗口(称为数据窗口)与图像进行逐元素相乘然后相加的操作。这个小窗口其实是一组待优化的权重,它可以被看作是一个特定的滤波器(filter)或卷积核。现在的卷积核往往是3×3的,因为GPU提供商对于这种规格的计算有优化,速度较快。

上图中蓝色的框就是指一个数据窗口,红色框为卷积核(滤波器),最后得到的绿色方形就是卷积的结果(数据窗口中的数据与卷积核逐个元素相乘再求和)。

三,卷积计算过程

从上图可以看出,每卷积一次,图像的行列数量都会减少。同样的,这样的步骤如何进行也就出现了新的问题:
  • 步长stride:每次滑动的位置步长,(为1最好)。
  • 卷积核的个数:决定输出的depth厚度,对于RGB颜色模型即为 3。
  • 填充值zero-padding:在外围边缘补充若干圈0,方便从初始位置以步长为单位可以刚好滑倒末尾位置,通俗地讲就是为了总长能被步长整除,而且还可以防止边缘数据被忽视,加若干圈0可以保证边缘数据被卷积到的次数增加。

以上图为例,那么:

  • 数据窗口每次移动两个步长取 3*3 的局部数据,即 stride=2
  • 两个神经元,即 depth=2 ,意味着有两个滤波器。
  • zero-padding=1

四,卷积核与神经元的区别

上面红框中的部分便可以理解为一个滤波器,即卷积核的累乘求和代替的神经元的累乘求和。多个滤波器叠加便成了卷积层。

五,卷积神经网络结构

  1. 输入层
    输入层接收原始图像数据。图像通常由三个颜色通道(红、绿、蓝)组成,形成一个二维矩阵,表示像素的强度值。

  2. 卷积和激活
    卷积层将输入图像与卷积核进行卷积操作。然后,通过应用激活函数(如ReLU)来引入非线性。这一步使网络能够学习复杂的特征。
    即 卷积 ——> 激活函数。

  3. 池化层
    池化层通过减小特征图的大小来减少计算复杂性。它通过选择池化窗口内的最大值或平均值来实现。这有助于提取最重要的特征。

  4. 多层堆叠
    CNN通常由多个卷积和池化层的堆叠组成,以逐渐提取更高级别的特征。深层次的特征可以表示更复杂的模式。

  5. 全连接和输出
    最后,全连接层将提取的特征映射转化为网络的最终输出。这里还需考虑偏置

六,pytorch实现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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import torch
import torch.nn as nn # 神经网络模块
import torch.nn.functional as F # 激活函数等
import torch.optim as optim # 优化器
from torchvision import datasets, transforms # 数据集和预处理

# 设置随机种子保证可重复性
torch.manual_seed(42)

# 数据准备
transform = transforms.Compose([
transforms.ToTensor(), # 将PIL图像转为Tensor (0-1范围)
transforms.Normalize((0.5,), (0.5,)) # 标准化:(input - mean)/std,归一化到[-1, 1]
])

train_dataset = datasets.MNIST(
'./data', # 数据存储路径
train=True, # 训练集
download=False, # 关闭下载
transform=transform # 应用预处理
)

test_dataset = datasets.MNIST(
'./data',
train=False, # 测试集
download=False, # 关闭下载
transform=transform
)

train_loader = torch.utils.data.DataLoader(
train_dataset,
batch_size=64, # 每批加载64个样本
shuffle=False # 打乱数据顺序
)

test_loader = torch.utils.data.DataLoader(
test_dataset,
batch_size=1000, # 测试时使用更大的批次
shuffle=False # 打乱数据顺序
)

# 定义CNN网络模型
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
# 第一个卷积层
self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1)
'''
in_channels:输入数据的通道数
out_channels:输出数据的通道数,即卷积层中滤波器的数量
kernel_size:卷积核(滤波器)的大小,3*3
padding:输入数据的边缘填充的像素数.
当 kernel_size=3 且 padding=1 时,输出特征图的高度和宽度与输入相同
'''
# 第二个卷积层
self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)
# 最大池化层
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
'''
在每个 2x2 的区域内,取最大值作为输出。
kernel_size:池化窗口的大小
stride:池化窗口滑动的步长
'''
# 全连接层
self.fc1 = nn.Linear(64 * 7 * 7, 128) # MNIST图像经过两次池化后大小为7x7
self.fc2 = nn.Linear(128, 10) # MNIST有10个类别

def forward(self, x):
# 通过第一个卷积层和激活函数
x = F.relu(self.conv1(x))
# 通过第一个池化层
x = self.pool(x)
# 通过第二个卷积层和激活函数
x = F.relu(self.conv2(x))
# 通过第二个池化层
x = self.pool(x)
# 展平张量
x = x.view(-1, 64 * 7 * 7)
# 通过第一个全连接层和激活函数
x = F.relu(self.fc1(x))
# 通过第二个全连接层
x = self.fc2(x)
return x

# 实例化网络
model = SimpleCNN()

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练网络
num_epochs = 10
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
for i, (inputs, labels) in enumerate(train_loader):
# 清零梯度
optimizer.zero_grad()
# 前向传播
outputs = model(inputs)
# 计算损失
loss = criterion(outputs, labels)
# 反向传播
loss.backward()
# 更新权重
optimizer.step()

running_loss += loss.item()
if i % 100 == 99: # 每100个batch打印一次损失
print(f'Epoch [{epoch + 1}/{num_epochs}], Step [{i + 1}/{len(train_loader)}], Loss: {running_loss / 100:.4f}')
running_loss = 0.0

# 测试网络
model.eval()
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in test_loader:
outputs = model(inputs)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()

print(f'Accuracy of the network on the 10000 test images: {100 * correct / total:.2f}%')

# 保存模型
torch.save(model.state_dict(), "CNN_model.pth")

测试

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
import torch
import torch.nn as nn # 神经网络模块
import torch.nn.functional as F # 激活函数等
from torchvision import datasets, transforms # 数据集和预处理
# 设置随机种子保证可重复性
torch.manual_seed(42)

# 数据准备
transform = transforms.Compose([
transforms.ToTensor(), # 将PIL图像转为Tensor (0-1范围)
transforms.Normalize((0.1307,), (0.3081,)) # 标准化:(input - mean)/std
])

train_dataset = datasets.MNIST(
'./data', # 数据存储路径
train=True, # 训练集
download=False, # 关闭下载
transform=transform # 应用预处理
)

test_dataset = datasets.MNIST(
'./data',
train=False, # 测试集
download=False, # 关闭下载
transform=transform
)

# 定义神经网络模型
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
# 第一个卷积层
self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1)
'''
in_channels:输入数据的通道数
out_channels:输出数据的通道数,即卷积层中滤波器的数量,输出的特征图的通道数
kernel_size:卷积核(滤波器)的大小,3*3
padding:输入数据的边缘填充的像素数.
当 kernel_size=3 且 padding=1 时,输出特征图的高度和宽度与输入相同
'''
# 第二个卷积层
self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)
# 最大池化层
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
'''
在每个 2x2 的区域内,取最大值作为输出。
kernel_size:池化窗口的大小
stride:池化窗口滑动的步长
'''
# 全连接层
self.fc1 = nn.Linear(64 * 7 * 7, 128) # MNIST图像经过两次池化后大小为7x7
self.fc2 = nn.Linear(128, 10) # MNIST有10个类别

def forward(self, x):
# 通过第一个卷积层和激活函数
x = F.relu(self.conv1(x))
# 通过第一个池化层
x = self.pool(x)
# 通过第二个卷积层和激活函数
x = F.relu(self.conv2(x))
# 通过第二个池化层
x = self.pool(x)
# 展平张量
x = x.view(-1, 64 * 7 * 7)
# 通过第一个全连接层和激活函数
x = F.relu(self.fc1(x))
# 通过第二个全连接层
x = self.fc2(x)
return x

# 加载模型参数
model = SimpleCNN()
model.load_state_dict(torch.load('CNN_model.pth'))
model.eval() # 将模型设置为评估模式

# 推理
with torch.no_grad(): # 禁用梯度计算
for image, label in test_dataset:
output = model(image.unsqueeze(0)) # 前向传播, 添加批量维度
prediction = output.argmax(dim=1).item() # 获取预测类别
print(f'Predicted: {prediction}, Actual: {label}')


from PIL import Image
# 加载单张图像
image_path = 'path_to_your_image.png' # 替换为你的图像路径
image = Image.open(image_path).convert('L') # 转换为灰度图像

# 预处理
image = transform(image).unsqueeze(0) # 添加batch维度

# 推理
with torch.no_grad():
output = model(image)
_, predicted = torch.max(output, 1)
print(f'Predicted: {predicted.item()}')

卷积神经网络(CNN,包含代码实现)
https://jimes.cn/2025/01/21/卷积神经网络/
作者
Jimes
发布于
2025年1月21日
许可协议