一、如何使用pytorch实现手写数字识别:
1.流程:
准备数据,需要准备DataLoader
构建模型,可以使用torch构造一个深层的神经网络
模型的训练
模型的保存,保存模型,后续持续使用
模型的评估,使用测试集,观察模型的好坏
2.目标
使用pytorch完成神经网络的构建
知道pytorch中激活函数的使用方法
知道pytorch中torchvision.transforms中常见图形处理函数的使用
理解CNN的结构
了解CNN的构成方法
知道如何训练模型和如何评估模型
初步理解神经网络,理解深度学习
二、流程细化
1.准备数据:
MNIST:手写数字的数据集
2.准备模型:
全连接层:当前一层的神经元和前一层的神经元相互连接,其核心操作就是y = wx,即矩阵的乘法,实现对前一层的数据的变换
模型的构建使用了一个三层的神经网络,其中包括两个全连接层和一个输出层,第一个全连接层会经过激活函数的处理,将处理后的结果交给下一个全连接层,进行变换后输出结果
在这个模型中需要注意的地方:
①激活函数如何使用
②每一层数据的形状
③模型的损失函数
神经网络模型:CNN
3.准备工作:
3.1 引入项目所需数据包:
import torch
#导入对图像的预处理模块
import torchvision
#导入dataset的分批读取包
from torch.utils.data import DataLoader
#导入神经网络包nn(可以定义和运行神经网络)
import torch.nn as nn
#有些导入functional这个包中包含了神经网络中使用的一些常用函数,这些函数的特点:不具有可学习的参数(RelU,pool,DropOut等)
import torch.nn.functional as F
#optim中实现了大多数的优化方法来更新网络权重和参数,如SGD,Adam
import torch.optim as optim #参数优化
import time
import matplotlib.pyplot as plt
import random
from numpy import argmax
import numpy as np
from PIL import Image #PIL是一种图像的格式
from torch.utils.tensorboard import SummaryWriter
其中,需要注意的点有:
①加载数据集:MNIST手写数字识别数据集中的图像是一个28*28的灰度图像,我们通过pytorch的内置函数将MNIST下载并读到内存中,将训练和测试数据集下载到同目录下的data文件下,其中shuffle参数为是否打乱原有数据顺序
②预处理数据:由于pytorch读取数据集MNIST中的图像时默认使用python中的PIL图像(P:8像素,I:整型,L:黑白),所以我们首先需要把PIL图像转化成更适合pytorch计算使用的图像张量,其次需要把原始0~255之间的像素值通过归一化处理成0~1之间的值,这两步预处理的目的都是欲使数据在神经网络中运算更高效。transforms.ToTensor()作用就是将PIL中28*28的灰度图像转化为tensor张量其维度为1x28x28(CxWxH)其中1的含义为单通道(RGB图像时调整为3通道)transforms.Normalize([0.1307],[0.3081]),其中0.1307和0.3081是MNIST数据集的均值和标准差,因为MNIST数据值都是灰度图,所以图使用MNIST数据集的均值和标准差将数据标准化处理
使用Compose方法即是将两个操作合并一起
在运行前定义各个超参数
'''定义各个超参数'''
# Basic Params-----------------------------
epoch = 1 # 模型训练轮数
learning_rate = 0.01 # 设置SGD中的初始学习率
batch_size_train = 64 # 指定DataLoader在训练集中每批加载的样本数量
batch_size_test = 1000 # 指定DataLoader在测试集中每批加载的样本数量
gpu = torch.cuda.is_available()
momentum = 0.5 # 设置SGD中的冲量
注:超参数是可以手动调整的参数
准备MNIST数据集的Dataset和DataLoader
# 准备MNIST数据集的Dataset和DataLoader
'''MNIST数据集共有四个文件:
train-images-idx3-ubyte.gz:训练集图片,6w张
train-labels-idx1-ubyte.gz:训练集图片对应的标签
t10k-images-idx3-ubyte.gz:测试集图片,1w张
t10k-labels-idx-ubyte.gz:测试集图片对应的标签
图片是0~9的手写数字图片,共10类,标签是图片的实际数字,每张图片都是28*28的单通道灰度图,且数字居中以减少预处理和加快运行
'''
# Load Data---------------------------------
train_loader = DataLoader(torchvision.datasets.MNIST('./data/', train=True, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,))
])),
batch_size=batch_size_train, shuffle=True) # shuffle表示乱序
test_loader = DataLoader(torchvision.datasets.MNIST('./data/', train=False, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,))
])),
batch_size=batch_size_test, shuffle=True)
train_data_size = len(train_loader)
test_data_size = len(test_loader)
print(train_data_size)
print(test_data_size)
其中,在下载MNIST时需注意在所在文件夹新建”data“文档用于存储MNIST文件
以该代码运行得到的结果为其中:10代表十类数字,938代表938个种类(找了很多人写的)
图像处理过程:(numpy)转换成矩阵,(ToTensor)转换成张量(大于三维的叫张量)
Ⅰ: ToTensor转化成张量——>成为矩阵
Ⅱ:Normalize归一化处理——>由Image变成Tensor
Ⅲ:Compose合并(将两个方法合并操作,对每一个图像都做该处理)
注:如果是彩色图像,还需要做一个灰度处理
测试一下
#测试-----------------------------------
examples = enumerate(test_loader)
batch_idx,(example_data,example_targets) = next(examples)
print(example_targets)
print(example_data.shape)
import matplotlib.pyplot as plt
fig = plt.figure()
for i in range(6):
plt.subplot(2,3,i+1)
plt.tight_layout()
plt.imshow(example_data[i][0],cmap='gray',interpolation='none')
plt.title("Ground Turth:{}".format(example_targets[i]))
plt.xticks([])
plt.yticks([])
plt.show()
其中,targets表示标签,这个数表示的到底是什么
shape表示x*x的形状
subplot()表示2*3的绘图区域
imshow(example_data[i][0])i是第几个,0表示二维
在这里使用“Ground Turth”确定真实值,并用“tagrgets”标签进行标注
3.2 构建模型
使用torch构造一个深层的神经网络
需要用到两个函数:
1. 首先定义一个网络类Net,需要其继承神经网络的module,并进行初始化(构造函数)
class Net(nn.Module): # 定义网络类,继承自神经网络nn的Module
def __init__(self):
2. 前向传播函数(也叫前向反馈函数)
def forward(self, x):
return self.model(x)
在模型中,设置条件:如果使用gpu,就调用cuda(),反之直接使用
if gpu:
net = Net().cuda()
else:
net = Net()
卷积操作的超参数:
卷积核个数:K
卷积核大小:F
滑动步长(Stride):S
填充(Padding):P
1.torch.nn.Conv2d(in_channels,out_channels,kernel_size, stride, padding):用于搭建卷积神经网络的卷积层,主要的输入参数有输入通道数,输出通道数,卷积核大小,卷积核移动步长和Padding值
in_channel:输入数据的通道数(例如RGB图像的通道数为3)
out_channel:输出数据的通道数(可根据模型调整)
kernel_size:卷积核大小,可以是int或者tuple(kernel_size = 2,意味着卷积核大小为2,kernel_size = (2,3),意味着卷积在第一维度大小为2,第二维度大小为3
stride:步长,默认为1,与kernel_size类似,stride = 2,意味在所有维度步长为2,stride = (2,3)意味着在第一维度步长为2,在第二维度步长为3
padding:零填充,在图像的周边增加几个空白像素
卷积运算后的维度变化:
激活函数:
1.二分类用到sigmoid()函数
使用sigmoid进行计算数对数似然损失,来定义我们的2分类的损失
#示例
nums = np.arange(-10,10,step = 1)#生成一个numpy数组
fig,ax = plt.subplots(figsize = (12,4))#绘制子图
ax.plot(nums,1.0/(1+np.exp(-nums)),‘r’)#绘制sigmoid的函数图像
plt.show()
·在二分类中有正类和负类,正类的概率为
,负类的概率为1-P(X)
2.ReLu()函数
激活层使用ReLu()激活函数。
线性整流函数(Rectified Linear Unit,ReLu),又称修正线性单元,是一种人工神经网络中常用的激活函数(activation function),通常指代以斜坡函数及其变种为代表的非线性函数
3. torch.nn.MaxPool2d():用于实现卷积神经网络中的最大池化层,主要的输入参数时池化窗口的大小、池化窗口移动步长和Padding的值
同样:
池化窗口大小的数据类型是整形,用于确定池化窗口的大小
池化窗口步长的数据类型也是整形,用于确定池化窗口每次移动的步长
Padding值和在torch.nn.Conv2d中定义的Padding的值的用法和意义时是一样的
4. 全连接层。
之前卷积层要求输入输出是四维张量(B,C,W,H),而全连接层的输入与输出都是二维张量(B,Input_feature),经过卷积、激活、池化后,使用view打平,进入全连接层
torch.nn.Flatten(start_dim = 1,end_dim = -1)作用:将连续的维度范围展平为张量。经常在nn.Sequential()中出现,一般写在某个神经网络模型之后,用于对神经网络模型的输出进行处理,得到tensor类型的数据
#nn.Linear():用于设置网络中的全连接层,需要注意的是全连接层的输入与输出都是二维张量
#in_features指的是输入的二维张量的大小,即输入的[batch_size,size]中的size
#in_features由输入张量的形状决定,out_features则决定了输出张量的形状
#out_features指的是输出的二维张量的大小,即输出的二维张量的形状为[batch_size,output_size],当然,他也代表了该全连接层的神经元个数
损失函数:
1.二分类用到sigmoid()函数
使用sigmoid进行计算对数似然损失,来定义2分类的损失
·在2分类中有正类和负类,正类的概率为,负类的概率为1-P(X)
·将这个结果进行计算对数似然有损失就可以得到最终的损失
那么在这么多的分类过程中该如何做?
·多分类和2分类中唯一的区别是我们不能够再使用sigmoid函数来计算当前样本属于某个类别的概率,而应该使用softmax函数
·softmax和sigmoid的区别在于我们需要去计算样本属于每个类别的概率,需要计算多次,而sigmoid只需要计算一次
softmax的公式如下:
softmax函数的表示实例:
假设:softmax之前的输出结果是2.3,4.1,5.6,那么经过softmax之后的结果是多少呢?
对于这个softmax输出的结果,是在[0,1]区间,我们可以把它当做概率和前面2分类的损失一样,多分类的损失只需要再把这个结果进行对数似然损失的计算即可
即:
最后,会计算每个样本的损失,即上式的平均值
我们把softmax概率传入对数似然损失得到的损失函数称为交叉熵损失
交叉熵 是统计学中的一个概念,用于衡量两个概率分布的差异性,利用交叉熵就可以很容易的衡量出当前的输出与目标输出的差距是多少,直接使用pytorch内置的交叉熵损失函数
loss_fn =nn.CrossEntropyLoss()
参数优化:
参数优化使用随机梯度下降
要使用Optimizer,我们首先要创建一个Optimizer对象,该对象会保持当前状态,并根据计算梯度来更新参数
·创建Optimizer时,需要为其提供一些需要迭代的参数进行迭代,还可以指定一些可选的,特定的,用于优化的参数,如学习率,权重衰减等参数
optimizer=optim.SGD(net.parameters(),lr=learning_rate,momentum=0.9)
·model.parameters(是获取model网络参数,构建好神经网络后,网络的参数都保存在
parameters()函数当中
·learningRate:学习率(梯度下降)
·momentum:“冲量”这个概念源自于物理中的力学,表示力对时间的积累效应
3.3 测试模型(评估模型)
评估的过程和训练的过程类似,但是:
·不需要计算梯度
·需要收集损失和准确率,用来计算平均损失和平均准确率
·损失的计算和训练时候损失的计算方法相同
准确率的计算:
·模型的输出为[batch_size,10]的形状
·其中最大值的位置就是其预测的目标值(预测值进行过softmax后为概率,softmax中分母都是相同的,分子越大,概率越大)
·最大值的位置获取的方法可以使用torch.max,返回最大值和最大值的位置
·返回最大值的位置后,和真实值([batch size])进行对比,相同表示预测成功
3.4 模型的训练
训练的流程:
①获取,遍历dataloader
②梯度设置为0
③进行向前计算
④计算损失
⑤反向传播
⑥更新参数
效果: