Pytorch实现MNIST手写数字识别

Pytorch实现MNIST手写数字识别

跟着莫烦大佬学习pytorch,根据自己的理解,把原来的代码改了一点(主要按我自己的理解,可能改错了。。。。。。),然后敲了一遍,记录在下面,方便后面复习。

莫烦大佬视频链接:https://mofanpy.com/tutorials/machine-learning/torch/

一. 导入要使用的库
import torch#torch不解释
import numpy as np#矩阵运算
import torchvision#获取数据集
import matplotlib.pyplot as plt #画图
import torch.nn as nn #Torch的神经网络模块
import torch.nn.functional as F #函数模块
import torch.utils.data as Data #处理数据集的数据
import time#计算程序耗时
二.获取数据集
train_data = torchvision.datasets.MNIST(   #获取数据集
    root='./mnist/',#保存的目录
    train=True,#True返回的是训练数据,否则是测试数据
    transform=torchvision.transforms.ToTensor(),#转换成Tensor类型
    download=True#数据已经下好了,就改为False
)
print(train_data.train_data.size())#看看训练数据的维度
print(train_data.train_labels.size())#看看标签的维度
train_datas = torch.unsqueeze(train_data.train_data,dim = 1).float()#增加一个维度
print(train_datas.size())#看看增加后训练数据的维度
train_labels = train_data.train_labels#获取标签
print(train_labels.size())
#train_labels = F.one_hot(train_labels,10)#这里不需要转换编码
dataset = Data.TensorDataset(train_datas,train_labels)#讲数据放入数据库中
test_data = torchvision.datasets.MNIST(      #获取测试数据
    root='./mnist/',
    train=False
)
test_datas = torch.unsqueeze(test_data.test_data,dim = 1).float()#增加维度
print(test_datas.size())#看看增加后测试集的维度
test_labels = test_data.test_labels
print(test_labels.size())#看看测试集标签的维度

train_load = Data.DataLoader(#划分batch
    dataset=dataset,
    shuffle=True,#打乱
    batch_size=5
)

输出:

torch.Size([60000, 28, 28])
torch.Size([60000])
torch.Size([60000, 1, 28, 28])
torch.Size([60000])
torch.Size([10000, 1, 28, 28])
torch.Size([10000])

可见,训练集有6000张28x28的图片。

几个问题:

  • 为什么要增加一个维度?

    Conv2d官方文档
    在这里插入图片描述
    可见我们后面要使用到的Conv2d模块的输入格式要求四维,维数的格式如上图。这里训练数据为(6000,28,28)需要转换成(6000,1,28,28),可以看成是6000个batch_size为1的单通道图片集合。

  • 使用的交叉熵损失函数,为什么不需要将数字标签转换成one-hot编码的方式?

    写到损失函数时,困惑于为啥直接用数字标签进行训练,后来发现原来是函数太强大了~~

    torch.nn.CrossEntropyLoss()不仅不需要你对输出进行Softmax(),也不要进行one-hot编码转换,直接数字标签即可,不愧为方便。当然数字标签转one-hot,可以使用train_labels = F.one_hot(train_labels,10),那个10是类别数。

在这里插入图片描述
在这里插入图片描述

可见,CrossEntropyLoss()的输入格式是(N,C),C指的是类别数,这里输入维度应该是(batch_size,10)。而target(即真实标签)维度为(N),所以训练数据的标签不需要提升维度

  • 为什么要用Data.TensorFloat()?

    主要是方便设置batch_size,训练数据为(6000,1,28,28),太大了,可以分成(5,1,28,28)之类的进行训练。

  • 一些维度的理解(不一定对)

在这里插入图片描述

所以这里处理训练集数据时:

获取的是如下情形,如果不处理直接拿整个数据集进行训练(可见太大了),相当于batch_size = 6000(训练数据个数),维度为(6000,1,28,28)(这里需要升维度,原因前面有解释)。

在这里插入图片描述

然后利用Data.TensorDataset和Data.DataLoader来划分成batch的形式。这里用(5,1,28,28)。

