深度学习笔记01-MNIST手写数字识别

前言

一、导入相关库,设置GPU

二、导入MNIST手写数字数据集

三、部分数据可视化

四、创建简易的CNN网络模型

五、编写训练函数

六、编写测试函数

七、开始训练

八、结果可视化

总结


前言

 本文为🔗365天深度学习训练营 中的学习记录博客

原作者:K同学啊


一、导入相关库,设置GPU

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvision

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

         设置用于计算的设备。如果电脑中有可用的GPU,将使用第一个GPU设备进行运算,没有则使用CPU。

二、导入MNIST手写数字数据集

代码如下:

train_dataset = torchvision.datasets.MNIST(root='./data', train=True, 
                                          transform=torchvision.transforms.ToTensor(), download=True)
test_dataset = torchvision.datasets.MNIST(root='./data', train=False, 
 
batch_size = 32
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size)

通过torchvsion.datasets.MNIST函数获取MNIST数据集,root表示数据集要存储的路径,train=True表示选取训练集,train=False表示选取测试集,transform=torchvision.transforms.ToTensor()是将数据转化为tensor类型。download=True代表从网上下载数据集并存储到root目录下。

使用torch.utils.data.DataLoader函数来分别加载训练集和测试集,batch_size为32,意为每批选取32张照片。shuffle=True表示随机打乱顺序。其它常用参数有num_workers : 用于数据加载的进程数。默认值为0。

imgs, labels = next(iter(train_loader))
imgs.shape

 选择第一个批次查看数据,数据的形状为[batch_size, channel, height, width]。

iter(train_loader)将训练数据加载器转化为一个数据迭代器,然后使用next()函数获取数据迭代器中的下一个元素,由于是第一次使用next()函数,所以会从训练数据集中加载第一个批次数据。

                                                                                                                       

可以看到第一个批次的图片数据形状为[batch_size=32, channel=1, height=28, width=28]。图片是黑白图片,所有channel=1,其像素为28*28。 

三、部分数据可视化

import numpy as np
# 指定图片大小, 图像大小为宽20高5的绘图(英寸inch)
plt.figure(figsize=(20, 5))
for i, imgs in enumerate(imgs[:20]): # 显示前20张图片
    img = np.squeeze(imgs.numpy()) # 维度缩减=>(32, 28, 28)
    # 将整个figure分成2行10列,绘制第i+1个子图
    plt.subplot(2, 10, i+1)
    plt.imshow(img, cmap=plt.cm.binary) # 显示灰度图
    plt.axis('off') # 不显示坐标轴
plt.show()

首先设置画布大小为宽度20,高度5,单位为inch。然后绘制第一个批次的前20张图片。先用np.squeeze(imgs.numpy())函数将图片的第"1"个维度也就是channel维度删掉,所以shape由[32, 1, 28, 28]变为[32, 28, 28],然后分成2行10列进行绘制,cmap=plt.cm.binary显示灰度图,plt.axis('off')表示不显示坐标轴。

四、创建简易的CNN网络模型

import torch.nn.functional as F

num_classes = 10 # 图片的类数

class model(nn.Module):
    def __init__(self):
        super(model, self).__init__()
        # 特征提取网络
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3)
        self.pool1 = nn.MaxPool2d(2)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3)
        self.pool2 = nn.MaxPool2d(2)
        # 分类网络
        self.fc1 = nn.Linear(5*5*64, 64)
        self.fc2 = nn.Linear(64, num_classes)
    
    def forward(self, x): # 前向传播
        x = self.conv1(x)
        x = F.relu(x)
        x = self.pool1(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = self.pool2(x)

        x = torch.flatten(x, start_dim=1)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)

        return x

 网络结构图:

首先进行特征提取,经过一个卷积核为3x3的卷积层,此时输出通道为32,图片尺寸为26x26,然后经过relu激活函数,再经过一个池化核为2x2的最大池化层,图片尺寸变为原来的一半,即13x13。通过一个卷积核为3x3的卷积层,通道数变成原来的2倍,即64,图片尺寸变为11x11,随后通过relu激活函数,再进入一个池化核为2x2的最大池化层,尺寸变为5x5。

然后进行分类,通过torch.flatten函数将数据展平,通过第一个全连接层,input_features=5*5*64,out_features=64,使用relu激活函数,最后经过第二个全连接层,将64降维到num_classes=10,即图片的类别数。

# 加载并打印模型
from torchinfo import summary
model = model().to(device)
summary(model)

打印模型查看

五、编写训练函数

# 设置超参数
loss_fn = nn.CrossEntropyLoss() # 创建损失函数
learning_rate = 1e-2 
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

设置训练所需的一些超参数,选择交叉熵损失函数为损失函数,学习率设置为0.01,优化器选用的SGD即随机梯度下降,也可以使用Adam等其它优化器。

