文章目录

  • 讲解
      • 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_sizeepoch意义详见本人的另一篇博客 神经网络中的 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专门讲过,算是非常基本和重要的概念。

定义网络

下面我们定义一个网络。

网络首先包含两个卷积层conv1conv2

之后接着两个全连接层(Linearfc1fc2得到输出。
需要特别注意: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_channelout_channelin_sizeout_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_size5×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_size3×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表示列自适应。(关于Samplebatchbatch_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)方法将网络移动到CPUGPU。(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,分别用datatarget 获取待处理的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)targetshape变成了和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的手写数字数据集的分类相关推荐

  1. 「MNIST」手写数字数据集下载并转换为图片格式(.png)

    我是 雪天鱼,一名FPGA爱好者,研究方向是FPGA架构探索和数字IC设计. 关注公众号[集成电路设计教程],获取更多学习资料,并拉你进"IC设计交流群". QQIC设计& ...

  2. 将MNIST手写数字数据集导入NumPy数组(《深度学习入门:基于Python的理论与实现》实践笔记)

    将MNIST手写数字数据集导入NumPy数组(<深度学习入门:基于Python的理论与实现>实践笔记) 一.下载MNIST数据集(使用urllib.request.urlretrieve( ...

  3. DL之CNN:自定义SimpleConvNet【3层,im2col优化】利用mnist数据集实现手写数字识别多分类训练来评估模型

    DL之CNN:自定义SimpleConvNet[3层,im2col优化]利用mnist数据集实现手写数字识别多分类训练来评估模型 目录 输出结果 设计思路 核心代码 更多输出 输出结果 设计思路 核心 ...

  4. [PyTorch] 基于python和pytorch的多项式回归

    讲解 须导入和函数库 mport torch import numpy as np from torch.autograd import Variable import torch.nn as nn ...

  5. 基于卷积神经网络(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. ...

  6. 用Python实现BP神经网络识别MNIST手写数字数据集(带GUI)

    概述 计算机神经网络则是人工智能中最为基础的也是较为重要的部分,它使用深度学习的方式模拟了人的神经元的工作,是一种全新的计算方法.本文的目标就是通过学习神经网络的相关知识,了解并掌握BP神经网络的实现 ...

  7. 基于python的手写数字识别knn_KNN分类算法实现手写数字识别

    需求: 利用一个手写数字"先验数据"集,使用knn算法来实现对手写数字的自动识别: 先验数据(训练数据)集: ♦数据维度比较大,样本数比较多. ♦ 数据集包括数字0-9的手写体. ...

  8. matlab 对mnist手写数字数据集进行判决分析_Python神经网络编程:手写数字的数据集MNIST...

    识别人的笔迹这个问题相对复杂,也非常模糊,因此这是一种检验人工智能的理想挑战.这不像进行大量数字相乘那样明确清晰. 让计算机准确区分图像中包含的内容,有时也称之为图像识别问题.科学家对这个问题进行了几 ...

  9. 动手学PaddlePaddle(4):MNIST(手写数字识别)

    本次练习将使用 PaddlePaddle 来实现三种不同的分类器,用于识别手写数字.三种分类器所实现的模型分别为 Softmax 回归.多层感知器.卷积神经网络. 您将学会 实现一个基于Softmax ...

最新文章

  1. servlet-------------jsp 地址栏变化
  2. 陈灯可重用代码段管理器(插件版最新版本:3.2;桌面版最新版本:2.3)
  3. 重装系统后软件安装 ----一直更新
  4. 2017-10-17 开源非英文关键词编程语言
  5. 【pwnable.tw】 death_note
  6. 7-Zip CommondLine 使用记录
  7. 改善代码可读性的5种方法
  8. 域名抢注php程序_“丁真”被抢注,蹭热点这门生意,能成就好品牌?
  9. 播客“日谈公园”完成数百万天使轮融资,来自头头是道基金
  10. gittrack_Git 追踪分支
  11. Qt容器类之三:通用算法
  12. 拼写检查器——朴素贝叶斯应用
  13. 【Python】爬取xici和快代理的免费代理ip
  14. C语言中 abs、cabs、labs和fbs的区别
  15. 多个安卓设备投屏到电脑_安卓手机怎么投屏到电脑上?这样做,在电脑上就能操控手机...
  16. 短按SOS键实现开始与停止白光LED灯闪烁状态
  17. OCSP 在SSL证书中起什么作用
  18. “筑巢引凤”亦“固巢养凤”:上海科创办领导与人才代表彭垚云端共话人才引育
  19. 树莓派中文输入法安装
  20. 高德地图交通态势爬取

热门文章

  1. 没个百来万就想自建技术团队?亲身经历告诉你,一个APP从无到有的开发到底要花多少钱!...
  2. C++发送HTTP请求获取网页HTML代码
  3. linux服务器最大连接数
  4. 设置按钮背景为透明去掉button按钮左右两边的留白
  5. 2021牛客暑期多校训练营1, 签到题DFBG
  6. AcWing基础算法课Level-2 第五讲 动态规划
  7. 样条线怎么挤出平面_最速降线的故事
  8. 存用部首查字典如何查_文献阅读技巧:牛人博士如何看文献!
  9. JavaScript文档对象模型概述(1)
  10. SQL Server 更新数据表记录