深度学习(一)MNIST手写数字识别

本文详细介绍了使用PyTorch实现一个简单的卷积神经网络(CNN)对MNIST数据集进行手写数字识别的过程,包括数据预处理、模型构建、参数设置、训练循环、损失函数和优化,以及模型的保存和可视化。

 代码来自https://github.com/1240117300/MINIST

1.随机种子

torch.manual_seed(1)

使神经网络的初始化每次都相同

2.超参数

EPOCH = 1 
BATCH_SIZE = 50
LR = 0.001 
DOWNLOAD_MNIST = False 

EPOCH表示训练的迭代次数,这里设为1表示只进行一次完整的训练

BATCH_SIZE是每个批次包含的样本数量,神经网络使用小批次训练,样本总数除以size就是每个EPOCH的批次数量

LR是学习率,控制每次迭代中权重更新的幅度,较小的学习率使得权重更新更稳定但收敛速度较慢,较大的学习率可以加速收敛但可能导致不稳定的权重更新

DOWNLOAD_MNIST = false表示不需要下载数据集

3.MNIST数据集下载

train_data = torchvision.datasets.MNIST(
    root='./data/',  
    train=True, 
    transform=torchvision.transforms.ToTensor(),  
    download=DOWNLOAD_MNIST,  
)
test_data = torchvision.datasets.MNIST(
    root='./data/',
    train=False  
)

使用 PyTorch 的 torchvision 库来下载和加载 MNIST 数据集

train_data 是用于训练的 MNIST 数据集,包括图像和标签。指定了以下参数:

  • root='./data/':这是数据集的存储目录,数据将被下载到这个目录。如果已经下载了数据,它也会从这个目录中加载数据。

  • train=True:表示正在加载用于训练的数据集。

  • transform=torchvision.transforms.ToTensor():这个转换将图像数据从范围 [0, 255] 转换为范围 [0, 1],并将其加载到 PyTorch 张量中。这是一种标准的数据预处理步骤。

  • download=DOWNLOAD_MNIST:根据 DOWNLOAD_MNIST 的值来判断是否下载数据集。如果已经下载,它将从本地加载。

test_data 是用于测试的 MNIST 数据集,包括测试集的图像和标签。指定了以下参数:

  • root='./data/':与训练数据集相同,测试数据集将从相同的目录加载。

  • train=False:表示正在加载测试数据集。

4.批训练

train_loader = Data.DataLoader(
    dataset=train_data,
    batch_size=BATCH_SIZE,
    shuffle=True  
)

DataLoader 是 PyTorch 中用于加载数据的实用工具,特别适用于训练深度学习模型。它的主要功能是将数据集分割成小批次(batch),并允许方便地迭代训练数据。

这段代码创建了一个数据加载器(DataLoader),用于将训练数据集 train_data 划分成小批次,并在训练过程中对其进行迭代。

5.测试

test_x = torch.unsqueeze(test_data.train_data, dim=1).type(torch.FloatTensor)[:2000] / 255
test_y = test_data.test_labels[:2000]

torch.unsqueeze(test_data.train_data, dim=1):这一步通过使用 torch.unsqueeze 对图像数据的维度进行扩充,将原来的 (2000, 28, 28) 形状扩充为 (2000, 1, 28, 28)。这是因为通常在深度学习中,图像数据的维度会包括通道(channel)维度,而灰度图像通常只有一个通道。因此,维度扩充用于匹配模型的输入要求。原来28,28因为对于MNIST数据集中的手写数字图像每个图像都是一个28*28的矩阵表示。

.type(torch.FloatTensor):这一步将数据类型转换为 torch.FloatTensor,以确保数据在后续计算中使用浮点数格式。

[:2000]:这是对数据进行切片操作,仅保留前2000个样本。这是为了限制处理的数据规模,以便快速测试模型。

test_data.test_labels[:2000]:这一行获取了测试数据集的标签,同样限制为前2000个样本。

图像的pixel本来是0到255之间,除以255对图像进行归一化使取值范围在(0,1)

总之,这些操作将测试数据准备为一个包含前2000个图像样本的张量(test_x),并且将其对应的标签存储在 test_y 中。这些准备步骤有助于将数据准备为可以输入到深度学习模型中进行预测的格式。

6.建立CNN模型

