[PyTorch] 基于Python和PyTorch的MNIST的手写数字数据集的分类
文章目录
- 讲解
- MNIST的介绍
- 须导入的函数库
- 检查 pytorch 的版本
- 定义超参数
- 下载 MNIST的数据集
- 定义网络
- 网络实例化
- 定义训练函数
- 定义测试函数
- 主函数
- 全部源代码
2020.07.24更新:关于网络层的更详细的信息,可以参考“评论”,写的比我要详细一点。
讲解
MNIST的介绍
好比编程入门有 Hello World,机器学习入门有 MNIST 。
MNIST的官方网址:THE MNIST DATABASE of handwritten digits
而本博客中MNIST的介绍部分的图片来自 MNIST介绍
简单来说,MNIST是一个入门级的计算机视觉数据集,它包含各种手写数字图片。
其中训练集包括60000张图片,测试集包括10000张图片。
训练集和测试集的划分非常重要。在机器学习模型设计时必须有一个单独的测试集(不用于训练而是用来评估这个模型的性能),才能更容易把模型推广到其他数据集上(泛化)。
MNIST的每一张图片包含 28X28
个像素点。我们可以用一个数组来表示这张图片:
我们把这个数组展开成一个向量,长度是 28x28 = 784
。如何展开这个数组(数字间的顺序)不重要,只要保持各个图片采用相同的方式展开。从这个角度来看,MNIST数据集的图片就是在784
维向量空间里面的点, 并且拥有比较部分。
须导入的函数库
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
检查 pytorch 的版本
print(torch.__version__)
定义超参数
BATCH_SIZE=512 # batch_size即每批训练的样本数量
EPOCHS=20 # 循环次数# 让torch判断是否使用GPU,即device定义为CUDA或CPU
DEVICE=torch.device("cuda" if torch.cuda.is_available() else "cpu")
batch_size
和 epoch
的意义详见本人的另一篇博客 神经网络中的 batch 和 epoch,有很详细的描述。所以在这里就不讲了。
设定超参数DEVICE的意义:
在网络实例化的时候,用.to(DEVICE)
将实例化的网络放到相应的CPU或GPU上。
下载 MNIST的数据集
# 训练集
train_loader = torch.utils.data.DataLoader( # vision.utils : 用于把形似 (3 x H x W) 的张量保存到硬盘中,给一个mini-batch的图像可以产生一个图像格网。datasets.MNIST('data', train=True, download=True,transform=transforms.Compose([transforms.ToTensor(), # 图像转化为Tensortransforms.Normalize((0.1307,), (0.3081,)) # 标准化])),batch_size=BATCH_SIZE, shuffle=True) # shuffle() 方法将序列的所有元素随机排序# 测试集
test_loader = torch.utils.data.DataLoader(datasets.MNIST('data', train=False, transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))])),batch_size=BATCH_SIZE, shuffle=True) # shuffle() 方法将序列的所有元素随机排序
关于batch_size
,在本人的另一篇博客 神经网络中的 batch 和 epoch专门讲过,算是非常基本和重要的概念。
定义网络
下面我们定义一个网络。
网络首先包含两个卷积层conv1
和conv2
。
之后接着两个全连接层(Linear
)fc1
和fc2
得到输出。
需要特别注意:Linear
需要接收的是展平后的多维的卷积成的特征图(借用view()
函数) 。
最后输出10个维度,作为0-9的标识来确定是哪个数字
建议:
最好将每一层的输入和输出维度都作为注释标注出来,这样后面阅读代码的会方便很多。
class ConvNet(nn.Module):def __init__(self):super().__init__()# 128x28self.conv1=nn.Conv2d(1,10,5) # 10, 24x24self.conv2=nn.Conv2d(10, 20,3) #128, 10x10self.fc1=nn.Linear(20*10*10, 500)self.fc2=nn.Linear(500, 10)def forward(self, x):in_size=x.size(0) # in_size 为 batch_size(一个batch中的Sample数)# 卷积层 -> relu -> 最大池化out = self.conv1(x) # 24out = F.relu(out)out = F.max_pool2d(out, 2, 2) # 12#卷积层 -> relu -> 多行变一行 -> 全连接层 -> relu -> 全连接层 -> sigmoidout = self.conv2(out) # 10out = F.relu(out)out = out.view(in_size, -1) # view()函数作用是将一个多行的Tensor,拼接成一行。out = self.fc1(out)out = F.relu(out)out = self.fc2(out)# softmaxout = F.log_softmax(out, dim=1)# 返回值 outreturn out
卷积层函数nn.Conv2d()
的参数意义,详见本人的另一篇博客 卷积函数 and 解卷积函数,里面对in_channel
、out_channel
和in_size
、out_size
有详细的讲解。
然后我们按整个流程来走一遍前向传播
网络结构 | 函数 |
---|---|
卷积层 | self.conv1() |
激活函数 | F.relu() |
最大池化层 | F.max_pool2d() |
卷积层 | self.conv2() |
激活函数 | F.relu() |
全连接层预处理 | out = out.view(in_size, -1) |
线性层 | self.fc1() |
激活函数 | F.relu() |
线性层 | self.fc2() |
变为概率 | out = F.log_softmax(out, dim=1) |
特征提取开始
第一个卷积层self.conv1=nn.Conv2d(1,10,5)
:
其参数意义为:
- 输入通道为 1 (输入图像是灰度图)
- 输出通道为 10(10分类问题,所以需要用到的卷积核就有 10 种)
- 卷积核
kernel_size
为 5×5
24输出维度 = 28输入维度 - 5卷积核size + 1
所以输出 shape 为:10 × 24 × 24
第一个激活函数out = F.relu(out)
输出维度不变仍为 10 × 24 × 24
第一个最大池化层out = F.max_pool2d(out, 2, 2)
该最大池化层在 2x2 空间里向下采样。
12输出维度 = 24输入维度 / 2。
所以输出 shape 为:10 × 12 × 12
第二个卷积层self.conv2=nn.Conv2d(10, 20,3)
其参数意义为:
- 输入通道为 10 (第一个最大池化层的输出通道数)
- 输出通道为 20 (需要用到的卷积核就有 20 种)
- 卷积核
kernel_size
为 3×3
10输出维度 = 12输入维度 - 3卷积核size + 1
所以输出 shape 为:20 × 10 × 10
第二个激活函数out = F.relu(out)
特征提取结束
输出前的数据预处理
因为全连接层Linear
的输出为最后的输出,而全连接层Linear
要求的输入为展平后的多维的卷积成的特征图(特征图为特征提取部分的结果)(这一点在介绍网络结构的时候专门拿黄色强调了)
out = out.view(in_size, -1)
将 in_size
(即batch_size
)个Sample
拉成一维。-1
表示列自适应。(关于Sample
、batch
、batch_size
的讲解见本人的另一篇博客 神经网络中的 batch 和 epoch)。
输出前的数据预处理结束
输出即全连接层
第一个全连接层self.fc1=nn.Linear(20*10*10, 500)
输入维度为 20 * 10 * 10= 2000
设定的输出维度为 500 × 1
第三个激活函数out = F.relu(out)
输出维度不变,仍为 500 × 1
第二个全连接层self.fc2=nn.Linear(500, 10)
输入维度为 500 × 1
输出维度设定为 10 × 1(因为是一个10分类的问题,所以最后要变成 10 × 1)
第三个激活函数out = F.log_softmax(out, dim=1)
用F.log_softmax()
将数据的范围改到[0, 1]之内,表示概率。
输出维度仍为 10 × 1,其值可以视为概率。
return(out)
输出即全连接层结束
网络实例化
我们网络将实例化,加入输入输出接口,实例化后使用.to(DEVICE)
方法将网络移动到CPU
或GPU
。(DEVICE
见超参数设定)
model = ConvNet().to(DEVICE)
定义训练函数
将训练的所有操作都封装到train
函数中
在训练模型时直接调用train
函数即可。
train
函数需要传入的参数有;
model
——实例化的网络device
——决定tensor
运算的位置train_loader
——训练集数据optimizer
——优化器epoch
——训练次数
第二行的model.train()
将模型转为训练模式,因为一些模块在train和evaluation表现不同(比如Dropout)
# 定义 训练函数 ,将训练的所有操作都封装到train函数中
def train(model, device, train_loader, optimizer, epoch):model.train()for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device) # CPU转GPUoptimizer.zero_grad() # 优化器清零output = model(data) # 由model,计算输出值loss = F.nll_loss(output, target) # 计算损失函数lossloss.backward() # loss反向传播optimizer.step() # 优化器优化if(batch_idx+1)%30 == 0: # 输出结果print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(epoch, batch_idx * len(data), len(train_loader.dataset),100. * batch_idx / len(train_loader), loss.item()))
# -------------------------------------------------------------
enumerate()
函数的讲解,可见本人的另一篇博客 索引序列函数:enumerate() / enumerate(sequence, start=0)。
简单来说这是个enumerate()
用于for
循环的例子。
enumerate(train_loader)
将train_loader
组合成了一个索引序列,为 数据 增加了 数据下标。
在 for batch_idx, (data, target) in enumerate(train_loader):
这一行中,
我们通过batch_idx
来获取数据下标,用data
获得待处理的数据(由手写数字的image
转成的,被标准化的tensor
,详见训练数据集下载的part),用target
获取实际值。最后由for
循环遍历整个train_loader
。
定义测试函数
将测试的所有操作都封装到test
函数中
在测试模型时直接调用test
函数即可。
test
函数需要传入的参数有;
model
——实例化的网络device
——决定tensor
运算的位置test_loader
——训练集数据
# ---------------------测试函数------------------------------
# 测试的操作也一样封装成一个函数
def test(model, device, test_loader):test_loss = 0 # 损失函数初始化为0correct = 0 # correct 计数分类正确的数目with torch.no_grad(): # 表示不反向求导(反向求导为训练过程)for data, target in test_loader: # 遍历所有的data和targetdata, target = data.to(device), target.to(device) # CPU -> GPUoutput = model(data) # output为预测值,由model计算出test_loss += F.nll_loss(output, target, reduction='sum').item() ### 将一批的损失相加pred = output.max(1, keepdim=True)[1] ### 找到概率最大的下标correct += pred.eq(target.view_as(pred)).sum().item()test_loss /= len(test_loader.dataset) # 总损失除数据集总数print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(test_loss, correct, len(test_loader.dataset),100. * correct / len(test_loader.dataset)))
# ---------------------------------------------------------------
with torch.no_grad()
表示不反向求导。训练过程需要反向求导(去更新优化模型参数),测试过程不需要反向求导。
for data, target in test_loader:
为遍历整个test_loader
,分别用data
,target
获取待处理的tensor
和实际值。
correct += pred.eq(target.view_as(pred)).sum().item()
中的.view_as(pred)
是改变shape
的函数(详细见本人的另一篇博客 更改矩阵形状——.reshape(m,n)和.view(m,n)和view_as(tensor))。
在这里target.view_as(pred)
将target
的shape
变成了和pred
一样(shape
统一了才能用.eq()
比较)
主函数
进行网络的训练和测试。
# 下面开始训练,这里就体现出封装起来的好处了,只要写两行就可以了
# 整个数据集只过一遍
for epoch in range(1, EPOCHS + 1):train(model, DEVICE, train_loader, optimizer, epoch)test(model, DEVICE, test_loader)
range(1, EPOCHS + 1)
表示执行EPOCHS
次。
全部源代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
# torchvision是独立于pytorch的关于图像操作的一些方便工具库。
# vision.datasets : 几个常用视觉数据集,可以下载和加载
# vision.models : 流行的模型,例如 AlexNet, VGG, ResNet 和 Densenet 以及训练好的参数。
# vision.transforms : 常用的图像操作,例如:数据类型转换,图像到tensor ,numpy 数组到tensor , tensor 到 图像等。
# vision.utils : 用于把形似 (3 x H x W) 的张量保存到硬盘中,给一个mini-batch的图像可以产生一个图像格网print(torch.__version__) # 检查 pytorch 的版本# 定义一些超参数
BATCH_SIZE=512 # batch_size即每批训练的样本数量
EPOCHS=20 # 循环次数
DEVICE=torch.device("cuda" if torch.cuda.is_available() else "cpu") # 让torch判断是否使用GPU,即device定义为CUDA或CPU# 下载 MNIST的数据集
# 训练集
train_loader = torch.utils.data.DataLoader( # vision.utils : 用于把形似 (3 x H x W) 的张量保存到硬盘中,给一个mini-batch的图像可以产生一个图像格网。datasets.MNIST('data', train=True, download=True,transform=transforms.Compose([transforms.ToTensor(), # 图像转化为Tensortransforms.Normalize((0.1307,), (0.3081,)) # 标准化(参数不明)])),batch_size=BATCH_SIZE, shuffle=True) # shuffle() 方法将序列的所有元素随机排序# 测试集
test_loader = torch.utils.data.DataLoader(datasets.MNIST('data', train=False, transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))])),batch_size=BATCH_SIZE, shuffle=True) # shuffle() 方法将序列的所有元素随机排序# 下面我们定义一个网络,网络包含两个卷积层,conv1和conv2,
# 然后紧接着两个线性层作为输出,
# 最后输出10个维度,这10个维度我们作为0-9的标识来确定识别出的是那个数字# 这里建议大家将每一层的输入和输出维度都作为注释标注出来,这样后面阅读代码的会方便很多
class ConvNet(nn.Module):def __init__(self):super().__init__()# 128x28self.conv1=nn.Conv2d(1,10,5) # 10, 24x24self.conv2=nn.Conv2d(10, 20,3) #128, 10x10self.fc1=nn.Linear(20*10*10, 500)self.fc2=nn.Linear(500, 10)def forward(self, x):in_size=x.size(0) # in_size 为 batch_size(一个batch中的Sample数)# 卷积层 -> relu -> 最大池化out = self.conv1(x) # 24out = F.relu(out)out = F.max_pool2d(out, 2, 2) # 12#卷积层 -> relu -> 多行变一行 -> 全连接层 -> relu -> 全连接层 -> sigmoidout = self.conv2(out) # 10out = F.relu(out)out = out.view(in_size, -1) # view()函数作用是将一个多行的Tensor,拼接成一行。out = self.fc1(out)out = F.relu(out)out = self.fc2(out)# softmaxout = F.log_softmax(out, dim=1)# 返回值 outreturn out# 我们实例化一个网络,实例化后使用“.to”方法将网络移动到GPU
model = ConvNet().to(DEVICE)# 优化器我们也直接选择简单暴力的Adam
optimizer = optim.Adam(model.parameters())# 定义 训练函数 ,我们将训练的所有操作都封装到train函数中
def train(model, device, train_loader, optimizer, epoch):model.train()for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device) # CPU转GPUoptimizer.zero_grad() # 优化器清零output = model(data) # 由model,计算输出值loss = F.nll_loss(output, target) # 计算损失函数lossloss.backward() # loss反向传播optimizer.step() # 优化器优化if(batch_idx+1)%30 == 0: # 输出结果print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(epoch, batch_idx * len(data), len(train_loader.dataset),100. * batch_idx / len(train_loader), loss.item()))
# -------------------------------------------------------------# ---------------------测试函数------------------------------
# 测试的操作也一样封装成一个函数
def test(model, device, test_loader):test_loss = 0 # 损失函数初始化为0correct = 0 # correct 计数分类正确的数目with torch.no_grad(): # 表示不反向求导(反向求导为训练过程)for data, target in test_loader: # 遍历所有的data和targetdata, target = data.to(device), target.to(device) # CPU -> GPUoutput = model(data) # output为预测值,由model计算出test_loss += F.nll_loss(output, target, reduction='sum').item() ### 将一批的损失相加pred = output.max(1, keepdim=True)[1] ### 找到概率最大的下标correct += pred.eq(target.view_as(pred)).sum().item()test_loss /= len(test_loader.dataset) # 总损失除数据集总数print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(test_loss, correct, len(test_loader.dataset),100. * correct / len(test_loader.dataset)))
# ---------------------------------------------------------------# 下面开始训练,这里就体现出封装起来的好处了,只要写两行就可以了
# 整个数据集只过一遍
for epoch in range(1, EPOCHS + 1):train(model, DEVICE, train_loader, optimizer, epoch)test(model, DEVICE, test_loader)
[PyTorch] 基于Python和PyTorch的MNIST的手写数字数据集的分类相关推荐
- 「MNIST」手写数字数据集下载并转换为图片格式(.png)
我是 雪天鱼,一名FPGA爱好者,研究方向是FPGA架构探索和数字IC设计. 关注公众号[集成电路设计教程],获取更多学习资料,并拉你进"IC设计交流群". QQIC设计& ...
- 将MNIST手写数字数据集导入NumPy数组(《深度学习入门:基于Python的理论与实现》实践笔记)
将MNIST手写数字数据集导入NumPy数组(<深度学习入门:基于Python的理论与实现>实践笔记) 一.下载MNIST数据集(使用urllib.request.urlretrieve( ...
- DL之CNN:自定义SimpleConvNet【3层,im2col优化】利用mnist数据集实现手写数字识别多分类训练来评估模型
DL之CNN:自定义SimpleConvNet[3层,im2col优化]利用mnist数据集实现手写数字识别多分类训练来评估模型 目录 输出结果 设计思路 核心代码 更多输出 输出结果 设计思路 核心 ...
- [PyTorch] 基于python和pytorch的多项式回归
讲解 须导入和函数库 mport torch import numpy as np from torch.autograd import Variable import torch.nn as nn ...
- 基于卷积神经网络(cnn)的手写数字识别(PyTorch)
目录 1.1 卷积神经网络简介 1.2 神经网络 1.2.1 神经元模型 1.2.2 神经网络模型 1.3 卷积神经网络 1.3.1卷积的概念 1.3.2 卷积的计算过程 1.3.3 感受野 1.3. ...
- 用Python实现BP神经网络识别MNIST手写数字数据集(带GUI)
概述 计算机神经网络则是人工智能中最为基础的也是较为重要的部分,它使用深度学习的方式模拟了人的神经元的工作,是一种全新的计算方法.本文的目标就是通过学习神经网络的相关知识,了解并掌握BP神经网络的实现 ...
- 基于python的手写数字识别knn_KNN分类算法实现手写数字识别
需求: 利用一个手写数字"先验数据"集,使用knn算法来实现对手写数字的自动识别: 先验数据(训练数据)集: ♦数据维度比较大,样本数比较多. ♦ 数据集包括数字0-9的手写体. ...
- matlab 对mnist手写数字数据集进行判决分析_Python神经网络编程:手写数字的数据集MNIST...
识别人的笔迹这个问题相对复杂,也非常模糊,因此这是一种检验人工智能的理想挑战.这不像进行大量数字相乘那样明确清晰. 让计算机准确区分图像中包含的内容,有时也称之为图像识别问题.科学家对这个问题进行了几 ...
- 动手学PaddlePaddle(4):MNIST(手写数字识别)
本次练习将使用 PaddlePaddle 来实现三种不同的分类器,用于识别手写数字.三种分类器所实现的模型分别为 Softmax 回归.多层感知器.卷积神经网络. 您将学会 实现一个基于Softmax ...
最新文章
- servlet-------------jsp 地址栏变化
- 陈灯可重用代码段管理器(插件版最新版本:3.2;桌面版最新版本:2.3)
- 重装系统后软件安装 ----一直更新
- 2017-10-17 开源非英文关键词编程语言
- 【pwnable.tw】 death_note
- 7-Zip CommondLine 使用记录
- 改善代码可读性的5种方法
- 域名抢注php程序_“丁真”被抢注,蹭热点这门生意,能成就好品牌?
- 播客“日谈公园”完成数百万天使轮融资,来自头头是道基金
- gittrack_Git 追踪分支
- Qt容器类之三:通用算法
- 拼写检查器——朴素贝叶斯应用
- 【Python】爬取xici和快代理的免费代理ip
- C语言中 abs、cabs、labs和fbs的区别
- 多个安卓设备投屏到电脑_安卓手机怎么投屏到电脑上?这样做,在电脑上就能操控手机...
- 短按SOS键实现开始与停止白光LED灯闪烁状态
- OCSP 在SSL证书中起什么作用
- “筑巢引凤”亦“固巢养凤”:上海科创办领导与人才代表彭垚云端共话人才引育
- 树莓派中文输入法安装
- 高德地图交通态势爬取
热门文章
- 没个百来万就想自建技术团队?亲身经历告诉你,一个APP从无到有的开发到底要花多少钱!...
- C++发送HTTP请求获取网页HTML代码
- linux服务器最大连接数
- 设置按钮背景为透明去掉button按钮左右两边的留白
- 2021牛客暑期多校训练营1, 签到题DFBG
- AcWing基础算法课Level-2 第五讲 动态规划
- 样条线怎么挤出平面_最速降线的故事
- 存用部首查字典如何查_文献阅读技巧:牛人博士如何看文献!
- JavaScript文档对象模型概述(1)
- SQL Server 更新数据表记录