背景

LeNET-5是最早的卷积神经网络之一,曾广泛用于美国银行。手写数字识别正确率在99%以上。

PyTorch是Facebook 人工智能研究院在2017年1月,基于Torch退出的一个Python深度学习的库。他是一个基于Python的可续计算包,提供两个高级功能:

1、具有强大的GPU加速的张量运算(如NumPy)。

2、包含自动求导系统的深度神经网络。

LeNet 5网络介绍

LeNet 5的网络结构图如下

虽然被称为LeNet 5,但这个网络不包括输入的话,一共有7层,1卷积层、2池化层、3卷积层、4池化层、5全连接层、6全连接层、7分类层(之后接输出)。

虽然LeNet 5 网络不大,但是包含了深度学习的基本模块:卷积层、池化层和全连接层,每一层都包含可训练参数,每个层有多个特征图,每个特征图 是由一个卷积滤波器提取出来的一种特征,然后每个特征图有多个神经元。

输入:LeNet 5网络因为有全连接层(全连接层就是)的存在,所以输入是固定的,是28*28的二维图片。

关于全连接层的知识,可以参考这篇博客

输出:输出的为分类结果,0-9之间的一个数,这个是由最后的softmax输出的。

关于softmax的具体知识,可以参考这篇文章。

原始的LeNet5

体验

不像网上的那些博客那样,上来就堆一大堆代码,我觉得一开始的体验是很有必要的,你直接下载我的这个代码,就可以无痛体验手写数字的分类结果。

先体验这个,运行Test.py即可。

会在窗口中显示下面的界面。

红框中的这个数字就是对手写数字的识别率了,96.24%,不是特别高,这是因为我只训练了10个epoch的缘故,你要是训练100个epoch的话,应该可以达到99%以上的epoch。

体验完这个Test.py之后,有没有感觉,只有一个黑框和数字的话,看起来很不爽,那么可以试一下下面这个Test_vis.py,直接以可视化的形式实时显示你的识别结果。(visdom或者plot的形式来展现这个结果)

LeNet 5训练的代码