class CNN(nn.Module): 
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(  # 输入图像大小(1,28,28)
                in_channels=1,  
                out_channels=16,  
                kernel_size=5,  
                stride=1,  
                padding=2,  
            ),  # 输出图像大小(16,28,28)
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2), 
            # 输出图像大小(16,14,14)
        )
        self.conv2 = nn.Sequential(
            # 输入图像大小(16,14,14)
            nn.Conv2d( 
                in_channels=16,
                out_channels=32,
                kernel_size=5,
                stride=1,
                padding=2
            ),
            # 输出图像大小 (32,14,14)
            nn.ReLU(),
            nn.MaxPool2d(2),
            # 输出图像大小(32,7,7)
        )
        # 建立全卷积连接层
        self.out = nn.Linear(32 * 7 * 7, 10)  # 输出是10个类

    # x的传播路线
    def forward(self, x):
        x = self.conv1(x)  
        x = self.conv2(x) 
        # 因为pytorch里特征的形式是[bs,channel,h,w],所以x.size(0)就是batchsize
        x = x.view(x.size(0), -1)  # view就是把x弄成batchsize行个tensor
        output = self.out(x)
        return output
cnn = CNN()
print(cnn)

1.)nn.Module 是 PyTorch 中的一个基类,用于构建神经网络模型。它是 PyTorch 中神经网络模型的基础组件之一,所有自定义的神经网络模型都应该继承自 nn.Module 类。

nn.Module 提供了一些重要的功能,包括:

  1. 参数管理nn.Module 可以追踪和管理神经网络中的参数(权重和偏置),这些参数在模型的构建过程中自动注册。这使得参数的训练和更新变得容易。

  2. 前向传播方法:所有继承自 nn.Module 的自定义模型都需要实现一个 forward 方法,用于定义模型的前向传播逻辑。这个方法定义了如何从输入数据生成输出。

  3. 子模块管理nn.Module 允许将多个子模块组合在一起,形成复杂的神经网络结构。这有助于模块化和组合不同的网络层。

  4. 自动求导nn.Module 中的操作会自动跟踪梯度信息,从而使 PyTorch 能够进行自动求导和反向传播,以便训练模型。

以下是一个简单的示例,展示如何创建一个继承自 nn.Module 的自定义神经网络模型:

import torch
import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.fc1 = nn.Linear(64, 128)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        return x

在上述示例中,MyModel 类继承自 nn.Module,并定义了两线性层(nn.Linear)、一个激活函数层(nn.ReLU),以及一个前向传播方法(forward),它描述了模型的结构和计算逻辑。

使用 nn.Module 可以更轻松地构建和管理复杂的神经网络模型,并与 PyTorch 的其他模块和功能进行集成。这有助于加速神经网络的开发和训练过程。

2.)建立了两个卷积层

第一个卷积层 conv1,该卷积层包含了多个操作,使用了 nn.Sequential 将它们按顺序组合在一起。

self.conv1 = nn.Sequential(...):这行代码创建了一个名为 conv1 的卷积层,并使用 nn.Sequential 定义了卷积层中的一系列操作。nn.Sequential 允许将多个操作按照顺序组合在一起,作为一个整体的层。

nn.Conv2d(...):这是第一个操作,它定义了一个二维卷积层。具体的参数包括输入通道数(in_channels,在这里是1,因为MNIST数据集是灰度图像,只有一个通道)、输出通道数(out_channels,在这里是16,表示有16个卷积核)、卷积核大小(kernel_size,是一个5x5的卷积核)、步长(stride,为1,表示卷积核每次移动的像素数)、填充(padding,为2,表示在输入图像周围补零以保持输出大小不变)。

nn.ReLU():这是激活函数操作,它将卷积层的输出通过ReLU(修正线性单元)激活函数,将负数变为零,保持正数不变。y=x当x>=0;y=0当x<0。

nn.MaxPool2d(...):这是池化操作,使用最大池化(MaxPooling)对卷积层的输出进行下采样。kernel_size=2 表示使用2x2的池化窗口,将每个2x2的区域中的最大值保留,以减小图像尺寸。这会导致输出图像的大小减半。

第二个卷积层 conv2,与第一个卷积层类似,使用了 nn.Sequential 将一系列操作组合在一起。

self.conv2 = nn.Sequential(...):这行代码创建了一个名为 conv2 的卷积层,并使用 nn.Sequential 定义了卷积层中的操作序列。

nn.Conv2d(...):这是第一个操作,它定义了第二个二维卷积层。与第一个卷积层不同,这次的输入通道数(in_channels)为16,因为这是第一个卷积层的输出通道数,而输出通道数(out_channels)为32。其余参数与第一个卷积层相似,包括卷积核大小、步长和填充。

