目录
21个项目玩转PyTorch实战
本文 学习笔记均来自于上述同款书名
通过经典项目入门 PyTorch,通过前沿项目提升 PyTorch,基于PyTorch玩转深度学习,本书适合人工智能、机器学习、深度学习方面的人员阅读,也适合其他 IT 方面从业者,另外,还可以作为相关专业的教材。
有需要的宝子们可以去拼多多购买,有便宜的
PyTorch 是基于 Torch 库的开源机器学习库,它主要由 Meta(原 Facebook)的人工智能研究实验室开发,在自然语言处理和计算机视觉领域都具有广泛的应用。本书介绍了简单且经典的入门项目,方便快速上手,如 MNIST数字识别,读者在完成项目的过程中可以了解数据集、模型和训练等基础概念。本书还介绍了一些实用且经典的模型,如 R-CNN 模型,通过这个模型的学习,读者可以对目标检测任务有一个基本的认识,对于基本的网络结构原理有一定的了解。另外,本书对于当前比较热门的生成对抗网络和强化学习也有一定的介绍,方便读者拓宽视野,掌握前沿方向。
以下进入正题
正文
1.1 MNIST数据集
mnist数据集主要是一些手写的数字的图片及对应标签,图片共有10类,分别对应0~9,如下图所示
1.1.1 数据集简介
原始的MNIST数据共包含4个文件
文件名 | 大小 | 用途 |
train-images | ≈9.45Mb | 训练图像数据 |
train-labels | ≈0.03Mb | 训练图像的标签 |
t10k-images | ≈1.57Mb | 测试图像数据 |
t10k-labels | ≈4.4Kb | 测试图像的标签 |
我们可以通过自行下载数据集,然后自行处理,也可以使用打包好的
1.1.2导入数据集
pytorch中有个包叫torchvision,主要由models,datasets,transforms三个子包组成.
其中,models定义了许多用来完成图像方面深度学习的任务模型
datasets包含MNIST,CIFAR,ImageFolder等一些常用的数据集,并提供了数据集设置的一些重要参数 ,可以通过简单数据集设置来进行数据集的调用
transforms用来对数据进行预处理,预处理会加快神经网络的训练,常见预处理包括从数组转成张量,归一化等常见的变化
下面导入一段代码,进行讲解
import numpy as np
import torch
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import torchvision
'''
运行说明
安装依赖命令,要安装这个版本。
pip install torch==1.4.0 torchvision==0.5.0
pip install matplotlib numpy
代码按照内容的顺序,方便阅读。
'''
# 1.1.2 导入数据集
train_loader = torch.utils.data.DataLoader(
datasets.MNIST(root='./data', #root表示数据加载的相对目录
train=True, #train表示是否加载数据库的训练集,False时加载测试集
download=True,#download表示是否自动下载
transform=transforms.Compose([#transform表示对数据进行预处理的操作
transforms.ToTensor(),
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)
上述代码最外层使用了DataLoader进行封装,若数据集 不存在会自动下载到data文件夹中,其中transforms进行了俩个操作
1.ToTensor 用来把RGB或numpy.ndarray(H*W*C)0~255的值映射到0~1的范围内,并转换成ToTenssor格式
2.Normalize,用来实现归一化,不同数据集中图像通道的均值(mean)和标准差(std)俩个数值不一样,如本文中的数据集均值是0.1307,标准差是0.3081,这些系数有利于加快神经网络的训练
接下里我们随机取一个batch的数据进行观察并进行可视化画出来
有不了解iter函数的小伙伴可以看看iter函数的使用:记录一下iter()的用法_iter函数_Ultraman、GAUSS的博客-CSDN博客
1.2构建模型
在本节我们定义一个图像识别网络,一个典型的神经网络训练过程包括定义神经网络,前向传播,计算损失,反向传播,更新参数.
1.2.1 定义神经网络
在pytorch中,torch.nn是专门为神经网络设计的模块化接口,nn库可以用来定义和运行神经网络,nn.Module是nn库中十分重要的类,包含网络各层的定义及forward函数.pytorch允许定义自己的神经网络,但是需要继承nn.Module类,并实现forward函数.在nn.Module的子类中定义forward函数,backward函数就会被自动实现.一般把神经网络中具有可学习系参数的层放在构造函数__init__中,而并不具有科学系参数的层(如ReLU),可放在构造函数中,也可不放在可构造函数中,以下是代码的大概框架.
import torch
import torch.nn as nn #导入nn库的常见做法
import torch.nn.functional as F#可以调用一些常见的函数,例如非线性以及池化等
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
nn.module1 = ...
nn.module2 = ..
nn.module3 = ..
def forward(self, x):
x = self.module(x)
x = self.module(x)
x = self.module(x)
return x
本文我们针对MNIST数据集,构建一个简单的图像识别网络
这就不得不说到最经典的卷积神经网络了,网络结构如图所示
这是个简单的前馈神经网络,其中convolutions是卷积操作,subsampling是下采样操作,也就是池化,full connection表示全连接层, gaussian connection是进行了欧式径向基函数运行并输出最终结果.他将输入的图片,经过俩层卷积和池化,在经过三层的全连接,最终输出我们想要的概率值
下图可以更加清晰看出
代码如下
# 1.2.1 定义神经网络
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
# 输出特征图的大小 = (输入特征图的大小 - 核大小 + 2 * 填充) / 步长 + 1
# 输入图片是1 channel输出是6 channel 利用5x5的核大小
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# 全连接 从16 * 4 * 4的维度转成120
self.fc1 = nn.Linear(16 * 4 * 4, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# 在(2, 2)的窗口上进行池化
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), 2)#(2,2)也可以直接写成数字2
x = x.view(-1, self.num_flat_features(x))#将维度转成以batch为第一维 剩余维数相乘为第二维
x = F.relu(self.fc1(x))
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)
输出结果如下
首先把该网络取名为 Net,再继承nn.Module 类,在初始化函数中定义了卷积和全连接。卷积利用的函数是nn.Conv2d,它接收三个参数,即输人通道、输出通道和核大小。由于MNIST 是白数据集,所以只有一个颜色的通道,如果是其他彩色数据集(如 CIFAR 或者自定义的彩色图片)则有R、G、B三个通道。所以最开始的输人为1,核大小定义为 5,输出定义为6,代表6个特征提取器提取出6份不同的特征。全连接层又叫 Full-connected Layer 或者 Dense Layer,在 PyTorch中通过 nn.Linear 函数来实现,是一个常用来做维度转换的层,接收两个参数,即输人维度和输出维度。初始化卷积和全连接后,我们在 forward 函数中定义前向传播,当输入变量x时,F.maxpool2d(F.relu(self.conv1(x)),(2,2))先输人第一层卷积,为了增加非线性表征,在卷积后加人一层ReLU层,再调用F中的max pool2d 函数定义窗口大小为(2,2)进行池化,得到新的X。以此类推将x输人接下来的网络结构中。
然后因为我也是个新手小白,再看网络的时候对nn.Linear(16*5*5,120)产生了疑惑,网上有俩种解释:关于PyTorch教程中神经网络一节中的 self.fc1 = nn.Linear(16 * 5 * 5, 120)_ka0110的博客-CSDN博客
nn.Linear(16*5*5,120)中为什么是16*5*5?_Chukai123的博客-CSDN博客
但是都感觉有点不理解,这里如果有小伙伴知道的,可以在评论区解答一下
经过我和我朋友讨论过后,发现其实以上俩种说法都不正确,大家可以看下我们的理解,若有错误,请指正:PyTorch教程数字识别中为什么是 self.fc1 = nn.Linear(16*5*5, 120)一事_后来后来啊的博客-CSDN博客
1.2.2 前向传播
定义完一个网络结构后,后期我们会将所有数据按照batch的方式进行输入,得到对应的网络输出,这也就是所谓的前向传播,这里取少量数据样本进行距离,代码如下
# 1.2.2 前向传播
image = images[:2]
label = labels[:2]
print(image.size())
print(label)
out = net(image)
print(out)
输出结果为:
取两张图片进行网络的输人,维度情况为 2x1x 2828,经过定义后进行网络输出,得到维度为10的 tensor,每个位置上的数值代表成为该类别的概率值。可以看出,第二张图片在0位置的概率值最大,对应标签是数字 1,而实际的标签索引为数字 2,对应标签是数字 3,之所以有差距是因为目前网络仍没有进行训练,只是随机初始化了结构中的权重,所以输出暂时没有考虑价值。
1.2.3计算损失
损失函数需要一对输人:模型输出和目标,用来评估输出距离目标有多远。损失用loss 来表示,损失函数的作用就是计算神经网络每次迭代的前向计算结果与真实值之间的差距,从而指导模型下一步训练往正确的方向进行。常见的损失函数有交叉熵损失函数和均方误差损失函数。
在PyTorch中m库模块提供了多种损失函数,常用的有以下几种处理回归问题的nn.MSELoss函数,处理二分类的nn.BCELoss 函数,处理多分类的nn.CrossEntropyLoss 函数,由于本次MNIST数据集是10个分类,因此选择nn.CrossEntropyLoss 函数,代码如下:
# 1.2.3 计算损失
image = images[:2]
label = labels[:2]
out = net(image)
criterion = nn.CrossEntropyLoss()
loss = criterion(out, label)
print(loss)
输出结果是:
表明当前两个样本通过网络输出后与实际差距仍有 2.2725,我们的训练目标就是最小化loss 值
1.2.4 反向传播和更新参数
当计算出一次前向传播的loss值之后,可进行反向传播计算梯度,以此来更新参数。在pytorch中对 loss 调用 backward 函数即可。backward 函数属于 torch.autograd 函数库,在深度学习过程中进行反向传播、计算输出变量关于输入变量的梯度。最后要做的事情就是更新神经网络的参数,简单的规则就是随机梯度下降,公式如下:
weight = weight - learning rate x gradient
当然,还有很多不同的更新规则,类似于 SGD、Nesterov-SGD、Adam、RMSProp 等,为了让这些可行、PyTorch建立了一个 torchoptim 包,调用它可以实现上述任意一种优化器,代码如下
# 1.2.4 反向传播与更新参数
#创建优化器
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() # 消除累积梯度
out = net(image)
loss = criterion(out, label)
loss.backward()
optimizer.step() # 更新参数
在训练开始前,先定义好优化器 optim.SGD 和损失函数nn.CrossEntropyLoss,当开始选代进行训练时,将数据进行输人,得到的输出用来和实际标签计算 loss,对 loss 进行反向传播,最后通过优化器更新参数。其中需要对优化器进行消除梯度,因为在使用backward 函数时,梯度是被累而不是被替换掉的,但是在训练每个 batch(指一批数据)时不需要将两个batch 的梯度混合起来积,所以这里需要对每个batch 设置一遍 zero_grad。
1.3 开始训练
为了方便后续使用模型,可以将训练过程写成一个函数,向该函数传入网络模型、损失函优化器等必要对象后,在MNIST 数据集上进行训练并打印日志观察过程,代码如下:
# 1.3 开始训练
def train(epoch):
net.train() # 设置为training模式
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(1)
调用 train(1)训练一轮的结果如下,可以看出 loss 值不断下降。其中,1 表示训练1 轮数据集直正训练时,则可以训练多轮,比如调用 train(20)表示训练 20轮
打印结果为:
[2, 1] loss: 0.023
[2, 101] loss: 2.297
[2, 201] loss: 2.275
[2, 301] loss: 2.204
[2, 401] loss: 1.681
[2, 501] loss: 0.777
[2, 601] loss: 0.526
[2, 701] loss: 0.418
[2, 801] loss: 0.370
[2, 901] loss: 0.320
进程已结束,退出代码0
对criterion有疑问的可以看看损失函数:【深度学习】一文看尽Pytorch之十九种损失函数_风度78的博客-CSDN博客
1.4 观察模型预测结果
在训练完成以后,为了检验模型的训练结果,可以在测试集上进行验证,通过不同的评估方法来评估。一个分类模型,常见的评估方法是求分类准确率,它能衡量所有类别中预测正确的个数占所有样本的比值,直观且简单,代码如下:
# 1.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))
训练时用的是 train_loader 数据集,测试时就得用另一部分的数据集 test_loader。代码中用到with torch.no_grad0,是为了让模型不进行梯度求导,和 model.eval0具有相同的作用。eval即evaluation 模式,train 即训练模式( 可以看到 1.3 节开始训练时的 model.train0),这两种模式仅当模型中有 Dropout和 BatchNorm 时才会有影响。因为训练时 Dropout 和 BatchNorm都会开启般而言,测试时 Dropout会被关闭,BatchNorm 中的参数也是利用训练时保留的参数,所以测时应进人评估模式。通过数据输入神经网络,得到神经网络的概率输出后,我们需要取最大值对的索引作为预测,这里用到了 torch.max 函数。该函数接收两个输人,一个是数据,另一个是表示要在哪一维度操作,很明显这里输入的是概率值及第二维的 1。返回两个输出,即最大的数值及大值对应的索引。在这里我们并不关心数值多少,所以对该变量用“_”命名,这是常见的对无于变量命名的方式。将预测索引和实际索引进行对比,即可得到准确率。上述代码输出:
Accuracy of the network on the 10000 test images: 91 %
这是在所有类别上进行评估,并无法侧重看出哪个类别预测的好与坏,如果想观察每个类别的预测结果,可以使用如下代码。
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
classes = [i for i in range(10)]
with torch.no_grad():
for data in test_loader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs,1)
c = (predicted == labels).squeeze() #squeeze() 用来压缩为1的维度
for i in range(len(labels)): #对所有labels逐个进行判断
label = labels[i]
class_correct[label] += c[i].item()
class_total[label] += 1
for i in range(10):
print('Accuracy of %5s : %2d %%' % (
classes[i], 100 * class_correct[i] / class_total[i]))
输出结果为:
Accuracy of 0 : 97 %
Accuracy of 1 : 96 %
Accuracy of 2 : 89 %
Accuracy of 3 : 95 %
Accuracy of 4 : 93 %
Accuracy of 5 : 82 %
Accuracy of 6 : 93 %
Accuracy of 7 : 94 %
Accuracy of 8 : 86 %
Accuracy of 9 : 84 %
进程已结束,退出代码0
可以看到每个类别各自的准确率,其中,数字0的准确率最高,为97%;其次是1,准确率为96%;这些数字中最低的准确率是82%,这也说明神经网络对该数据集的学习效果好
我们随机输入6张图片并进行观察实际结果及模型预测结果的情况
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)
1.5 总结
本章首章首先介绍了MNIST数据集,以及利用pytorch导入数据集,接着通过搭建神经网络的步骤(定义神经网络,前向传播,计算损失,反向传播,更新参数)构建了一个简单的图像卷积神经网络,最后对数据集进行训练并进行观察模型预测结果