之前的内容已经学习了如何定义神经网络、计算损失和更新网络的权重。 现在我们可以完整的训练一个分类器。
我们将按顺序执行以下步骤:
torchvision
加载和规范化 CIFAR10 训练和测试数据集通常,当我们必须处理图像、文本、音频或视频数据时, 可以使用将数据加载到 numpy 数组中的标准 python 包。 然后将此数组转换为 torch.*Tensor
类型。
例如针对计算机视觉可以使用名为 torchvision
的包,它具有用于常见数据集的数据加载器,比如ImageNet、CIFAR10、MNIST 数据集等。和用于图像的数据转换器, torchvision.datasets
和 torch.utils.data.DataLoader
这为我们提供了极大的便利并避免了编写模板代码。 对于本次代码,将使用 CIFAR10 数据集。 它有:'飞机','汽车','鸟','猫','鹿', “狗”、“青蛙”、“马”、“船”、“卡车”十个类别。 CIFAR-10 中的图像尺寸是 3x32x32,即 32x32 像素大小的 3 通道彩色图像。
使用 torchvision
,加载 CIFAR10
import torch
import torchvision
import torchvision.transforms as transforms
torchvision 数据集的输出是范围 [0, 1] 的 PILImage图像。 最后我们将它标准化转换为范围 [-1, 1] 的张量。
transform = transforms.Compose( #图像预处理包。一般用Compose把多个步骤整合到一起:
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])#用均值和标准差对张量图像进行归一化
batch_size = 4
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size,
shuffle=True, num_workers=0)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size,
shuffle=False, num_workers=0)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')#定义其中的类别
其中ToTensor()能够把灰度范围从0-255变换到0-1之间,ToTensor()
将 shape
为(H, W, C)
的nump.ndarray
或 img
转为 shape
为 (C, H, W)
的 tensor
,再将每一个数值归一化到[0,1]
,其归一化方法比较简单,直接除以255即可。
transforms.Normalize(std=(0.5,0.5,0.5),mean=(0.5,0.5,0.5)),则其作用就是先将输入归一化到(0,1),再使用公式"(x-mean)/std",将每个元素分布到(-1,1)之间,对每个通道而言,执行image=(image-mean)/std。每个样本图像变成了均值为0 方差为1 的标准正态分布。有时 std 和 mean 中的数据是从imagenet
训练集中抽样算出来的
为了更加直观,让我们展示一些训练图像:
import matplotlib.pyplot as plt #和matlab类似的绘图API,方便用户快速绘制2D图表。
import numpy as np
def imshow(img):
img = img / 2 + 0.5 # 逆标准化,Normalize的逆过程,img = img * std + mean
npimg = img.numpy() # 将张量转化成numpy的ndarray类型
plt.imshow(np.transpose(npimg, (1, 2, 0)))#ndarray对象的形状(C, H, W)转换为(H, W, C)
plt.show()
# 获取一组随机训练图片,#获取数据流中的数据元素
dataiter = iter(trainloader)
images, labels = dataiter.next()#得到图像数据和对应标签
# 展示图片
imshow(torchvision.utils.make_grid(images))##组成图像的网络,其实就是将多张图片组合成一张图片。
# 输出对应标签
print(' '.join(f'{classes[labels[j]]:5s}' for j in range(batch_size)))
#join()返回被子字符串连接的字符串。5s表示空格大小
其中transpose()函数的实质作用是改变序列。根据维度(shape)索引决定的,就是调换数组的行列值的索引值。
Python numpy.transpose 详解_November丶Chopin的博客-CSDN博客_numpy transpose
输出结果: 标签:car frog truck deer
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))#卷积+激活+池化
x = self.pool(F.relu(self.conv2(x)))
x = torch.flatten(x, 1) # 展平层、展平除批次外的所有尺寸
x = F.relu(self.fc1(x))#全连接层+激活函数
x = F.relu(self.fc2(x))
x = self.fc3(x)#全连接层
return x
net = Net()
print(net)
输出结果:
Net(
(conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
(pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear(in_features=400, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
使用具有动量的分类交叉熵损失函数和 随机梯度下降SGD优化器。
import torch.optim as optim
criterion = nn.CrossEntropyLoss()#定义交叉熵损失函数
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
其中torch.optim是一个实现了各种优化算法的库。SGD就是optim中的一个算法(优化器):随机梯度下降算法。torch.optim.SGD参数学习率lr、动量momentum、权重衰减weight_decay的解析_jjw_zyfx的博客-CSDN博客_pytorch sgd参数
第一个参数 net.parameters() 包括权重w,和偏置b等是神经网络中的参数,也是SGD优化的重点。
第二个参数lr是学习率。sgd中的学习率lr的作用可以理解为: p ′ = p − l r ∗ d p 其中p就是模型中的参数比如:权重(w), 偏置(b)。dp 就是对p的一阶求导, lr 即学习率, p ′为p的另一种形式,用来替换上一次的p。
第三个参数momentum是冲量,在普通的梯度下降法x+=v中,每次x的更新量v为v=−dx∗lr,其中dx为目标函数func(x)对x的一阶导数,当使用冲量时,把每次x的更新量v考虑为本次的梯度下降量−dx∗lr与上次x的更新量v乘上介于[0,1][0,1]因子momentum的和,即v′=−dx∗lr+v∗momemtum
遍历我们的数据迭代器,并将输入提供给网络和优化器。
for epoch in range(2): # 在数据集上循环多次
running_loss = 0.0
for i, data in enumerate(trainloader, 0):# data中包含输入图像张量inputs,张量标签labels
inputs, labels = data # 获取输入;数据是[输入、标签]的列表
# 将优化器参数梯度归零
optimizer.zero_grad()
# 前向传播 + 计算损失 + 反向传播 + 优化
outputs = net(inputs) # 输入图像张量进网络,得到输出张量outputs
loss = criterion(outputs, labels) # 利用网络的输出outputs和标签labels计算损失值
loss.backward() # 反向传播+参数更新,是标准代码的标准流程
optimizer.step()
# 打印轮次和损失值
running_loss += loss.item()
if i % 2000 == 1999: # 每2000小批量打印一次
print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.3f}')
running_loss = 0.0
print('Finished Training')
输出结果:
[1, 2000] loss: 2.253
[1, 4000] loss: 1.901
[1, 6000] loss: 1.699
[1, 8000] loss: 1.564
[1, 10000] loss: 1.507
[1, 12000] loss: 1.454
[2, 2000] loss: 1.363
[2, 4000] loss: 1.347
[2, 6000] loss: 1.313
[2, 8000] loss: 1.297
[2, 10000] loss: 1.251
[2, 12000] loss: 1.288
Finished Training
快速保存我们训练好的模型(参数):
PATH = './cifar_net.pth'#首先设定模型的保存路径
torch.save(net.state_dict(), PATH)#保存模型的状态字典
我们已经在训练数据集上训练了网络 2 次。 但是我们需要检查网络是否学到了任何东西。通过预测神经网络的类标签来检查这一点输出,并根据实际情况对其进行检查。 如果预测是正确,则将样本添加到正确预测列表中。
dataiter = iter(testloader)#获取一批数据流
images, labels = dataiter.next()
# 打印原始图像, 展示来自测试集的图像。
imshow(torchvision.utils.make_grid(images))
#打印真实的数据标签
print('GroundTruth: ', ' '.join(f'{classes[labels[j]]:5s}' for j in range(4)))
# 首先实例化模型的类的对象
net = Net()
net.load_state_dict(torch.load(PATH))# 加载训练阶段保存好的模型的状态字典
outputs = net(images)# 利用模型对图片进行预测
_, predicted = torch.max(outputs, 1)# 共有十个类别,采用模型计算出的概率最大的作为预测的类别
print('Predicted: ', ' '.join(f'{classes[predicted[j]]:5s}'# 打印预测标签的结果
for j in range(4)))
输出结果: GroundTruth: cat ship ship plane
Predicted: cat ship ship ship
现在看看网络在整个数据集上的表现。
correct = 0
total = 0
# 因为没有训练,所以我们不需要计算输出的梯度
with torch.no_grad():
for data in testloader:
images, labels = data
# 通过网络运行图像计算输出
outputs = net(images)
# 我们选择概率最高的类作为预测
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)#测试数据集的样本个数
correct += (predicted == labels).sum().item()#预测正确个数
print(f'Accuracy of the network on the 10000 test images: {100 * correct // total} %')
输出结果:
Accuracy of the network on the 10000 test images: 54 %
分析结果:对于拥有10个类别的数据集,随机猜测的准确率是10%,模型达到了54%,说明模型学到了真实的东西。为了更细致的看一下模型在哪些类上表现得更好,在哪些类上表现得更差,我们分类别的进行准确率的计算
# 准备计算每个类别的预测
correct_pred = {classname: 0 for classname in classes}#字典生成器
total_pred = {classname: 0 for classname in classes}#创建包含10个类别的字典
# 同样不需要梯度
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predictions = torch.max(outputs, 1)
# 收集每个类别的正确预测
for label, prediction in zip(labels, predictions):
if label == prediction:
correct_pred[classes[label]] += 1#给此标签对应的类别加1
total_pred[classes[label]] += 1#此类别的总数加1
# 打印每一个类别的准确率
for classname, correct_count in correct_pred.items():
accuracy = 100 * float(correct_count) / total_pred[classname]
print(f'Accuracy for class: {classname:5s} is {accuracy:.1f} %')
其中 zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
输出结果:
Accuracy of plane : 100 %
Accuracy of car : 100 %
Accuracy of bird : 100 %
Accuracy of cat : 100 %
Accuracy of deer : 100 %
Accuracy of dog : 100 %
Accuracy of frog : 100 %
Accuracy of horse : 100 %
Accuracy of ship : 100 %
Accuracy of truck : 100 %
就像如何将张量转移到 GPU 上一样,转移神经网络到GPU上。如果有GPU,让我们首先将我们的设备定义为第一个可见的 cuda 设备 可用的 CUDA:
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
# 假设我们在CUDA机器上,这应该打印CUDA设备:
print(device)
假设 device
是一个 CUDA 设备。当训练模型的时候,只需要将模型转移到GPU上,同时将输入的图片和标签页转移到GPU上即可。然后这些方法将递归地遍历所有模块并转换它们的 CUDA 张量的参数和缓冲区:
net.to(device)#将模型转移到GPU上
inputs, labels = data[0].to(device), data[1].to(device)
#将输入的图片张量和标签张量转移到GPU上