欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

CNN 入门 - MNIST 手写数字识别

最编程 2024-05-19 12:59:22
...

理论基础

什么是CNN

CNN全称是Convolutional Neural Network(卷积神经网络),主要应用在影像上。至少有一层使用了卷积层的神经网络就可以称为卷积神经网络。

网络结构

CNN的网络结构包括:输入层、卷积层、激活层、池化层、全连接层

输入层

一张图片如何作为模型的输入?图片是由一个个的像素点构成,每个像素点又由RGB三种数值构成,所以就可以用三维矩阵来表示一张图片,三维矩阵的长度和宽度代表图片的大小,深度代表图片的色彩通道个数。

image.png

卷积层

卷积层又被称为过滤器(filter)或者内核(kernel),卷积层每一个节点的输入是上一层输出的一小部分,参考下图理解卷积过程。

2019111615502886.png

在卷积层中,需要自己定义卷积核的大小(通常是3 * 3或5 * 5) 以及 步长填充方式。相关计算公式:输出边长=(输入宽度 – 滤波器边长 + 2 * 补零个数)/ 步幅 + 1

image.png

激活层

对卷积层的结果进行一次非线性映射。添加激活函数之后,输出就是一个非线性函数,使神经网络的表达能力增强。CNN激励函数一般为ReLU,其特点为:收敛快、求梯度简单、较脆弱。

池化层

池化层可以缩小三维矩阵的长度和宽度(即压缩图像的大小),从而缩小全连接层中节点的个数,加快计算速度并防止过拟合。使用最大值操作的池化层被称之为最大池化层(max pooling)。使用平均值操作的池化层被称之为平均池化层(mean pooling)。

最大池化层图解:

image.png

平均池化层图解:

image.png

全连接层

全连接层主要用来得出分类结果。卷积层和池化层可以看成图像特征提取的过程,最后仍需要全连接层完成图像分类任务

手写数字识别案例

整体网络结构如下图所示

image.png

代码实现:

# 导入框架
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(卷积神经网络)(超详细)