nn.ReLU():这是激活函数操作,将卷积层的输出通过ReLU激活函数。

nn.MaxPool2d(2):这是第二个池化操作,使用2x2的最大池化窗口对卷积层的输出进行下采样。这会导致输出图像的大小减半,从(32,14,14)到(32,7,7)。

3.)全卷积连接层

定义了神经网络的输出层。

self.out = nn.Linear(32 * 7 * 7, 10):这行代码创建了一个线性层(全连接层),将卷积神经网络的特征映射到最终的类别预测。

  • 32 * 7 * 7:这部分表示输入特征的维度,其中32是第二个卷积层的输出通道数,而7x7是该层输出的图像大小。

  • 10:这是输出层的输出维度,通常对应于分类任务中的类别数量。在这个示例中,模型将预测10个不同的类别。

全连接层的作用是将卷积神经网络中提取的特征映射转化为每个类别的得分或概率。这是神经网络中的最后一层,通常通过softmax函数或其他激活函数来生成类别预测。

这段代码的示例中,神经网络的结构包括两个卷积层用于特征提取,紧接着是一个全连接层用于最终的分类任务。

7.优化及损失函数

optimizer = torch.optim.Adam(cnn.parameters(), lr=LR)
loss_func = nn.CrossEntropyLoss()  

optimizer = torch.optim.Adam(cnn.parameters(), lr=LR):这行代码创建了一个 Adam 优化器,用于更新卷积神经网络 (CNN) 模型 (cnn) 的参数。cnn.parameters() 返回模型中所有可训练参数的迭代器。设置 lr 参数来指定学习率。

loss_func = nn.CrossEntropyLoss():这行代码创建了一个交叉熵损失函数 (loss_func),通常用于多类别分类任务。目标标签是one-hotted,这表明模型输出的是类别的概率分布,而交叉熵损失函数将与真实类别标签进行比较。交叉熵损失函数会自动处理类别标签的编码,因此不需要将标签进行 one-hot 编码。

8.训练

for epoch in range(EPOCH):
    for step, (b_x, b_y) in enumerate(train_loader): 
        output = cnn(b_x)  
        loss = loss_func(output, b_y) 
        optimizer.zero_grad()  
        loss.backward() 
        optimizer.step() 
        if step % 50 == 0:
            test_output = cnn(test_x)
            pred_y = torch.max(test_output, 1)[1].data.numpy()
            accuracy = float((pred_y == test_y.data.numpy()).astype(int).sum()) / float(test_y.size(0))
            print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.numpy(), '| test accuracy: %.2f' % accuracy)
torch.save(cnn.state_dict(), 'cnn2.pkl')

for epoch in range(EPOCH)::这是训练循环的外部循环,它会迭代指定的训练周期(epochs),其中 EPOCH 是在之前定义的训练周期数量。

for step, (b_x, b_y) in enumerate(train_loader)::这是训练循环的内部循环,它会迭代每个小批次的训练数据。train_loader 包含了训练数据集,每次迭代会提供一个小批次的输入数据 b_x 和对应的标签 b_y

output = cnn(b_x):这行代码将输入数据 b_x 通过卷积神经网络 cnn 运行,以获得模型的输出 output。这是前向传播的过程。

loss = loss_func(output, b_y):这行代码计算了模型输出 output 和真实标签 b_y 之间的损失,使用的是之前定义的交叉熵损失函数 loss_func

optimizer.zero_grad():这一行代码用于清除之前计算的梯度信息,以准备计算新的梯度。

loss.backward():这是反向传播的过程,它计算了损失相对于模型参数的梯度信息。

optimizer.step():这一行代码应用梯度更新模型的参数,以便减小损失。

if step % 50 == 0::这是一个条件语句,用于在每个批次的一部分中执行以下操作:

test_output = cnn(test_x):对测试数据 test_x 运行模型,以获得测试数据的输出。

pred_y = torch.max(test_output, 1)[1].data.numpy()

  • test_output:这是模型对测试数据 test_x 的输出。通常,这个输出是一个包含了每个类别的分数或概率分布。

  • torch.max(test_output, 1):这个部分使用 PyTorch 的 max 函数,第一个参数是 test_output,第二个参数 1 表示在每个样本中沿着第一个维度(通常是类别维度)找到最大值。

  • [1]:一旦 max 函数找到了每个样本中的最大值,它返回一个包含两个张量的元组。第一个张量包含最大值,第二个张量包含最大值的索引。[1] 部分用于提取第二个张量,即最大值的索引。

  • .data:这一步是为了获取包含索引值的数据部分。在 PyTorch 中,张量对象具有 .data 属性,通过它可以获取张量中的原始数据。

  • .numpy():最后,.numpy() 方法将 PyTorch 张量转换为 NumPy 数组,以便更容易地处理和分析。

