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

PyTorch, MNIST

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

    实际上识别手写数字是大二《人工智能》的一个实验,当时用的是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%
    dierci
    epoch = 30
    acc=96.33%
    disici

    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多一点,精度好一点,一般来讲.

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用神经网络识别手写数字

    发表评论