import torch import torch.nn as nn # 用nn来创建神经网络的各个层、损失函数和激活函数。 import torch.optim as optime # 导入PyTorch的优化器包 from torchvision import datasets, transforms # datasets是 常见视觉数据集的数据加载器;# transforms可以进行常见的图像变换,如随机裁剪、旋转等 from torch.autograd import Variable # 自动求导数用 from torch.utils.data import DataLoader # 读取数据用的,用来将自定义的数据或者其他数据封装成Tensor。 class LeNet(nn.Module): def __init__(self): super(LeNet, self).__init__() self.conv1 = nn.Sequential( nn.Conv2d(1, 6, 3, 1, 2), nn.ReLU(), nn.MaxPool2d(2, 2) ) self.conv2 = nn.Sequential( nn.Conv2d(6, 16, 5), nn.ReLU(), nn.MaxPool2d(2, 2) ) self.fc1 = nn.Sequential( nn.Linear(16 * 5 * 5, 120), # 输出的维度是120 nn.BatchNorm1d(120), # 那么这一层对应的输入就是120了 nn.ReLU() ) self.fc2 = nn.Sequential( nn.Linear(120, 84), nn.BatchNorm1d(84), # 批标准化,可以加快神经网络的收敛速度(注:批标准化一般放在全连接层后面,激活函数层的前面) nn.ReLU() ) self.fc3 = nn.Linear(84, 10) def forward(self, x): x1 = self.conv1(x) x2 = self.conv2(x1) x3 = x2.view(x2.size()[0], -1) # 调整张量的维度 x4 = self.fc1(x3) x5 = self.fc2(x4) x6 = self.fc3(x5) return x6 def train_net(): global device, batch_size, LR, Momentum, train_dataset, test_dataset, train_loader, test_loader global net, criterion, optimizer, epochs for epoch in range(epochs): # 走一个循环 sum_loss = 0.0 # 损失函数一开始的时候设为0 print(epoch) for i, data in enumerate(train_loader): # 通过enumerate的迭代,从训练集中取数据,i为索引,data为train_loader中的数据 # train_loader的长度为928,那么938*64=60032,正好是训练集数据的大小 # 这个循环一共会进行928次,到第900之后,就不会有数据打印出来了 inputs, labels = data # input为输入的数据,label为标签 inputs = inputs.cuda() labels = labels.cuda() inputs, labels = Variable(inputs), Variable(labels) # 将Tensor数据转换为Variable类型 optimizer.zero_grad() # 将梯度归零 outputs = net(inputs) # 将数据传入网络进行前向运算 loss = criterion(outputs, labels) # criterion是用torch.nn创建的交叉熵函数, # 利用输出的预测值和标签之间的差值,得到损失函数的计算值 # vis.line(Y=torch.FloatTensor([loss]), win='loss',update='append' if i > 0 else None) loss.backward() # loss反向传播 optimizer.step() # 使用优化器,对模型权重进行更新 sum_loss = sum_loss + loss.item() # 将这次bitch的误差进行累计 if i % 100 == 0: # 每100次输出一下当前的loss print('[{}-{} loss:{}]'.format(epoch + 1, i + 1, sum_loss / 100)) sum_loss = 0 torch.save(net, 'net-10 epochs.pth') return net def save_net(net, net_filename): torch.save(net, net_filename) def init(): global device, batch_size, LR, Momentum, train_dataset, train_loader global net, criterion, optimizer, epochs device = torch.device('cuda') # device设为用CPU进行训练 batch_size = 10000 # 每一次训练所取的样本数是64个 LR = 0.001 # 学习率 Momentum = 0.9 # 动量是0.9 net = LeNet().to(device) # 实例化一个LeNet网络,同时设定设备是CPU还是GPU criterion = nn.CrossEntropyLoss() # 定义损失函数,这个是用的torch.nn方法建立的交叉熵函数 optimizer = optime.SGD(net.parameters(), lr=LR, momentum=Momentum) # 用的是SGD随机梯度下降算法, # 里面定义好网络的参数,学习率以及动量的大小 # 将网络的各种配置参数存入到优化器中,以便之后更新网络用 epochs = 10 # 使用训练集的全部数据对模型进行一次完整训练,被称之为一代训练 train_dataset = datasets.MNIST(root='./data', # 载入训练的数据集 train=True, # 设定这个是训练用的 transform=transforms.ToTensor(), # 将载入的数据集转变为tensor download=True) # 询问是否要进行数据集的下载,若是下载好了的话,就不用再下载了 # 建立一个数据迭代器,将数据按照batch_size的设置分成若干个小块儿 train_loader = torch.utils.data.DataLoader(dataset=train_dataset, # 将读取的数据转变为TenSor batch_size=batch_size, # 将Tensor按照之前的batch_size进行分类 shuffle=True) def main(): init() train_net() main()

训练代码如上所示,我也是新手,我们就一步一步来看。

代码主要分为导入包、参数初始化、定义LeNet 5的网络、网络训练以及保存模型5部分。

1.导入包

这几个包的用途,我都在后面详细做了详细注释。

import torch import torch.nn as nn # 用nn来创建神经网络的各个层、损失函数和激活函数。 import torch.optim as optime # 导入PyTorch的优化器包,可以从这个包中选择预先定义的SGD等优化函数 from torchvision import datasets, transforms # datasets是 常见视觉数据集的数据加载器;# transforms可以进行常见的图像变换,如随机裁剪、旋转等 from torch.autograd import Variable # 自动求导数用 from torch.utils.data import DataLoader # 读取数据用的,用来将自定义的数据或者其他数据封装成Tensor。

2.参数初始化

这里,我将参数初始化来写到这个init()函数中了,初始化的,主要有

batch_size,就是网络每一次运行的时候,加载多少数据,你可以类比,batch size就是公交汽车拉人的数量,一次拉的人的数量越多,肯定效率越高,但是这个又不能超过汽车的运载能力。因为这里跑的是MNIST数据集,里面的图片比较小,所以我设的比较大,你可以根据自己电脑的配置来选择自己的batch size。

LR,学习率。想象一个场景,你在指挥一个盲人往前去一个目标,你每次都只能告诉他应该往前还是往后,这个学习率对应的就是盲人每次移动的步伐。步伐太大,可能会错过目的地点,步伐太小,又会走得太慢。因此正确的做法是,在一开始距离目标比较远的时候,将这个步伐设的大一点儿,等距离目标比较近的时候,将这个步伐设的小一点儿。实际上,在深度学习中,学习率也是这样设定的。

Momentum,动量值,这个值是用来设定SGD的重力加速度的。

epoch,训练次数。

train_dataset ,是下载或者读取本地的MNIST数据集。因为后面download的参数设置的为True,因此这个函数实现的功能就是:若是本地已经有这个MNIST数据集的话,那么就读取本地的数据集;若是本地没有这个MNIST的数据集的话,那么就先下载这个数据集再进行读取。

train_loader,这个是在读取的MNIST数据集train_dataset的基础上,建立一个数据迭代器,将数据按照batch_size的大小,分成若干个小块儿,MNIST的训练集的大小为60000,若是batch_size的大小为100的话,那么这个train_loader就会被分割为600个小块儿;若是batch size的大小为1000的话,那么这个train_loader就会被分割为60个小块儿。

criterion,定义损失函数的类型,这里使用的是torch.nn自带的交叉熵损失函数CrossEntropyLoss()

def init(): global device, batch_size, LR, Momentum, train_dataset, train_loader global net, criterion, optimizer, epochs device = torch.device('cuda') # device设为用CPU进行训练 batch_size = 10000 # 每一次训练所取的样本数是64个 LR = 0.001 # 学习率 Momentum = 0.9 # 动量是0.9 net = LeNet().to(device) # 实例化一个LeNet网络,同时设定设备是CPU还是GPU criterion = nn.CrossEntropyLoss() # 定义损失函数,这个是用的torch.nn方法建立的交叉熵函数 optimizer = optime.SGD(net.parameters(), lr=LR, momentum=Momentum) # 用的是SGD随机梯度下降算法, # 里面定义好网络的参数,学习率以及动量的大小 # 将网络的各种配置参数存入到优化器中,以便之后更新网络用 epochs = 10 # 使用训练集的全部数据对模型进行一次完整训练,被称之为一代训练 train_dataset = datasets.MNIST(root='./data', # 载入训练的数据集 train=True, # 设定这个是训练用的 transform=transforms.ToTensor(), # 将载入的数据集转变为tensor download=True) # 询问是否要进行数据集的下载,若是下载好了的话,就不用再下载了 # 建立一个数据迭代器,将数据按照batch_size的设置分成若干个小块儿 train_loader = torch.utils.data.DataLoader(dataset=train_dataset, # 将读取的数据转变为TenSor batch_size=batch_size, # 将Tensor按照之前的batch_size进行分类 shuffle=True)

3.定义网络

class LeNet(nn.Module): def __init__(self): super(LeNet, self).__init__() self.conv1 = nn.Sequential( nn.Conv2d(1, 6, 3, 1, 2), nn.ReLU(), nn.MaxPool2d(2, 2) ) self.conv2 = nn.Sequential( nn.Conv2d(6, 16, 5), nn.ReLU(), nn.MaxPool2d(2, 2) ) self.fc1 = nn.Sequential( nn.Linear(16 * 5 * 5, 120), # 输出的维度是120 nn.BatchNorm1d(120), # 那么这一层对应的输入就是120了 nn.ReLU() ) self.fc2 = nn.Sequential( nn.Linear(120, 84), nn.BatchNorm1d(84), # 批标准化,可以加快神经网络的收敛速度(注:批标准化一般放在全连接层后面,激活函数层的前面) nn.ReLU() ) self.fc3 = nn.Linear(84, 10) def forward(self, x): x1 = self.conv1(x) x2 = self.conv2(x1) x3 = x2.view(x2.size()[0], -1) # 调整张量的维度 x4 = self.fc1(x3) x5 = self.fc2(x4) x6 = self.fc3(x5) return x6

这个LeNet5代码的话,网络参数分别为(注意这里的层数是说的网络实际上的小层数,卷积和池化都算作是分别的层,而不是在定义的时候组合出的大层):

第1层:卷积层,nn.Conv2d(1, 6, 3, 1, 2),表示输入的为1通道,输出的为6通道,卷积核的大小为3,卷积步长为1,padding为2,原始输入为,28 * 28,卷积过后的计算公式为,(n + 2p - f)/s + 1,其中n为输入图像的大小,p为padding,f为卷积的大小,s为步长,那么这一层之后的特征图大小为(28+ 2*2 - 3)/1+1 = 30,其实是有6个卷积核,那么这一层之后输出最终为,30 * 30* 6。

第2层:池化层,nn.MaxPool2d(2, 2),表示池化的卷积核大小为2,窗口移动的步长为2,没有padding。还是套那个卷积的公式(n + 2p - f)/s + 1,(30+ 2 *0 - 2) / 2 +1 = 15,相当于将上一层输送过来的特征图隔两个取一个点,最终输出的为 15 * 15 * 6.

第3层:卷积层,nn.Conv2d(6, 16, 5),输入为6通道,输出为16通道,卷积核的大小为5,步长s为1,padding为0,输入为 15 * 15,那么输出为(15 + 2*0 - 5)/1 +1 = 11,特征图为 16 * 11 * 11

第4层:池化层,n.MaxPool2d(2, 2),输入为11 * 11,经过池化层之后,会得到小数5.5,一般这种情况,就是取整了,不过是向下取整,也就是5,因此这一层之后最终输出的特征图为 16 * 5 * 5

第5层:全连接层,也叫做线性层,Linear(16 * 5 * 5, 120),输入为 16 * 5 * 5 = 400个神经元(之前上一层输出的特征图上面的每一个点都称为一个神经元),输出为120个神经元。

第6层:全连接层,nn.Linear(120, 84),输入为 120 个神经元,输出为84个神经元。

第7层:全连接层,nn.Linear(84, 10),输入为84个神经元,输出为10个神经元,对应的就是那10个手写数字了。

x3 = x2.view(x2.size()[0], -1) # 调整张量的维度,将其来铺成线性单维度的向量,

2为batch size的大小,400则为特征图铺成一个向量的神经元的数量。

最后的这三层全连接层可视化之后就是下面这种效果。黑色区域不是真的黑色区域,而是因为这些个线太密了,所以看上去才像是黑色。

这个是搭建网络过程中的各个子函数的具体对应及其参数

torch.nn.Conv2d

普通卷积

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)

in_channels:输入通道数

out_channels:输出通道数(卷积核的个数,有几个卷积核就会输出几个通道的维度)

kernel_size:卷积核的尺寸

stride:卷积步长(默认值为1)

padding:输入在每一条边补充0的层数(默认不补充)

dilation:卷积核元素之间的间距(默认值为1)

groups:从输入通道到输出通道的阻塞连接数(默认值是1)

bias:是否添加偏置(默认是True)

torch.nn.MaxPool2d

最大池化

torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

kernel_size:池化的窗口大小

stride:窗口移动的步长,默认值是kernel_size

padding:输入的每一条边补充0的层数

dilation:一个控制窗口中元素步幅的参数

return_indices:如果等于True,会返回输出最大值的序号,对于上采样操作会有帮助

ceil_mode:如果等于True,计算输出信号大小的时候,会使用向上取整,代替默认的向下取整的操作

torch.nn.Linear

线性变换

torch.nn.Linear(in_features,out_features,bias = True)

in_features:每个输入样本的大小

out_features: 每个输出样本的大小

bias:如果设置为False,则图层不会学习附加偏差。默认值:True。

def __init__(self)是对网络进行定义,而def forward(self, x)则是将网络进行前向传播,给定一个28*28的图像,他会沿着这个网络进行传播,最终输出的为10个神经元。

4.训练

训练的时候按照epoch进行,有多少个epoch就会进行多少个循环。

进入epoch循环之后,先将损失函数的计算值设为0,每一次进行epoch之后,损失函数的值都需要先清为0。

进入epoch大循环之后,是一个以batch size为单位的小循环,enumerate后面跟的是训练集,

enumerate是一个迭代器,通过enumerate的迭代,从训练集中取数据,i为索引,data为train_loader中的数据,不过这个i是其对应的在train_loader中的索引,在这个网络的训练中其实用不到。

(感觉这个代码也没有什么好讲解的呀,所有的内容自己都已经是写在了这个程序注释里面,再说的话,就是废话了)

data是一个list列表,长度为2,也就是说data里面有两个数据,第一个数据是手写数字机的图像inputs,为Tensor,第二个数据是图像对应的标签,为0-9之间的数字。inputs里面图片的个数,和batch size的大小相同(data最原始就是由batch size划分出来的,大小肯定是和batch size相同啦~)。

下面为data的打印值

此时这个inputs和labels还知识普通的Tensor,需要将其转换为cuda类型的Tensor,这样才可以调用GPU。

optimizer梯度优化器,简单来说就是你要以什么样的方式来将这个网络的梯度降低,自己这里选用的是SGD,可以选用的还有Adam,AdamW。在进行梯度传播之前,需要先将这个梯度清零才行。

梯度清零之后,终于可以将inputs这个Tensor来输入到网络net中进行传播了,输出的outputs为Tensor,多维度的,有几个batch size,这个outputs里面就会有几个Tensor。每个Tensor其实是输出的为

loss = criterion(outputs, labels)

每一次一次传播之后,通过交叉熵损失函数来计算输出预测值与实际标签之间的差距,然后返回为一个值,这个值就叫做loss。

其中预测值和标签的值显示分别如下:

注意,是预测值在前面,标签在后面才行,顺序反了的话,是会报错的。

optimizer.step()

之后再将这个梯度反向传播回网络的各层中,网络各层中的各个神经元,根据这个loss的值来相应地进行自身权值的调整。如下图所示

optimizer.step()

不过将loss反向传播到网络中后,知识具备了调整权值的条件,但具体怎么调整权值,以什么方式来调整权值,则是由这个optimizer优化器函数来决定的,optimizer.step()就是使用内置的规则SGD,根据LOSS来对模型的权值进行调整。调整完权值之后,至此,一次网络的训练过程完毕。

sum_loss = sum_loss + loss.item()

下面将loss累加一下,其实这个loss累加,只是为了可视化,实际上对于网络训练来说,并没有什么实质性的作用,因此,即使将其删掉,也问题不大。

而至于为什么要用loss的item呢?item()这个函数可以将

测试部分

_, predicted = torch.max(outputs, 1)

这个torch.max输出出来的为

也就是说输出的Tensor里面,包含着两个Tensor,第一个Tensor为具体的最大的值,第二个Tensor为这个Tensor所在位置的索引。

(待更)

pytorch 预测手写体数字_教你用PyTorch从零开始实现LeNet 5手写数字的识别相关推荐

  1. 卷积神经网络mnist手写数字识别代码_搭建经典LeNet5 CNN卷积神经网络对Mnist手写数字数据识别实例与注释讲解,准确率达到97%...

    LeNet-5卷积神经网络是最经典的卷积网络之一,这篇文章就在LeNet-5的基础上加入了一些tensorflow的有趣函数,对LeNet-5做了改动,也是对一些tf函数的实例化笔记吧. 环境 Pyc ...

  2. LeNet实现手写数字识别

    MNIST数据集:https://github.com/wwjjss/MNIST/tree/main 代码:https://github.com/wwjjss/LeNet_MNIST/tree/mai ...

  3. matlab朴素贝叶斯手写数字识别_机器学习系列四:MNIST 手写数字识别

    4. MNIST 手写数字识别 机器学习中另外一个相当经典的例子就是MNIST的手写数字学习.通过海量标定过的手写数字训练,可以让计算机认得0~9的手写数字.相关的实现方法和论文也很多,我们这一篇教程 ...

  4. 基于TensorFlow深度学习框架,运用python搭建LeNet-5卷积神经网络模型和mnist手写数字识别数据集,设计一个手写数字识别软件。

    本软件是基于TensorFlow深度学习框架,运用LeNet-5卷积神经网络模型和mnist手写数字识别数据集所设计的手写数字识别软件. 具体实现如下: 1.读入数据:运用TensorFlow深度学习 ...

  5. 手写数字识别的实现(案例)

    1.概念介绍: 图像识别(Image Recognition)是指利用计算机对图像进行处理.分析和理解,以识别各种不同模式的目标和对像的技术. 图像识别的发展经历了三个阶段:文字识别.数字图像处理与识 ...

  6. 基于深度学习的手写数字识别算法Python实现

    摘 要 深度学习是传统机器学习下的一个分支,得益于近些年来计算机硬件计算能力质的飞跃,使得深度学习成为了当下热门之一.手写数字识别更是深度学习入门的经典案例,学习和理解其背后的原理对于深度学习的理解有 ...

  7. 读书笔记-深度学习入门之pytorch-第四章(含卷积神经网络实现手写数字识别)(详解)

    1.卷积神经网络在图片识别上的应用 (1)局部性:对一张照片而言,需要检测图片中的局部特征来决定图片的类别 (2)相同性:可以用同样的模式去检测不同照片的相同特征,只不过这些特征处于图片中不同的位置, ...

  8. 综合案例——手写数字图像处理算法比较

    手写数字图像识别各种算法的比较 1.准备工作 1.1.数据集介绍 使用到了两个手写数字的数据集: scikit-learn 中的手写数字数据集: mnist 中的手写数字数据集. 1.1.1.scik ...

  9. 深度学习——手写数字识别

    深度学习--手写数字问题 前不久入门学习了Tensorflow深度学习框架,了解一下什么是神经网络和Tensorflow的简单使用.下面通过Tensorflow框架来建造神经网络模型来对手写数字进行训 ...

最新文章

  1. [CareerCup] 2.4 Partition List 划分链表
  2. NUC972的BSP包的使用
  3. C#知识点总结系列:C# 数据结构
  4. 2019春季学期第四周作业
  5. 【经典回放】多种语言系列数据结构算法:树(C#、JavaScript、VB6版)
  6. 如何取消计算机用户名,Win10如何取消登录界面显示用户名?
  7. 传统emmc所用的sdio接口_SolidGear SD/SDIO/eMMC协议分析仪
  8. c语言文件加密解密单词统计,C语言文件加密解密及单词统计程序.doc
  9. HDU1406 完数【水题】
  10. Python 06 编码
  11. Windows XP刻录机不能刻盘显示函数不正确的解决办法
  12. Python读取微信朋友圈
  13. 中国联通 光猫 吉比特 G-140W-UG 管理员 账号密码
  14. 可以额外获得大量福卡的绝招
  15. cad动态块制作翻转_cad动态块制作教程
  16. 20日均线操作系统法
  17. 读《南怀瑾讲人生哲理》
  18. Delphi7 To Delphi XE的变化
  19. Android Framework开发大揭秘!从小白到大佬的进阶之路
  20. 虚拟内存与物理内存的区别,

热门文章

  1. 姚明叶莉夜奔小九寨沟 深山幽谷密拍婚纱照(图)
  2. 阿里云盘,终究杀疯了 !
  3. web/b.s扑克斗地主小小完工
  4. 一键提高工作效率,这4款软件是你的工作好帮手
  5. IDEA学习笔记——文件资源定位图标。小齿轮的显示和隐藏(Autoscroll from Source)
  6. 拼图类APP原型模板分享——简拼
  7. 撰写毕设论文正文的摘要、绪论、相关技术介绍-“一楼正式开建”-03
  8. eclipse跳转到指定行快捷键_Eclipse常用快捷键
  9. 自动化测试成熟度模型
  10. Android log.e(),log.d(),log.i()等的区别