PyTorch项目笔记(一):MINST手写体数据集的识别
MINST手写体数据集的识别,利用pytorch实现。
MINST手写体数据集的识别
MINST手写体数据集
MNIST数据集(Mixed National Institute of Standards and Technology database)是美国国家标准与技术研究院收集整理的大型手写数字数据集,包含了60,000个样本的训练集以及10,000个样本的测试集。Mnist中所有样本都会将原本28*28的灰度图转换为长度为784的一维向量作为输入,其中每个元素分别对应了灰度图中的灰度值。Mnist使用一个长度为10的向量作为该样本所对应的标签,其中向量索引值对应了该样本以该索引为结果的预测概率。
1、导入数据集
利用DataLoader导入数据集
PyTorch中数据读取的一个重要接口是torch.utils.data.DataLoader,该接口定义在dataloader.py脚本中,只要是用PyTorch来训练模型基本都会用到该接口,该接口主要用来将自定义的数据读取接口的输出或者PyTorch已有的数据读取接口的输入按照batch size封装成Tensor,后续只需要再包装成Variable即可作为模型的输入。
torch.utils.data.DataLoader参数:
- dataset (Dataset) – 加载数据的数据集。
- batch_size (int, optional) – 每个batch加载多少个样本(默认: 1)。
- shuffle (bool, optional) – 设置为True时会在每个epoch重新打乱数据(默认: False).
- sampler (Sampler, optional) – 定义从数据集中提取样本的策略,即生成index的方式,可以顺序也可以乱序
- num_workers (int, optional) – 用多少个子进程加载数据。0表示数据将在主进程中加载(默认: 0)
- collate_fn (callable, optional) –将一个batch的数据和标签进行合并操作。
- pin_memory (bool, optional) –设置pin_memory=True,则意味着生成的Tensor数据最开始是属于内存中的锁页内存,这样将内存的Tensor转义到GPU的显存就会更快一些。
- drop_last (bool, optional) – 如果数据集大小不能被batch size整除,则设置为True后可删除最后一个不完整的batch。如果设为False并且数据集的大小不能被batch
size整除,则最后一个batch将更小。(默认: False)- timeout,是用来设置数据读取的超时时间的,但超过这个时间还没读取到数据的话就会报错
datasets.MNIST类 CLASS torchvision.datasets.MNIST(root: str,
train: bool = True, transform: Optional[Callable] = None,
target_transform: Optional[Callable] = None, download: bool = False)
- root (string): 表示数据集的根目录,其中根目录存在MNIST/processed/training.pt和MNIST/processed/test.pt的子目录
- train (bool, optional): 如果为True,则从training.pt创建数据集,否则从test.pt创建数据集
- download (bool, optional): 如果为True,则从internet下载数据集并将其放入根目录。如果数据集已下载,则不会再次下载
- transform (callable, optional): 接收PIL图片并返回转换后版本图片的转换函数
- target_transform (callable, optional): 接收PIL接收目标并对其进行变换的转换函数
导入数据集的过程
- 使用DataLoader对数据进行封装,PyTorch会在root目录下检测数据是否存在,当数据不存在时,则自动将数据下载到data目录中。
- 使用ToTensor()将0 ~ 255的像素值映射到0 ~ 1的范围内,并转化为Tensor格式。
- 使用Normalize(mean, std)方法实现归一化。不同数据集中的图像通道对应的均值(mean)和标准差(std)是不同的。MNIST数据集的均值是0.1307,标准差是0.3081,这些系数是数据集提供方提供的,有利于加速神经网络的训练。
- 随机取出一个batch下的数据进行观察。
import numpy as np
import torch
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import torchvision
#训练集
train_loader = torch.utils.data.DataLoader(
datasets.MNIST(root='./data', #root表示数据加载的相对目录
train=True, #train表示是否加载数据库的训练集,False时加载测试集
download=True,#download表示是否自动下载
transform=transforms.Compose([#transform表示对数据进行预处理的操作
transforms.ToTensor(),#图片转张量,同时归一化0-255 ---》 0-1
transforms.Normalize((0.1307,), (0.3081,))#标准化均值方差
])),batch_size=64, shuffle=True)#batch_size表示该批次的数据量 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=64, shuffle=True)
def imshow(img):
img = img / 2 + 0.5 # 逆归一化 在深度学习中,通常在将图像输入模型之前会对其进行归一化操作,
#使其像素值落在特定的范围内(例如[0, 1]或[-1, 1])。逆归一化的目的是将其还原回原始的像素值范围。
npimg = img.numpy()#显示图像时,通常需要将图像数据从 PyTorch 格式转换为 NumPy 格式。
plt.imshow(np.transpose(npimg, (1, 2, 0)))#使用np.transpose函数将第一个维度(通道维度)变为转置数组的第二个维度,第二个维度变为第三个维度,第三个维度变为第一个维度
plt.show()
# 得到batch中的数据
dataiter = iter(train_loader)#iter 用于创建一个可迭代对象的迭代器。
#使用iter函数将train_loader转换为一个迭代器对象dataiter,从而可以逐个获取train_loader中的批次数据。
images, labels = dataiter.__next__()
# 展示图片
imshow(torchvision.utils.make_grid(images))
在计算机图像处理中,图像通常以通道优先顺序存储,即通道维度在最前面。然而,在Matplotlib中显示图像时,默认假设图像数据是以通道为最后一个维度的顺序存储的。因此,为了正确显示图像,需要对图像数据进行转置,将通道维度放置在最后一个维度。
转置操作可以通过改变数组的维度顺序来实现。在这个例子中,使用np.transpose函数将第一个维度(通道维度)变为转置数组的第二个维度,第二个维度变为第三个维度,第三个维度变为第一个维度。这样,就将图像数据从通道优先顺序转换为以通道为最后一个维度的顺序。
数据集下载成功:
一个batch下的数据如图所示:
2、构建模型
神经网络训练步骤包括以下:
- 定义神经网络
- 前向传播
- 计算损失
- 反向传播
- 更新参数
2.1定义神经网络
借鉴csdn上大神的一个图像识别网络:指路->http://t.csdnimg.cn/mYEzi
Convolutions是卷积操作;Subsampling是下采样操作,也就是池化;Full connection 表示全连接层。该网络将输入的图片,经过两层卷积和池化,再经过三层全连接层,最后输出图片对应每个阿拉伯数字的概率。
名词解释:
- 卷积操作(Convolutional Operation):卷积操作是神经网络中的一种基本操作,用于提取输入数据中的特征。卷积操作通过滑动一个卷积核(也称为滤波器)在输入数据上进行计算,将输入数据的局部区域与卷积核进行逐元素的乘积运算,并将乘积结果相加得到输出特征图。卷积操作在图像处理领域尤为常见,因为它能有效地提取图像中的空间局部特征,例如边缘、纹理等。在卷积神经网络(Convolutional Neural Network, CNN)中,卷积操作通常用于处理图像数据。
- 下采样操作(Pooling Operation):下采样操作是一种减小特征图尺寸的操作。常见的下采样操作有最大池化(Max Pooling)和平均池化(AveragePooling)。这些操作将特征图划分为不重叠的区域,并在每个区域内取最大值或平均值作为输出特征值。下采样操作有助于减少特征图的空间维度,同时保留重要的特征信息,提高模型的鲁棒性和计算效率。在池化操作中,常见的池化操作之一是在(2, 2)的窗口上进行池化。这种池化操作通常称为2x2最大池化或平均池化。
- 全连接层(Fully Connected Layer):全连接层也称为密集连接层(Dense Layer),是神经网络中的一种常见层类型。在全连接层中,每个神经元与上一层的所有神经元都有连接。这就意味着前一层的所有神经元的输出都被传递到全连接层中的每个神经元,且每个神经元都有自己的权重参数。全连接层在网络中起到整合和映射特征的作用,通常用于将前面的卷积层或其他特征提取层的输出映射为最终的输出类别或回归值。
神经网络的前向传播是指信号从输入层经过网络中的各层逐层传递到输出层的过程。在前向传播中,神经网络将输入数据通过一系列的线性和非线性变换,逐渐提取并组合特征,最终生成对应的输出结果。前向传播在神经网络中非常重要,原因如下:
- 特征提取:通过前向传播,神经网络可以自动学习和提取输入数据中的有用特征。每个隐藏层都可以看作是对输入数据进行特征提取的一种方式。随着信号在网络中传播,特征表示逐渐变得更加抽象和高级。前向传播过程中的每个层都会对输入进行一定的变换,使得网络能够自动学习输入数据的复杂特征。
- 参数传递:神经网络的每个层都包含一组可学习的参数,如权重和偏置。前向传播过程中,这些参数被用于计算每个神经元的输出。通过前向传播,神经网络中的参数得以有效地传递和利用。不同层之间的参数传递是通过矩阵乘法和非线性激活函数实现的。
- 输出预测:神经网络的前向传播将输入数据转化为网络的输出结果,这通常是它在特定任务上的预测或分类。通过前向传播,网络通过将输入数据映射到输出空间来进行预测和推断。这使得神经网络成为一种功能强大的模型,可用于图像分类、语音识别、自然语言处理等各种任务。
import torch
import torch.nn as nn
import torch.nn.functional as F#可以调用一些常见的函数,例如非线性以及池化等
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 1 input image channel, 6 output channels, 5x5 square convolution
# 输入图片是1 channel输出是6 channel 利用5x5的核大小
self.conv1 = nn.Conv2d(1, 6, 5)#定义了第一个卷积层,输入图像通道数为1(单通道灰度图像),输出通道数为6,卷积核大小为5x5。
self.conv2 = nn.Conv2d(6, 16, 5)#定义了第二个卷积层,输入通道数为6(第一个卷积层的输出通道数),输出通道数为16,卷积核大小为5x5。
# 全连接 从16 * 4 * 4的维度转成120
self.fc1 = nn.Linear(16 * 4 * 4, 120)#定义了第一个全连接层,将16x4x4的输入数据展平为一个大小为120的向量。全连接层的输入尺寸与卷积核的尺寸无关,而是与卷积层的输出通道数相对应
self.fc2 = nn.Linear(120, 84)#定义了第二个全连接层,将120维的向量转换为84维。
self.fc3 = nn.Linear(84, 10)#定义了最后一个全连接层,将84维的向量转换为10维,通常用于多类别分类任务。
def forward(self, x):#神经网络前向传播
# 在(2, 2)的窗口上进行池化
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))#应用ReLU激活函数后,对第一个卷积层的输出进行最大池化操作。
x = F.max_pool2d(F.relu(self.conv2(x)), 2)#(2,2)也可以直接写成数字2
对第二个卷积层的输出进行最大池化操作。
x = x.view(-1, self.num_flat_features(x))#将维度转成以batch为第一维 剩余维数相乘为第二维
#将卷积层的输出数据展平为一维向量,以便进行全连接层的计算 self.num_flat_features(x)函数用于计算展平后的特征数量
x = F.relu(self.fc1(x)) #应用ReLU激活函数后,通过第一个全连接层。
x = F.relu(self.fc2(x))#通过第二个全连接层。
x = self.fc3(x)#通过最后一个全连接层,这一层的输出通常用于进行分类。
return x
def num_flat_features(self, x):#用于计算展平后的特征数量。
size = x.size()[1:] # 第一个维度batch不考虑
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)#显示每一层的参数和结构信息。
输出结果
2.2前向传播
前向传播是指:定义了神经网络之后,将所有数据按照batch的方式进行输入,得到对应的网络输出。我们取前两张图片进行观察。我们选择了前两个图像和它们的标签,然后将这些图像传递给模型以获得预测。
image = images[:2]
label = labels[:2]
print(image.size())
print(label)
out = net(image)#前向传播
print(out)#输出包含网络的预测结果
运行结果:
随机取出的图片是数字4和9。最后输出的维度为10的tensor,每个位置上的数值代表成为该类别的概率值。以第二张图片为例,第二张图片为数字5的概率最大,与实际数字9不一致。输出结果与实际情况不符的原因是目前网络没有进行训练,只是随机初始化了结构中的权重,所以输出暂时没有参考价值。
2.3计算损失
损失函数需要一对输入:模型输出与目标,用于评估输出距离目标有多远。损失用loss来表示,损失函数的作用就是计算神经网络每次迭代的前向计算结果与真实值之间的差距,从而指导模型下一步训练往正确的方向进行。常见的损失函数有交叉熵损失函数和均方误差损失函数。
在PyTorch中,nn库模块提供了多种损失函数,常见的有以下几种:
- 回归问题:nn.MSELoss()
- 二分类问题:nn.BCELoss()
- 多分类问题:nn.CrossEntropyLoss()
由于MNIST数据集是十个分类,因此选择nn.CrossEntropyLoss().
image = images[:2]
label = labels[:2]
out = net(image)
criterion = nn.CrossEntropyLoss()#交叉熵损失
loss = criterion(out, label)#计算模型预测out和标签真实值的交叉熵损失
print(loss)
结果如下:
结果表明当前两个样本通过网络输出后与实际差距仍有2.3094,我们的训练目标是最小化loss值。
2.4反向传播与参数更新
当计算出一次前向传播loss值之后,可进行反向传播计算梯度,以此来更新参数。在Pytorch中,对loss调用backward()即可。
在深度学习过程中进行反向传播,计算输出变量关于输入变量的梯度。最后要做的就是更新神经网络的参数,最简单的规则就是随机梯度下降(SGD),为了更新神经网络的权重以最小化损失函数,公式如下:
weight=weight−learningRate×gradient
weight是模型的参数
learningRate是学习率,控制了参数更新的步长
gradient是梯度
#创建优化器
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.01)#lr代表学习率
criterion = nn.CrossEntropyLoss()
# 在训练过程中
image = images[:2]
label = labels[:2]
optimizer.zero_grad() # 消除累积梯度,PyTorch会在每次反向传播时累积梯度,所以你需要在每个批次之前将其清除,以避免梯度混合
out = net(image)
loss = criterion(out, label)
loss.backward()
optimizer.step() # 更新参数
3、模型训练
可将训练过程封装成一个函数,像该函数传入网络模型、损失函数、优化器等必要对象后,在MNIST数据集上进行训练并打印日志观察过程,代码如下:
def train(n):
net.train() # 设置为training模式
for epoch in range(n):
running_loss = 0.0
for i, data in enumerate(train_loader):
# 得到输入 和 标签
inputs, labels = data
# 消除梯度
optimizer.zero_grad()
# 前向传播 计算损失 后向传播 更新参数
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 打印日志
running_loss += loss.item()
if i % 100 == 0: # 每100个batch打印一次
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 100))
running_loss = 0.0
train(2)#此处表示训练两轮
结果如下图所示,从结果中可以看出训练过程中loss值不断下降:
4、模型评估
训练完成之后为了检验模型的训练结果,可以在测试集上进行验证,通过不同的评估方法进行评估。分类模型的常见评估方法是进求分类准确率,它能衡量所有类别中预测正确的个数占所有样本的比值,代码如下:
correct = 0
total = 0
with torch.no_grad():#或者model.eval() 表示关闭梯度跟踪
for data in test_loader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))
torch.max()这个函数返回的是两个值,第一个值是具体的value(我们用下划线_表示),第二个值是value所在的index(也就是predicted)。1也可以写作dim=1。表示输出所在行的最大值,若改写成dim=0则输出所在列的最大值。预测为对应类别的概率,而行代表样本、列代表类别,所以这里应该用dim=1。
结果显示:“在10000张测试图像上的网络准确率为95%”
训练时用的是train loader数据集,测试时就得用另一部分的数据集 test_loader。
5、结果测试
我们随机取出8张数字图片进行测试:
image = images[:8]
label = labels[:8]
imshow(torchvision.utils.make_grid(image))
print("数字图片标签值:", end="")
print(label)
out = net(image)
out_label = []
for m in out:
t = torch.argmax(m).item()
out_label.append(t)
print("神经网络预测值:", end="")
print(out_label)
结果显示:
以上即为MINST数据集的识别,记录自己入门学习的一些琐碎知识,如有不足,欢迎补充。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)