### 编写训练函数 ###
def train(train_loader, model, loss_fn, optimizer):
    size = len(train_loader.dataset) # 训练集的大小, 一共60000张图片
    num_batches = len(train_loader) # 批次数目,1875(60000/32)

    train_loss, train_acc = 0, 0 # 初始化训练损失和准确率
    for x, y in train_loader:
        x, y = x.to(device), y.to(device) 
        pred = model(x) # 网络输出
        loss = loss_fn(pred, y) # 计算网络输出和真实值之间的差距, targets为真实值, 计算二者差值即为损失
        optimizer.zero_grad() # 梯度清零
        loss.backward() # 反向传播
        optimizer.step() # 更新参数

        train_acc += (pred.argmax(1) == y).type(torch.float).sum().item() 
        train_loss += loss.item()
    
    train_acc /= size # 所有训练样本的平均预测准确率
    train_loss /= num_batches # 每个批次的平均损失

    return train_acc, train_loss

len(train_loader.dataset)为训练集样本的数量,一共60000张图片。len(train_loader)是批次的数目,一共是60000张照片,batch_size=32,所有批次数目是60000/32=1875。通过for循环遍历每个批次,先将数据和标签拷贝到GPU上运算,model(x)得到网络的输出,然后通过输出和真实标签值计算得到损失函数值,将梯度清零,然后反向传播,不断地更新权重参数。

(pred.argmax(1) == y).sum().item()表示计算预测正确的样本数量,.item()将结果转换为python类型标量值,便于在python中使用。

最后函数返回训练准确率和训练损失。

六、编写测试函数

测试中不进行梯度下降来更新网络权重参数,因此不需要传入优化器。

def test(test_loader, model, loss_fn):
    size = len(test_loader.dataset) # 测试集的大小, 一共10000张照片
    num_batches = len(test_loader) # 批次数目,313(10000/32=312.5向上取整)
    test_loss, test_acc = 0, 0
    # 当不进行训练时,停止梯度更新,节省计算内存消耗
    with torch.no_grad():
        for imgs, targets in test_loader:
            imgs, targets = imgs.to(device), targets.to(device)
            target_pred = model(imgs)
            loss = loss_fn(target_pred, targets)

            test_loss += loss.item()
            test_acc += (target_pred.argmax(1) == targets).type(torch.float).sum().item()

    test_acc /= size
    test_loss /= num_batches

    return test_acc, test_loss

with.torch.no_grad(),测试时停止梯度更新,可以节省计算内存消耗。其余部分和训练函数大致相同。

七、开始训练

epochs = 10
train_loss = []
train_acc = []
test_loss = []
test_acc = []

for epoch in range(epochs):
    model.train()
    epoch_train_acc, epoch_train_loss = train(train_loader, model, loss_fn, optimizer)

    model.eval()
    epoch_test_acc, epoch_test_loss = test(test_loader, model, loss_fn)

    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)

    template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}')
    print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss))
print('Done')

 设置epochs = 10,即进行10轮训练,将训练损失、训练准确率、测试损失、测试准确率存入空列表,以便后续进行可视化。model.train()开启训练模式,也就是启用Batch Normalization和Dropout。由于测试时不需要批量正则化和dropout,因此在测试时需要添加model.eval(),不启用BN和Dropout。

 八、结果可视化

import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore') # 忽略警告信息
plt.rcParams['font.sans-serif'] = ['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号
plt.rcParams['figure.dpi'] = 100 # 图片分辨率

epochs_range = range(epochs)
plt.figure(figsize=(12, 3))

plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

最终绘制损失函数和准确率。 


总结

本文通过使用简易的CNN模型实现了MNIST手写数字数据集的识别。

主要流程如下:

1.  导入相关的库,配置设备GPUorCPU

2. 通过torchvison.datasets.MNIST函数导入MNIST数据集,并通过torch.utils.data.DataLoader函数按批次加载数据集

3. 可以选择查看一个批次的图片,将其可视化。可以通过next(iter())函数实现。

4. 建立卷积神经网络模型,分为特征提取网络和分类网络,特征提取一般步骤为:卷积-激活-池化,不断重复,提取图片特征。然后将特征展平进入全连接层进行最后的分类,将特征降维到图片的类别数。

5. 编写训练函数和测试函数,首先要设置超参数,学习率,优化器,损失函数。然后分批加载数据集,计算预测输出,损失函数,梯度清零,反向传播,参数更新。最后函数返回损失函数值和准确率。

6. 设置epoch,进行主循环。注意训练时使用model.train(),测试使用model.eval()。

7. 最后可以将训练loss、测试loss、训练准确率、测试准确率进行可视化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值