CNN 入门 - MNIST 手写数字识别
最编程
2024-05-19 12:59:22
...
理论基础
什么是CNN
CNN全称是Convolutional Neural Network(卷积神经网络),主要应用在影像上。至少有一层使用了卷积层的神经网络就可以称为卷积神经网络。
网络结构
CNN的网络结构包括:输入层、卷积层、激活层、池化层、全连接层。
输入层
一张图片如何作为模型的输入?图片是由一个个的像素点构成,每个像素点又由RGB三种数值构成,所以就可以用三维矩阵来表示一张图片,三维矩阵的长度和宽度代表图片的大小,深度代表图片的色彩通道个数。
卷积层
卷积层又被称为过滤器(filter)或者内核(kernel),卷积层每一个节点的输入是上一层输出的一小部分,参考下图理解卷积过程。
在卷积层中,需要自己定义卷积核的大小(通常是3 * 3或5 * 5) 以及 步长和填充方式。相关计算公式:输出边长=(输入宽度 – 滤波器边长 + 2 * 补零个数)/ 步幅 + 1
激活层
对卷积层的结果进行一次非线性映射。添加激活函数之后,输出就是一个非线性函数,使神经网络的表达能力增强。CNN激励函数一般为ReLU,其特点为:收敛快、求梯度简单、较脆弱。
池化层
池化层可以缩小三维矩阵的长度和宽度(即压缩图像的大小),从而缩小全连接层中节点的个数,加快计算速度并防止过拟合。使用最大值操作的池化层被称之为最大池化层(max pooling)。使用平均值操作的池化层被称之为平均池化层(mean pooling)。
最大池化层图解:
平均池化层图解:
全连接层
全连接层主要用来得出分类结果。卷积层和池化层可以看成图像特征提取的过程,最后仍需要全连接层完成图像分类任务。
手写数字识别案例
整体网络结构如下图所示
代码实现:
# 导入框架
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 定义超参数
BATCH_SIZE = 128 # 每批处理的数据个数
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 定义设备
EPOCHS = 10 # 要训练的轮数
# 构建pipeline,对图像做变换
pipeline = transforms.Compose([
transforms.ToTensor(), # 将图片转换成tensor
transforms.Normalize((0.1307,),(0.3081)) # 正则化,降低模型复杂度
])
# 下载数据集
train_set = datasets.MNIST("data", train=True, download=True, transform=pipeline)
test_set = datasets.MNIST("data", train=False, download=True, transform=pipeline)
# 加载数据
train_loader = DataLoader(train_set, BATCH_SIZE, True) # True 打乱顺序
test_loader = DataLoader(test_set, BATCH_SIZE, True)
# 定义网络模型
class MyCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 10, 5) # 1:灰度图片是单通道 10:输出通道 5:卷积核大小
self.conv2 = nn.Conv2d(10, 20, 3) # 10:输入通道 20:输出通道 3:卷积核大小
self.fc1 = nn.Linear(20 * 10 * 10, 500) # 全连接层,输入通道和输出通道
self.fc2 = nn.Linear(500, 10) # 全连接层
def forward(self, x): # 前向传播
input_size = x.size(0) # 获取batch_size 这里每次输入的是一批数据
x = self.conv1(x) # 输入:batch_size*1*28*28 输出:batch_size*10*24*24
x = F.relu(x) # 激活层 输出:batch_size*10*24*24
x = F.max_pool2d(x, 2, 2) # 池化层 输入:batch_size*10*24*24 输出:batch_size*10*12*12
x = self.conv2(x) # 输入:batch_size*10*12*12 输出:batch_size*20*10*10
x = F.relu(x) # 激活层 输出:batch_size*20*10*10
# 拉平操作
x = x.view(input_size, -1) # -1 代表自动计算维度,这里的维度是20*10*10
x = self.fc1(x) # 全连接层 输入:batch_size*20*10*10 输出:batch_size*500
x = F.relu(x) # 激活层
x = self.fc2(x) # 全连接层 输入:batch_size*500 输出:batch_size*10
output = F.log_softmax(x, dim=1) # 分类,返回10个数字的概率 损失函数?
return output
# 定义优化器
model = MyCNN().to(DEVICE)
optimizer = optim.Adam(model.parameters())
# 定义训练方法
def train_model(model, device, train_loader, optimizer, epoch):
model.train() # 训练模型
for index, (data, target) in enumerate(train_loader):
# 数据和目标值部署到device
data, target = data.to(device), target.to(device)
# 初始化梯度为0
optimizer.zero_grad()
# 获取训练后的结果
output = model(data)
# 计算损失
loss = F.cross_entropy(output, target)
# 反向传播
loss.backward()
# 参数优化
optimizer.step()
# 打印过程
if index % 3000 == 0:
print(index)
print("第{}轮训练,损失为:{:.6f}".format(epoch, loss.item()))
# 定义测试方法
def test_model(model, device, test_loader):
model.eval() # 模型验证
# 测试数据正确率
correct = 0.0
# 测试数据的损失
test_loss = 0.0
with torch.no_grad(): # 测试数据不进行优化,不计算梯度
for data, target in test_loader:
# 部署数据
data, target = data.to(device), target.to(device)
# 获取测试结果
output = model(data)
# 累加测试的损失
test_loss += F.cross_entropy(output, target).item()
# 找到预测值,即测试结果中最大值的下标
pre = output.max(1, keepdim=True)[1] # 0为值 1为索引
# 累加正确的个数
correct += pre.eq(target.view_as(pre)).sum().item()
# 平均损失
test_loss /= len(test_loader.dataset)
# 正确率
correct = 100.0 * correct / len(test_loader.dataset)
print("测试的损失为:{:.4f},正确率为:{:.3f}".format(test_loss, correct))
# 训练模型
for epoch in range(1, EPOCHS + 1):
train_model(model, DEVICE, train_loader, optimizer, epoch)
# 测试模型
test_model(model, DEVICE, test_loader)
本文中所引用图片均来自于李宏毅机器学习课件和博客 深度学习——CNN(卷积神经网络)(超详细)