使用神经网络识别手写数字

时间:2022-10-06 15:56:11

PyTorch, MNIST

本文目标:理解代码,能够复现
更多细节指路⭐️代码参考原博客写得非常详细????

实际上识别手写数字是大二《人工智能》的一个实验,当时用的是TensorFlow

使用神经网络识别手写数字
对于这个数据集手动扩展训练数据的话,比如平移、旋转一个角度这样.
Neural Networks and Deep Learning.Michael Nielsen非常通俗易懂,以此为例带人入门神经网络.快看完了,真的对新手友好????

Model

# model.py

# 继承于nn.Module这个父类
class MnistNet(nn.Module):
    # 初始化网络结构
    def __init__(self):
        super(MnistNet, self).__init__()
        # 全连接
        self.fc1 = nn.Linear(28 * 28 * 1, 28)
        self.fc2 = nn.Linear(28, 10) # 尺寸,数据集的类别10

    # 正向传播过程
    def forward(self, x):
        # 把三维的张量展平成一维向量
        x = x.view(-1, 28 * 28 * 1)
        x = self.fc1(x)  # [batch_size,28]
        x = F.relu(x)  # [batch_size,28]
        x = self.fc2(x)  # [batch_size,10]
        # return x
        # return F.sigmoid(x)
        return F.log_softmax(x, dim=-1) # 对softmax取对数

Train

import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
import torchvision

# 一般都要分批次进行训练,硬件算力非常有限
train_batch_size = 64
test_batch_size = 1000
# MNIST数据集中图像的尺寸28*28
img_size = 28

def get_dataloader(train=True):
    assert isinstance(train, bool), "train 必须是bool类型"

    # 准备数据集,其中0.1307,0.3081为MNIST数据的均值和标准差,这样操作能够对其进行标准化
    # 因为MNIST只有一个通道(黑白图片),所以元组中只有一个值
    # 第一次使用要将download设置为True才会自动下载数据集
    # torchvision.datasets在先导入pytorch中的数据集
    dataset = torchvision.datasets.MNIST('/data', train=train, download=False,
                                         transform=torchvision.transforms.Compose([
                                             torchvision.transforms.ToTensor(),
                                             torchvision.transforms.Normalize((0.1307,), (0.3081,)), ]))
    # 准备数据迭代器
    batch_size = train_batch_size if train else test_batch_size
    dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True)
    return dataloader


# model.py
···

# 定义训练的网络模型
mnist_net = MnistNet()
# 优化器adam,学习率设为0.001
optimizer = optim.Adam(mnist_net.parameters(), lr=0.001)
# criterion = nn.NLLLoss()
# criterion = nn.CrossEntropyLoss()
train_loss_list = []
train_count_list = []


def train(epoch):
    mode = True
    mnist_net.train(mode=mode)
    train_dataloader = get_dataloader(train=mode)
    # loss_function = nn.CrossEntropyLoss()
    print(len(train_dataloader.dataset))
    print(len(train_dataloader))
    # 遍历训练集
    for idx, (data, target) in enumerate(train_dataloader):
        # 清除历史梯度
        optimizer.zero_grad()
        # 正向传播
        output = mnist_net(data)
        # 计算损失
        # loss = loss_function(output, target)  #交叉熵损失
        loss = F.nll_loss(output, target) # 对数似然损失
        loss.backward() # 反向传播
        optimizer.step() # 优化器更新参数
        if idx % 10 == 0: # print every 10 mini-batches
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch+1, idx * len(data), len(train_dataloader.dataset),
                       100. * idx / len(train_dataloader), loss.item()))

            train_loss_list.append(loss.item())
            train_count_list.append(idx * train_batch_size + (epoch - 1) * len(train_dataloader))
            # 保存训练得到的参数
            # python中有一种存储方式,可以存储为.pkl文件,可以将python项目过程中用到的一些暂时变量、或者需要提取、暂存的字符串、列表、字典等数据保存起来
            torch.save(mnist_net.state_dict(), "./mnist_net.pkl")
            torch.save(optimizer.state_dict(), './mnist_optimizer.pkl')

# 测试
def test():
    test_loss = 0
    correct = 0
    mnist_net.eval()
    test_dataloader = get_dataloader(train=False)
    with torch.no_grad(): # 冻结参数:
        # 在以下步骤中不用计算每个节点的损失梯度,以防止过度占用内存
        for data, target in test_dataloader:
            output = mnist_net(data)
            test_loss += F.nll_loss(output, target, reduction='sum').item()
            pred = output.data.max(1, keepdim=True)[1]  # 获取最大值的位置,[batch_size,1]
            correct += pred.eq(target.data.view_as(pred)).sum()
    test_loss /= len(test_dataloader.dataset)
    print('\nloss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format(
        test_loss, correct, len(test_dataloader.dataset),
        100. * correct / len(test_dataloader.dataset)))


if __name__ == '__main__':

    test()
    for i in range(10):  # epoch自己根据需要调轮数
        train(i)
        test()

使用神经网络识别手写数字

Result

使用神经网络识别手写数字
原代码跑的epoch1的acc=93.26%
其实没训练过的时候,随便也有12%~13%
使用神经网络识别手写数字
跑完acc有95.75%

但是《NNDL》上说精度能到99%,所以简单鼓捣一下

1、简单粗暴增加epoch次数
epoch = 10
acc=96.33%
使用神经网络识别手写数字
epoch = 30
acc=96.33%
使用神经网络识别手写数字

2、损失函数
这里学到的,在pytorch中有两种方式实现交叉熵损失
①loss = nn.CrossEntropyLoss(input,target)

②把softmax概率传入对数似然损失得到的损失函数:

#1. 对输出值计算softmax和取对数
output = F.log_softmax(x,dim=-1)
#2. 使用torch中带权损失
loss = F.nll_loss(output,target)

3、激活函数
把softmax换成sigmoid
使用神经网络识别手写数字不明白

更改超参数得到改进不是很随便的,但是简单任务来看,训练epoch多一点,精度好一点,一般来讲.