accuracy = float((pred_y == test_y.data.numpy()).astype(int).sum()) / float(test_y.size(0))

 

  • pred_y:这是模型对测试数据的预测类别,即模型预测的每个样本的类别。

  • test_y.data.numpy():这是测试数据的真实类别标签。与 pred_y 一样,它是一个包含了每个样本的真实类别的 NumPy 数组。

  • (pred_y == test_y.data.numpy()):这一部分进行元素级的比较,对每个样本的预测类别和真实类别进行比较。结果是一个布尔值数组,其中 True 表示模型的预测与真实标签一致,False 表示不一致。

  • .astype(int):将布尔值数组转换为整数数组,其中 True 变成 1False 变成 0

  • .sum():对整数数组中的元素求和,得到预测正确的样本数量。

  • float(test_y.size(0))test_y.size(0) 表示测试数据集中的总样本数量。将其转换为浮点数是为了进行除法运算。

torch.save(cnn.state_dict(), 'cnn2.pkl'):在每个训练周期结束后,该行代码将训练好的模型参数保存到名为 'cnn2.pkl' 的文件中,以便后续的使用或部署。

9.加载和可视化

cnn.load_state_dict(torch.load('cnn2.pkl'))
cnn.eval()
# print 10 predictions from test data
inputs = test_x[:32]  # 测试32个数据
test_output = cnn(inputs)
pred_y = torch.max(test_output, 1)[1].data.numpy()
print(pred_y, 'prediction number')  # 打印识别后的数字
# print(test_y[:10].numpy(), 'real number')

img = torchvision.utils.make_grid(inputs)
img = img.numpy().transpose(1, 2, 0)

# 下面三行为改变图片的亮度
# std = [0.5, 0.5, 0.5]
# mean = [0.5, 0.5, 0.5]
# img = img * std + mean
cv2.imshow('win', img)  # opencv显示需要识别的数据图片
key_pressed = cv2.waitKey(0)

cnn.load_state_dict(torch.load('cnn2.pkl')):这行代码加载之前训练好的 CNN 模型参数。cnn2.pkl 是保存了模型参数的文件名。

cnn.eval():这一行将模型设置为评估模式(evaluation mode),这意味着模型不会更新梯度,并且不会执行丢弃(dropout)等训练特定的操作。在推理阶段,通常需要将模型设置为评估模式。

inputs = test_x[:32]:选择测试数据中的前 32 个样本进行推理。这里选择了前 32 个样本以便进行可视化展示。

test_output = cnn(inputs):将测试数据传递给模型,获取模型的输出。这是模型的前向传播过程。

pred_y = torch.max(test_output, 1)[1].data.numpy():从模型的输出中获取预测的类别,类似于之前解释的部分。这将返回一个包含预测类别的 NumPy 数组。

print(pred_y, 'prediction number'):这行代码打印出模型对测试数据的预测类别,以及 "prediction number" 的标签。

图片可视化部分:

  • img = torchvision.utils.make_grid(inputs):这行代码将输入数据的前 32 个样本合成一张图片,以便进行可视化。

  • img = img.numpy().transpose(1, 2, 0):将合成的图片从 PyTorch 张量转换为 NumPy 数组,并重新排列维度,以适应图像显示的要求。

    transpose(1, 2, 0):这是一个维度重排操作,通过交换维度的位置,将图像的维度从 (C, H, W) 重新排列为 (H, W, C)。具体来说:

    • 第一个维度(维度 0)是通道数,通常是颜色通道,例如红、绿、蓝 (RGB)。

    • 第二个维度(维度 1)是图像的高度。

    • 第三个维度(维度 2)是图像的宽度。

将维度重新排列为 (H, W, C) 是因为图像显示库(如 OpenCV)通常期望图像的维度顺序是 (高度, 宽度, 通道)

cv2.imshow('win', img):这行代码使用 OpenCV 显示合成的图片,以便查看测试的数据图片。

key_pressed = cv2.waitKey(0):这行代码等待用户按键,以便查看图像后续关闭窗口。cv2.waitKey(0) 会阻塞程序,直到用户按下键盘的任意键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值