用Pytorch搭建一个简单的CNN(MNIST数据集—十分类问题)


前言

最近在学习一些深度学习的相关知识,为了更好地弄清楚神经网络的搭建过程,记录学习到的一些代码,本文将以笔记的形式进行内容呈现。文章主要是为了方便自己日后学习,同时也希望可以为刚刚接触深度学习的小伙伴提供一些参考。
文章代码的来源为:Pytorch:手把手教你搭建简单的卷积神经网络(CNN),实现MNIST数据集分类任务

文章中对代码进行了一些改动,并对深度学习的相关知识以及所用相关代码进行较为详细的介绍。

一、MNIST数据集

MNIST数据集是一个手写数字识别数据集,由60,000个训练图像和10,000个测试图像组成。该数据集中的每个图像都是28x28像素的灰度图像,图像中是0~9数字,每个图片有其对应的标签。
该数据集部分图片数据如下:
在这里插入图片描述

二、使用步骤

1.基本库的导入和随机种子的设定

代码如下:

import numpy as np
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import time
np.random.seed(1234)

np.random.seed()函数:
1.利用随机数种子,每次生成的随机数相同。
具体解释:调用 np.random.randn() 生成随机数时,每一次生成的数都是随机的。但如果先使用 np.random.seed(x) 设定好种子(x 可以是任意数字),接着调用np.random.randn(),其生成的随机数将会是同一个。

2.MINIST数据集的下载、保存与加载

代码如下(示例):

# 引用MNIST数据集,这里用的是torchvision已经封装好的MINST数据集
trainset = torchvision.datasets.MNIST(
    root='MNIST',  # root是下载MNIST数据集保存的路径,可以自行修改
    train=True,
    transform=torchvision.transforms.ToTensor(),
    download=True
)

testset = torchvision.datasets.MNIST(
    root='MNIST',
    train=False,
    transform=torchvision.transforms.ToTensor(),
    download=True
)
# 下载之后利用DataLoader实例化为适合遍历的训练集和测试集   batch_size一般设置较小的然后根据硬件条件逐渐增加
trainloader = DataLoader(dataset=trainset, batch_size=16,
                         shuffle=True)  # DataLoader是一个很好地能够帮助整理数据集的类,可以用来分批次,打乱以及多线程等操作
testloader = DataLoader(dataset=testset, batch_size=16, shuffle=True)

torchvision.datasets.MNIST()参数介绍:

  • root:下载数据的目录。
  • train:为True表示下载的是训练集,为False表示下载的不是训练集。
  • transform 函数是一个 PyTorch 转换操作,它可将图像转换为张量。
    如果还想对转换的张量进行标准化可以参考下面的代码:图像转换为张量并对其进行标准化,其中均值为 0.1307,标准差为 0.3081。
    标准化的作用:标准化每一张图像的尺度,从而更有利用训练模型
transform=torchvision.transforms.Compose([
                                   torchvision.transforms.ToTensor(),
                                   torchvision.transforms.Normalize(
                                       (0.1307,), (0.3081,)) 
                                       ])

DataLoader()参数介绍:

  • batch_size:每次迭代所使用的数据样本数。
  • shuffle (bool): 是否在每个 epoch 之前打乱数据。如果设置为 True,则在每个 epoch 之前重新排列数据集以获得更好的训练效果。

关于batch_size的相关知识:
batch_size是一个很重要的超参数,它决定了模型在训练过程中一次处理的数据量大小
合适的batch_size对不仅对模型的训练效果至关重要,也对保护我们自己电脑至关重要,通常来说batch_size最开始一般设置为较小的值如16,32,如果硬件配置较好可以考虑设置为64,158,256。
详细的相关知识可以参考这篇博客batch_size详细介绍

注意:跑网上相关代码时,一定要先看看代码中batch_size的设置,如果电脑配置不行尽量先设置小一点,不然可能会强制关机,对电脑硬件设置也有一定的损伤。

可视化某一批图像数据

# 可视化某一批数据
train_img, train_label=next(iter(trainloader))   # iter迭代器,可以用来遍历trainloader里面每一个数据,这里只迭代一次来进行可视化
fig, axes = plt.subplots(4, 4, figsize=(10, 10)) #四行四列,16张图片
axes_list = []
# 输入到网络的图像
for i in range(axes.shape[0]):
    for j in range(axes.shape[1]):
        axes[i, j].imshow(train_img[i*4+j,0,:,:],cmap="gray") ##   # 这里画出来的就是我们想输入到网络里训练的图像,与之对应的标签用来进行最后分类结果损失函数的计算
        axes[i, j].axis("off")
# 对应的标签
plt.show() # 显示图像
print(train_label)

3.用pytorch搭建CNN

# 卷积模块,由卷积核和激活函数组成
class conv_block(nn.Module):
    def __init__(self,ks,ch_in,ch_out):
        super(conv_block,self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(ch_in, ch_out, kernel_size=ks,stride=1,padding=1,bias=True),  # 二维卷积核,用于提取局部的图像信息
            nn.ReLU(inplace=True), #这里用ReLU作为激活函数
            nn.Conv2d(ch_out, ch_out, kernel_size=ks,stride=1,padding=1,bias=True),
            nn.ReLU(inplace=True),
        )
    def forward(self,x):
        return self.conv(x)

1.nn.Conv2d()函数:对由多个输入平面组成的输入信号进行二维卷积。具体可参考nn.Conv2d()函数详解,下面只针对上述代码做介绍:
1)ch_in:输入图像通道数。
2)ch_out:卷积产生的通道数。
3)kernel_size:卷积核尺寸。
4)stride:卷积步长。
5)padding:填充操作,padding为1表示在原始输入图像的二维矩阵周围拓展一圈。
6)bias:为真则在输出中添加一个可学习的偏差。默认为True。
2.nn.ReLU()函数:inplace为True还是False 都不会改变Relu后的结果。
inplace=True时可以利用in-place计算可以节省内(显)存,同时还可以省去反复申请和释放内存的时间。

CNN的主体部分:由卷积模块和全连接组成。

注意:可以根据自己电脑的性能调整卷积层、全连接层和神经元的个数

class CNN(nn.Module):
    def __init__(self, kernel_size, in_ch, out_ch):
        super(CNN, self).__init__()
        feature_list = [16, 32, 64]  # 代表每一层网络的特征数,扩大特征空间有助于挖掘更多的局部信息
        self.conv1 = conv_block(kernel_size, in_ch, feature_list[0])
        self.conv2 = conv_block(kernel_size, feature_list[0], feature_list[1])
        self.conv3 = conv_block(kernel_size, feature_list[1], feature_list[2])
        self.fc = nn.Sequential(  # 全连接层主要用来进行分类,整合采集的局部信息以及全局信息
            nn.Linear(feature_list[2] * 28 * 28, 512),  # 此处28为MINST一张图片的维度
            nn.ReLU(),
            nn.Linear(512, 256),
            nn.ReLU(),
            nn.Linear(256, 10)
        )

    def forward(self, x):
        device = x.device
        x1 = self.conv1(x)
        x2 = self.conv2(x1)
        x3 = self.conv3(x2)
        x5 = x3.view(x3.size()[0], -1)  # 全连接层相当于做了矩阵乘法,所以这里需要将维度降维来实现矩阵的运算
        out = self.fc(x5)
        return out

4.训练CNN并保存损失最小的模型

网络参数的定义:

# 网络参数定义,包括优化器,迭代轮数,学习率,运行硬件等等的确定
device = torch.device("cuda")  #此处根据电脑配置进行选择,如果没有cuda就用cpu
#device = torch.device("cpu")
net = CNN(3,1,1).to(device = device,dtype = torch.float32)
epochs = 50  #训练轮次
optimizer = torch.optim.Adam(net.parameters(), lr=1e-4, weight_decay=1e-8)  #使用Adam优化器
criterion = nn.CrossEntropyLoss()  #分类任务常用的交叉熵损失函数
train_loss = []

optimizer = torch.optim.Adam(net.parameters(), lr=1e-4, weight_decay=1e-8)详解:

  • model.parameters():返回模型中所有可训练参数的迭代器。Adam优化器将使用这些参数来更新模型的权重和偏差。
  • lr:学习率(一个超参数)。表示每一次参数更新时的步长大小,需要进行调整以确保模型在训练过程中能够收敛到合适的解。
  • weight_deca:权重衰减是一种正则化项,用于减少模型的过拟合风险。权重衰减会惩罚模型中较大的权重值,以鼓励模型学习简单的权重。

每一轮训练的主体部分:

# Begin training 每一轮训练的主体
MinTrainLoss = 999
for epoch in range(1, epochs + 1):
    total_train_loss = []  # 存储当前epoch中每个batch的损失值
    net.train()   # 进入训练模式
    start = time.time()  # 记录当前时间
    for input_img, label in trainloader:
        input_img = input_img.to(device=device, dtype=torch.float32)  # 将取出来的训练集数据进行torch能够运算的格式转换
        label = label.to(device=device, dtype=torch.float32)  # 输入和输出的格式都保持一致才能进行运算
        optimizer.zero_grad()  # 每一次算loss前需要将之前的梯度清零,这样才不会影响后面的更新
        pred_img = net(input_img)
        loss = criterion(pred_img, label.long())  # 采用交叉熵损失函数计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 优化器进行下一次迭代
        total_train_loss.append(loss.item())
    train_loss.append(np.mean(total_train_loss))  # 将一个minibatch里面的损失取平均作为这一轮的loss
    end = time.time()
    # 打印当前的loss
    print(
        "epochs[%3d/%3d] current loss: %.5f, time: %.3f" % (epoch, epochs, train_loss[-1], (end - start)))  # 打印每一轮训练的结果
    # train_loss[-1]表示获取train_loss的最后一个元素,即当前epoch的平均损失
    if train_loss[-1] < MinTrainLoss:
        torch.save(net.state_dict(), "./model_min_train.pth")  # 保存loss最小的模型
        MinTrainLoss = train_loss[-1]

4.测试训练好的CNN模型

首先可视化改批测试数据,然后进行测试和评估,输出预测结果和对应的标签。

# 导入网络模型,输入某一批测试数据,查看结果
# 可视化测试集某一批数据
test_img,test_label=next(iter(testloader))
fig, axes = plt.subplots(4, 4, figsize=(10, 10))##
axes_list = []
#输入到网络的图像
for i in range(axes.shape[0]):
    for j in range(axes.shape[1]):
        axes[i, j].imshow(test_img[i*4+j,0,:,:],cmap="gray")##
        axes[i, j].axis("off")

# 预测我拿出来的那一批数据进行展示
cnn = CNN(3, 1, 1).to(device=device, dtype=torch.float32)
cnn.load_state_dict(torch.load("./model_min_train.pth", map_location=device))  # 导入我们之前已经训练好的模型
cnn.eval()  # 评估模式

test_img = test_img.to(device=device, dtype=torch.float32)
test_label = test_label.to(device=device, dtype=torch.float32)

pred_test = cnn(test_img)  # 记住,输出的结果是一个长度为10的tensor
test_pred = np.argmax(pred_test.cpu().data.numpy(), axis=1)  # 所以我们需要对其进行最大值对应索引的处理,从而得到我们想要的预测结果

# 预测结果以及标签
print("预测结果")
print(test_pred)
print("标签")
print(test_label.cpu().data.numpy())

总结

下面是batch_size为64的一些结果图:
1.某一批训练集标签,64个标签对于64张图片
在这里插入图片描述2.训练迭代过程:
在这里插入图片描述3.某一批测试集的预测结果和其对应的正确标签:
在这里插入图片描述文章大部分来源于网络,若有侵权,可联系作者删除。

  • 37
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值