通过cnn网络提取后一张图片特征向量是维度为10,这里一次性5张,所以输出格式是(5,10),满足CrossEntropyLoss()的输入格式。按照这一思维,这里对应得数字标签应该是(5,1)才对,而实际上使用的是(5)?原因和CrossEntropyLoss()函数的输入格式有关,上面已经提及到了。

三. 搭建网络
class CNN(torch.nn.Module):
    def __init__(self):
        super(CNN,self).__init__()#对父类进行初始化
        self.conv1 = torch.nn.Sequential(
            torch.nn.Conv2d(
                in_channels = 1,
                out_channels = 16,
                kernel_size = 5,
                stride = 1,
                padding = 2,
            ),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size = 2)
        )
        self.conv2 = torch.nn.Sequential(
            torch.nn.Conv2d(
                in_channels = 16,
                out_channels = 32,
                kernel_size = 5,
                stride = 1,
                padding = 2
            ),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size = 2)
        )
        self.out = torch.nn.Linear(32*7*7,10)
    def forward(self,x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = x.view(x.size(0),-1)#考虑到batch
        output = self.out(x)
        return output#返回特征向量
    
cnn = CNN()#实例化一个网络
device = torch.device('cuda:0')#选取GPU
cnn = cnn.to(device)#将模型放到GPU上
print(cnn)#打印网络结构

输出:

CNN(
  (conv1): Sequential(
    (0): Conv2d(1, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (1): ReLU()
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (conv2): Sequential(
    (0): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (1): ReLU()
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (out): Linear(in_features=1568, out_features=10, bias=True)
)
四. 开始训练

optimizer = torch.optim.Adam(cnn.parameters(),lr = 0.001)#设置优化器
#设置损失函数
loss_func = torch.nn.CrossEntropyLoss()#只需要数字标签即可,不用转换成one-hot编码
print('开始训练......')
for epoch in range(200):#训练200轮
    start = time.time()#记录开始时间
    for step,(train_x,train_y) in enumerate(train_load):#用枚举进行遍历,
        #step没用上,单纯就是为了熟悉一下这种方式,有序遍历用enumerate
        #print(train_x.size())
        train_x = train_x.to(device)#网络在GPU上,训练数据当然要放在GPU上。
        train_y = train_y.to(device)
        prediction = cnn(train_x)#计算一个batch的特征向量
        # print(printdiction)
        # print(train_y)
        loss = loss_func(prediction,train_y)#计算成误差
        optimizer.zero_grad()#梯度清零
        loss.backward()#反向传播,计算梯度
        optimizer.step()#更新
    #开始严重测试集误差
    with torch.no_grad():#这里面的不需要计算梯度,节省内存
        ac_sum = 0
        if(epoch%5 == 0):#训练5轮。。。。
            pre_test = []
            for test in test_datas:#这里一个一个推理,直接用全部,爆显存???
                test = torch.unsqueeze(test,dim = 0)#遍历过后维度下降了,得升回来
                test = test.to(device)
                pre_test.append(cnn(test).cpu().numpy())#转化成numpy放回cpu上去
                #这里维度为(1,10)
            #print(pre_test)
            #这里如果不先转成numpy类型,list中的类型为tensor,下一步就会报错,为啥?百度的
            pre_test = torch.FloatTensor(pre_test)#list->Tensor
            print(pre_test.size())#维度为(1000,1,10),需要去掉维度为1的那一维,原因见上面CrossEntropyLoss()
            pre_test = torch.squeeze(pre_test)#去掉维度为1的那一维
            pred_y = torch.max(pre_test,1)[1].data#待会儿写篇博客解释
            for x,y in zip(pred_y,test_labels):#同时遍历两个序列用zip
                if(x == y):
                    ac_sum += 1
            accuracy = ac_sum/ test_labels.size(0)
            print("经过%d次训练,在测试集上准确率为:%.4f,耗时:%.4f"%(epoch,accuracy,time.time()-start))
            torch.save(cnn.state_dict(), 'net_params.pkl') #保存参数

开始:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值