如何使用Pytorch迅速写一个Mnist数据分类器

一段时间没有更新博文,想着也该写两篇文章玩玩了。而从一个简单的例子作为开端是一个比较不错的选择。本文章会手把手地教读者构建一个简单的Mnist(Fashion-Mnist同理)的分类器,并且会使用相对完整的Pytorch训练框架,因此对于初学者来说应该会是一个方便入门且便于阅读的文章。本文的代码来源于我刚学Pytorch时的小项目,可能在形式上会有引用一些github上的小代码。同时文风可能会和我之前看的一些外国博客有点相近。

本文适用对象: 刚入门的Pytorch新手,想要用Pytorch来完成作业的鱼干。

那么就开始coding吧。

首先,你需要安装好Python 3+,Pytorch 1.0+,我个人使用的是Pytorch1.4,我想1.0以上的版本都可以使用。

然后在想要的位置,新建一个main.py的文件,然后就可以开始敲键盘了。

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets
from torchvision import transforms
import torch.utils.data import argparse

第一步自然是导入相应的包。前面的都是Pytorch的包,最后一句导入的argparse便于用来修改训练的参数,这在Pytorch复现深度学习模型时非常常见。

model_names = ['Net','Net1']

parser = argparse.ArgumentParser(description='PyTorch Mnist Training')
parser.add_argument('-a', '--arch', metavar='ARCH', default='Net', choices=model_names,
help='model archtecture: ' + '|'.join(model_names) + '(default:Net)')
parser.add_argument('--epochs', default=5, type=int, metavar='N', help='number of total epochs')
parser.add_argument('--momentum', default=0.9, type=float, metavar='M', help='momentum')
parser.add_argument('-b', '--batchsize', default=32, type=int, metavar='N', help='mini-batch size')
parser.add_argument('--lr', '--learning-rate', default=1e-2, type=float, metavar='LR', help='initial learning rata',
dest='lr')
args=vars(parser.parse_args())

第一行的model_names是一个list,用来存储我们之后会实现的两种网络结构的名字。然后我定义了一个argparse的对象,关于argparse可以自寻一些教程观看,大概只需要知道可以从指令行输入参数即可。在parser中又定义了arch(使用的网络),epochs(迭代轮次),momentum(梯度动量大小),batchsize(一次送入的图片量大小),learning-rate(学习率)参数。之前的model_name也正是用在arch参数中,限定了网络框架将会从此二者中选择其一。

def main():
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") #parameter
batch_size = args["batchsize"]
lr = args["lr"]
momentum = args["momentum"]
num_epochs = args["epochs"]

主函数中,先定义cuda对象,便于使用gpu并行运算。在#parameter中,我们把一些从命令行中获得的参数引入到相应的变量中,以便后续书写。

    #prepare the dataset

    mnist_data = datasets.MNIST("./mnist_data",train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=(0.13,),std=(0.31,))
]))
'''
mnist_data = datasets.FashionMNIST("./fashion_mnist_data", train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=(,), std=(,))
]))
'''
train_loader = torch.utils.data.DataLoader(
mnist_data,batch_size=batch_size,shuffle=True, num_workers=1,pin_memory=True
)
test_loader = torch.utils.data.DataLoader(
mnist_data,batch_size=batch_size,shuffle=True, num_workers=1,pin_memory=True
)

之后将引入Pytorch中datasets包自带的MNIST集,download参数设置为True,以便于本地没有Mnist数据集时直接下载,之后会在当前目录下创建一个mnist_data的文件夹以存放数据,。transform中的transforms.ToTensor()是用于将图片形式的数据转换成tensor类型,而transforms.Normalize(mean=(0.13,),std=(0.31,))则是将tensor类型的数据进行归一化,这里的0.13和0.31可以直接使用。如果你想要使用注释中的FashionMNIST数据集则需要使用的是注释中的内容,当然,mean和std需要另外求解。

之后,定义train_loadertest_loader,将数据集作为可迭代的对象使用。shuffle=True以实现乱序读取数据,一般都会这么设置。num_workerspin_memory都会影响到数据读取速度,前者是会在读取时创建多少个进程,后者是影响到数据读入到GPU中,一般来说,对于这个项目前者设置为1已经够用,后者设置为True和False都不影响。在更大型的项目中,如果设备较好,前者可以设置大一些。

    model = Net1().to(device) if args["arch"]=='Net1' else Net().to(device)
optimizer = torch.optim.SGD(model.parameters(),lr=lr,momentum=momentum)
criteon = nn.CrossEntropyLoss().to(device)

第一行是model实例化,并且会根据args["arch"]选择是用Net还是Net1to(device)会将model放置于device上运行。第二行定义了一个优化器,使用的是SGD,并且放入model的参数、学习率和动量大小。criteon定义损失函数,这边使用的是交叉熵函数,这一损失函数在分类问题中十分常用。

    #train
for epoch in range(num_epochs):
train(model,device,train_loader,optimizer,epoch,criteon)
test(model,device,test_loader,criteon) torch.save(model.state_dict(), "mnist_{}.pth".format(num_epochs))

这就是训练过程,在其中又使用了traintest两个函数(下面会说),根据num_epochs数目进行循环。循环结束后,torch.save将会把模型的参数model.state_dict()mnist_{}.pth的形式存放到当前文件夹下。

def train(model,device,train_loader,optimizer,epoch,criteon):
class_name = model.__class__.__name__ model.train()
loss = 0
for idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
pred = model(data)
if class_name == 'Net':
loss = F.nll_loss(pred, target)
elif class_name == 'Net1':
loss = criteon(pred, target) optimizer.zero_grad()
loss.backward()
optimizer.step() if idx % 100 == 0:
print("train epoch: {}, iteration: {}, loss: {}".format(epoch, idx, loss.item()))

这里定义了train函数的训练过程。class_name中存放了当前使用的模型名字。 model.train()开启训练模式。在for idx, (data, target) in enumerate(train_loader):取出当前数据集的idx,data和种类target。循环中,先把data和target放置于device上,pred = model(data)会进行一次前传,获得相应数据的预测种类pred

对不同的模型,我采用了不同定义损失函数的方式,这里需要结合下面的模型结构来看。optimizer.zero_grad()会将上轮累计的梯度清空,之后loss.backward()梯度反向传播,利用optimizer.step()更新参数。而当if idx % 100 == 0:也就是迭代的数据批次到达100的倍数了,就会输出相关信息。

def test(model,device,test_loader,criteon):
class_name = model.__class__.__name__
model.eval()
total_loss = 0 #caculate total loss
correct = 0
with torch.no_grad():
for idx, (data, target) in enumerate(test_loader):
data, target = data.to(device), target.to(device)
pred = model(data)
if class_name == 'Net':
total_loss += F.nll_loss(pred, target,reduction="sum").item()
elif class_name == 'Net1':
total_loss += criteon(pred, target).item()
correct += pred.argmax(dim=1).eq(target).sum().item() total_loss /= len(test_loader.dataset)
acc = correct/len(test_loader.dataset)
print("Test loss: {}, Accuracy: {}%".format(total_loss,acc*100))

test函数总体结构类似,model.eval()将会把模型调整测试模式,with torch.no_grad():来声明测试模式下不需要积累梯度信息。correct += pred.argmax(dim=1).eq(target).sum().item()则是会计算出预测对了的数目,之后通过total_loss计算总误差和acc计算准确率。

class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.conv1 = nn.Conv2d(1, 20, kernel_size=5,stride=1)
self.conv2 = nn.Conv2d(20,50,kernel_size=5,stride=1)
self.fc1 = nn.Linear(4*4*50, 500)
self.fc2 = nn.Linear(500, 10) def forward(self, x):
x = F.relu(self.conv1(x))
x = F.max_pool2d(x,2,2)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x,2,2)
x = x.view(-1,4*4*50)
x = F.relu(self.fc1(x))
x = self.fc2(x)
x = F.log_softmax(x,dim=1)
return x

Net不过是一个具有两个卷积层和两个线性全连层的网络。self.conv1 = nn.Conv2d(1, 20, kernel_size=5,stride=1)表示conv1是一个接受1个channel的tensor输出20个channel的tensor,且卷积大小为5,步长为1的卷积层。self.fc1 = nn.Linear(4*4*50, 500)则是接收一个4 * 4 * 50长的一维tensor并且输出长为500的一维tensor。

前传函数forward中,x作为输入的数据,输入后会通过 conv1->relu->pooling->conv2->relu->pooling->view将多维tensor转化成一维tensor->fc1->relu->fc2->log_softmax来获得最终的x的值。这里就需要提train和test函数中的if和elif语句了。使用的时Net时,loss = F.nll_loss(pred, target),这是因为log_softmax之后使用nll_loss和直接使用 nn.CrossEntropyLoss()是等效的,因此:

class Net1(nn.Module):
def __init__(self):
super(Net1,self).__init__()
self.conv_unit=nn.Sequential(
nn.Conv2d(1, 20, kernel_size=5,stride=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Conv2d(20,50,kernel_size=5,stride=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.fc_unit=nn.Sequential(
nn.Linear(4*4*50, 500),
nn.ReLU(),
nn.Linear(500, 10)
) def forward(self, x):
x = self.conv_unit(x)
x = x.view(-1,4*4*50)
x = self.fc_unit(x)
return x

Net1中最后并没有使用log_softmax,是因为直接在train的过程中,使用了nn.CrossEntropyLoss()。此外,Net1和Net不同的地方也就是在结构中使用了nn.Sequential()来单元化卷积层和全连层。

if __name__ == '__main__':
main()

之后就可以使用了!

在命令行中使用:

$ python main.py

就会按照默认的参数训练一个Mnist分类器了。

第三轮的效果:

train epoch: 2, iteration: 1300, loss: 0.010509848594665527
train epoch: 2, iteration: 1400, loss: 0.0020529627799987793
train epoch: 2, iteration: 1500, loss: 0.0027058571577072144
train epoch: 2, iteration: 1600, loss: 0.010049819946289062
train epoch: 2, iteration: 1700, loss: 0.0352507084608078
train epoch: 2, iteration: 1800, loss: 0.009431719779968262
Test loss: 0.01797709318200747, Accuracy: 99.42833333333333%

如果希望查看参数列表,则可以在命令行使用:

$ python main.py -h

就会出现:

usage: main.py [-h] [-a ARCH] [--epochs N] [--momentum M] [-b N] [--lr LR]

PyTorch Mnist Training

optional arguments:
-h, --help show this help message and exit
-a ARCH, --arch ARCH model archtecture: Net|Net1(default:Net)
--epochs N number of total epochs
--momentum M momentum
-b N, --batchsize N mini-batch size
--lr LR, --learning-rate LR
initial learning rata

于是如果想要使用Net1,lr为0.001的方式训练,就可以按照这样:

$ python main.py -a Net1 --lr 0.001

第三轮结果:

train epoch: 2, iteration: 1200, loss: 0.03096039593219757
train epoch: 2, iteration: 1300, loss: 0.060124486684799194
train epoch: 2, iteration: 1400, loss: 0.08865253627300262
train epoch: 2, iteration: 1500, loss: 0.13717596232891083
train epoch: 2, iteration: 1600, loss: 0.003894627094268799
train epoch: 2, iteration: 1700, loss: 0.06881710141897202
train epoch: 2, iteration: 1800, loss: 0.03184908628463745
Test loss: 0.0013615453808257978, Accuracy: 98.69666666666667%

至此,你获得了一个Mnist训练器的训练方法。

如何使用Pytorch迅速实现Mnist数据及分类器的更多相关文章

  1. PyTorch 数据集类 和 数据加载类 的一些尝试

    最近在学习PyTorch,  但是对里面的数据类和数据加载类比较迷糊,可能是封装的太好大部分情况下是不需要有什么自己的操作的,不过偶然遇到一些自己导入的数据时就会遇到一些问题,因此自己对此做了一些小实 ...

  2. 使用Tensorflow操作MNIST数据

    MNIST是一个非常有名的手写体数字识别数据集,在很多资料中,这个数据集都会被用作深度学习的入门样例.而TensorFlow的封装让使用MNIST数据集变得更加方便.MNIST数据集是NIST数据集的 ...

  3. MNIST数据集和IDX文件格式

    MNIST数据集 MNIST数据集是Yan Lecun整理出来的. NIST是美国国家标准与技术研究院(National Institute of Standards and Technology)的 ...

  4. pytorch 加载mnist数据集报错not gzip file

    利用pytorch加载mnist数据集的代码如下 import torchvision import torchvision.transforms as transforms from torch.u ...

  5. 基于MNIST数据的卷积神经网络CNN

    基于tensorflow使用CNN识别MNIST 参数数量:第一个卷积层5x5x1x32=800个参数,第二个卷积层5x5x32x64=51200个参数,第三个全连接层7x7x64x1024=3211 ...

  6. tensorflow学习笔记——使用TensorFlow操作MNIST数据(2)

    tensorflow学习笔记——使用TensorFlow操作MNIST数据(1) 一:神经网络知识点整理 1.1,多层:使用多层权重,例如多层全连接方式 以下定义了三个隐藏层的全连接方式的神经网络样例 ...

  7. tensorflow学习笔记——使用TensorFlow操作MNIST数据(1)

    续集请点击我:tensorflow学习笔记——使用TensorFlow操作MNIST数据(2) 本节开始学习使用tensorflow教程,当然从最简单的MNIST开始.这怎么说呢,就好比编程入门有He ...

  8. GAN生成式对抗网络(三)——mnist数据生成

    通过GAN生成式对抗网络,产生mnist数据 引入包,数据约定等 import numpy as np import matplotlib.pyplot as plt import input_dat ...

  9. EOFError: Compressed file ended before the end-of-stream marker was reached解决办法(在Windows下查看已下载的MNIST数据文件)

    出现这个问题的原因是因为文件下载到一半就中断了,解决办法是删除datasets中下载到一半的数据包. 下面以我遇到的问题为例: 我下载数据下载到最后一个包就没有反应了,于是我强制终止了运行,可能是因为 ...

随机推荐

  1. TPO4-1 Deer Populations of the Puget Sound

    The causes of this population rebound are consequences of other human actions. First, the major pred ...

  2. CountingSort(计数排序)原理及C++代码实现

    计数排序是需要假设输入数据的排序之一,它假设输入元素是0到k区间内的一个整数,其中k为某个整数.当k=O(n)时,计数排序的时间复杂度为θ(n). 因为不是通过比较来排序,所以它的时间复杂度可以达到θ ...

  3. Linux_更改主机名

    老师上linux课截图必须改主机名字,每个人一个代号,所以就写篇这个咯 查看主机名 [root@localhost.localdomain Desktop]# hostname localhost.l ...

  4. JS UTC 昨天

    var birthday = new Date("Jan 01, 1983 01:15:00") var formatDate = function (date) {       ...

  5. 使用这些高效Java工具类享受开发乐趣

    使用这些高效Java工具类享受开发乐趣导语在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.在开发中,使用这些工具类,不仅可以提高编码效率,还可以提高 ...

  6. Linux quota磁盘配额

    quota:磁盘配额 限制某一群组所能使用的最大磁盘配额 限制某一用户的最大磁盘配额 使用限制: 仅能针对整个filesystem 核心必须支持quota quota的记录文件 只对一般身份使用者有效 ...

  7. Android下的鉴权实现方案

    软件原理 不赘述,参考: 软件License认证方案的设计思路 License文件离线鉴权 机械指纹,不可逆的加密算法,如MD5 功能鉴权,可逆的不对称加密算法,服务端公钥加密,app端私钥解密,如R ...

  8. ajax异步的加深理解

    过去印象中的ajax的异步操作,一直还居然在$.ajax函数内部的异步,真是大错特错,实际的异步操作,是针对整个js文件来的. 今天总算意识到了,实际情况如下: $(function(){ //[弹框 ...

  9. python--mysql的CURD操作

    from pymysql import * def main(): # 创建Connextion连接 conn = connect(host='localhost', port=3306, user= ...

  10. ZOJ-1177-K-Magic Number

    就是分别以1到9作为开头构造结果,取最小答案.看了参考书之后才做出来,对参考书上的代码进行了一些改进 Accepted 1177 C++11 0 408 #include "bits/std ...