注意
单击此处下载完整的示例代码
简介 ||张量 ||Autograd ||建筑模型 ||TensorBoard 支持 ||训练模型 ||模型理解
PyTorch 简介¶
创建时间: 2021年11月30日 |上次更新时间:2024 年 1 月 19 日 |上次验证: Nov 05, 2024
请跟随下面的视频或在 youtube 上观看。
PyTorch 张量¶
按照 03:50 开始的视频进行操作。
首先,我们将导入 pytorch。
import torch
让我们看看一些基本的张量操作。首先,只是其中的几个 创建张量的方法:
z = torch.zeros(5, 3)
print(z)
print(z.dtype)
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
torch.float32
上面,我们创建了一个填充了零的 5x3 矩阵,并查询其数据类型 找出零是 32 位浮点数,即 默认的 PyTorch。
如果你想要整数怎么办?您始终可以覆盖 违约:
i = torch.ones((5, 3), dtype=torch.int16)
print(i)
tensor([[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]], dtype=torch.int16)
你可以看到,当我们更改默认值时,张量会很有帮助 在打印时报告此情况。
随机初始化学习权重是很常见的,通常使用 PRNG 的特异性种子,用于结果的可重复性:
torch.manual_seed(1729)
r1 = torch.rand(2, 2)
print('A random tensor:')
print(r1)
r2 = torch.rand(2, 2)
print('\nA different random tensor:')
print(r2) # new values
torch.manual_seed(1729)
r3 = torch.rand(2, 2)
print('\nShould match r1:')
print(r3) # repeats values of r1 because of re-seed
A random tensor:
tensor([[0.3126, 0.3791],
[0.3087, 0.0736]])
A different random tensor:
tensor([[0.4216, 0.0691],
[0.2332, 0.4047]])
Should match r1:
tensor([[0.3126, 0.3791],
[0.3087, 0.0736]])
PyTorch 张量直观地执行算术运算。的张量 可以添加、乘以相似的形状等。使用标量的操作 分布在 Tensor 上:
ones = torch.ones(2, 3)
print(ones)
twos = torch.ones(2, 3) * 2 # every element is multiplied by 2
print(twos)
threes = ones + twos # addition allowed because shapes are similar
print(threes) # tensors are added element-wise
print(threes.shape) # this has the same dimensions as input tensors
r1 = torch.rand(2, 3)
r2 = torch.rand(3, 2)
# uncomment this line to get a runtime error
# r3 = r1 + r2
tensor([[1., 1., 1.],
[1., 1., 1.]])
tensor([[2., 2., 2.],
[2., 2., 2.]])
tensor([[3., 3., 3.],
[3., 3., 3.]])
torch.Size([2, 3])
以下是可用数学运算的一小部分示例:
r = (torch.rand(2, 2) - 0.5) * 2 # values between -1 and 1
print('A random matrix, r:')
print(r)
# Common mathematical operations are supported:
print('\nAbsolute value of r:')
print(torch.abs(r))
# ...as are trigonometric functions:
print('\nInverse sine of r:')
print(torch.asin(r))
# ...and linear algebra operations like determinant and singular value decomposition
print('\nDeterminant of r:')
print(torch.det(r))
print('\nSingular value decomposition of r:')
print(torch.svd(r))
# ...and statistical and aggregate operations:
print('\nAverage and standard deviation of r:')
print(torch.std_mean(r))
print('\nMaximum value of r:')
print(torch.max(r))
A random matrix, r:
tensor([[ 0.9956, -0.2232],
[ 0.3858, -0.6593]])
Absolute value of r:
tensor([[0.9956, 0.2232],
[0.3858, 0.6593]])
Inverse sine of r:
tensor([[ 1.4775, -0.2251],
[ 0.3961, -0.7199]])
Determinant of r:
tensor(-0.5703)
Singular value decomposition of r:
torch.return_types.svd(
U=tensor([[-0.8353, -0.5497],
[-0.5497, 0.8353]]),
S=tensor([1.1793, 0.4836]),
V=tensor([[-0.8851, -0.4654],
[ 0.4654, -0.8851]]))
Average and standard deviation of r:
(tensor(0.7217), tensor(0.1247))
Maximum value of r:
tensor(0.9956)
关于 PyTorch 张量的强大功能,还有很多内容需要了解。 包括如何设置它们以在 GPU 上进行并行计算 - 我们将 在另一个视频中将更深入地介绍。
PyTorch 模型¶
按照 10:00 开始的视频进行操作。
我们来谈谈如何在 PyTorch 中表达模型
import torch # for all things PyTorch
import torch.nn as nn # for torch.nn.Module, the parent object for PyTorch models
import torch.nn.functional as F # for the activation function
图:LeNet-5
上图是 LeNet-5 的示意图,LeNet-5 是最早的卷积神经网络之一 nets,也是深度学习爆炸式增长的驱动力之一。它是 用于读取手写数字的小图像(MNIST 数据集), 并正确分类图像中表示的数字。
以下是其工作原理的简化版本:
C1 层是一个卷积层,这意味着它扫描输入 image 来了解它在训练过程中学到的特征。它输出一个 它在图像中看到了它学习的每个特征。这 “activation map” 在 S2 层中进行下采样。
C3 层是另一个卷积层,这次扫描 C1 的 功能组合的激活映射。它还发出了 描述这些特征的空间位置的激活图 组合,在 S4 层中进行缩减采样。
最后,末尾的全连接层 F5、F6 和 OUTPUT, 是采用最终激活映射的分类器,而 将其分类为表示 10 位数字的 10 个箱中的一个。
我们如何用代码表达这个简单的神经网络呢?
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
# 1 input image channel (black & white), 6 output channels, 5x5 square convolution
# kernel
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120) # 5*5 from image dimension
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# Max pooling over a (2, 2) window
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# If the size is a square you can only specify a single number
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))
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:] # all dimensions except the batch dimension
num_features = 1
for s in size:
num_features *= s
return num_features
查看此代码,您应该能够发现一些结构 与上图的相似之处。
这演示了典型 PyTorch 模型的结构:
它继承自 - 模块可以嵌套 - 事实上, 甚至 和 Layer 类也继承自 。
torch.nn.Module
Conv2d
Linear
torch.nn.Module
模型将具有一个函数,它在其中实例化 它的 layers,并加载它可能的任何数据工件 需要(例如,NLP 模型可能会加载词汇表)。
__init__()
模型将具有函数。这是实际的 计算发生:输入通过网络层传递 和各种函数来生成输出。
forward()
除此之外,您可以像构建任何其他 Model 类一样构建 Model 类 Python 类,添加您需要的任何属性和方法 支持模型的计算。
让我们实例化此对象并通过它运行示例输入。
net = LeNet()
print(net) # what does the object tell us about itself?
input = torch.rand(1, 1, 32, 32) # stand-in for a 32x32 black & white image
print('\nImage batch shape:')
print(input.shape)
output = net(input) # we don't call forward() directly
print('\nRaw output:')
print(output)
print(output.shape)
LeNet(
(conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
(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)
)
Image batch shape:
torch.Size([1, 1, 32, 32])
Raw output:
tensor([[ 0.0898, 0.0318, 0.1485, 0.0301, -0.0085, -0.1135, -0.0296, 0.0164,
0.0039, 0.0616]], grad_fn=<AddmmBackward0>)
torch.Size([1, 10])
上面发生了一些重要的事情:
首先,我们实例化类,然后打印对象。的子类将报告它所具有的图层
created 及其形状和参数。这可以提供一个方便的
模型的概述(如果要获取模型的处理要点)。LeNet
net
torch.nn.Module
在此之下,我们创建一个虚拟输入,表示 32x32 图像,其中 1 color 通道。通常,您会加载图像拼贴并将其转换为 此形状的张量。
您可能已经注意到我们的张量还有一个额外的维度 - batch
尺寸。PyTorch 模型假定它们正在处理批量数据
- 例如,一批 16 个图像拼贴的形状为 .由于我们只使用一个图像,因此我们创建一个批处理
的 1 个形状为 .(16, 1, 32, 32)
(1, 1, 32, 32)
我们通过调用函数来请求模型进行推理:。此调用的输出表示模型的
输入表示特定数字的置信度。(由于这个
实例的 instance 还没有学到任何东西,我们不应该期望
以查看输出中的任何信号。看 的形状 ,我们
可以看到它还有一个 batch 维度,其大小应该
始终匹配 Input Batch 维度。如果我们传入了 input
batch 的 16 个实例,其形状为 。net(input)
output
output
(16, 10)
数据集和 Dataloader¶
按照 14:00 开始的视频进行操作。
下面,我们将演示如何使用一个可立即下载的 来自 TorchVision 的开放访问数据集,如何转换 模型的消耗,以及如何使用 DataLoader 为批次提供 的数据添加到模型中。
我们需要做的第一件事是将输入的图像转换为 PyTorch 张量。
#%matplotlib inline
import torch
import torchvision
import torchvision.transforms as transforms
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))])
在这里,我们为输入指定了两个转换:
transforms.ToTensor()
将 Pillow 加载的图像转换为 PyTorch 张量。transforms.Normalize()
调整张量的值,因此 他们的平均值为零,标准差为 1.0。最 激活函数在 x = 0 附近具有最强的梯度,因此 将数据集中在那里可以加快学习速度。 传递给转换的值是 means(第一个元组)和 中图像的 RGB 值的标准差(第二个元组) 数据集。您可以通过运行以下 几行代码:- ```
从 torch.utils.data 导入 ConcatDataset transform = 转换。Compose([转换.ToTensor()]) trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True,transform=transform)
#stack all 将图像一起训练成形状为 #(50000, 3, 32, 32) x = torch.stack([sample[0] for sample in ConcatDataset([trainset])])
#get 每个通道的平均值 均值 = torch.mean(x, dim=(0,2,3)) #tensor([0.4914, 0.4822, 0.4465]) std = torch.std(x, dim=(0,2,3)) #tensor([0.2470, 0.2435, 0.2616])
还有更多可用的转换,包括裁剪、居中、 旋转和反射。
接下来,我们将创建 CIFAR10 数据集的实例。这是一组 32x32 彩色图像图块,代表 10 类对象:6 类动物 (torio、猫、鹿、狗、青蛙、马)和 4 种交通工具(飞机、 汽车、轮船、卡车):
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz
0%| | 0.00/170M [00:00<?, ?B/s]
0%| | 655k/170M [00:00<00:25, 6.55MB/s]
4%|4 | 7.41M/170M [00:00<00:03, 42.4MB/s]
11%|#1 | 19.1M/170M [00:00<00:01, 76.4MB/s]
18%|#7 | 30.3M/170M [00:00<00:01, 90.5MB/s]
23%|##3 | 39.6M/170M [00:00<00:01, 91.2MB/s]
30%|### | 51.3M/170M [00:00<00:01, 100MB/s]
36%|###5 | 61.4M/170M [00:00<00:01, 98.8MB/s]
42%|####2 | 72.3M/170M [00:00<00:00, 102MB/s]
49%|####8 | 83.2M/170M [00:00<00:00, 104MB/s]
55%|#####4 | 93.6M/170M [00:01<00:00, 102MB/s]
62%|######1 | 105M/170M [00:01<00:00, 106MB/s]
68%|######7 | 116M/170M [00:01<00:00, 103MB/s]
74%|#######4 | 127M/170M [00:01<00:00, 104MB/s]
81%|######## | 138M/170M [00:01<00:00, 107MB/s]
87%|########7 | 149M/170M [00:01<00:00, 106MB/s]
94%|#########3| 160M/170M [00:01<00:00, 108MB/s]
100%|##########| 170M/170M [00:01<00:00, 98.3MB/s]
Extracting ./data/cifar-10-python.tar.gz to ./data
注意
运行上面的单元格时,可能需要一点时间 数据集下载。
这是在 PyTorch 中创建 dataset 对象的一个示例。下载
数据集(如上面的 CIFAR-10)是 的子类。 PyTorch 中的类包括
在 TorchVision、Torchtext 和 TorchAudio 中也可下载数据集
作为实用程序数据集类,例如 ,
它将读取一个包含标记图像的文件夹。您也可以创建自己的
的子类。torch.utils.data.Dataset
Dataset
torchvision.datasets.ImageFolder
Dataset
当我们实例化数据集时,我们需要告诉它一些事情:
我们希望数据去到的文件系统路径。
无论我们是否使用此集合进行训练;大多数数据集 将拆分为训练和测试子集。
我们是否要下载数据集(如果尚未下载)。
我们要应用于数据的转换。
数据集准备就绪后,您可以将其提供给 :DataLoader
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)
子类包装对数据的访问,并且专门用于
它提供的数据类型。他们一无所知
数据,但组织 INTO 提供的输入张量
batches 的 Batches 中使用您指定的参数。Dataset
DataLoader
Dataset
在上面的示例中,我们要求 a 为我们提供
4 张图片来自 ,随机化它们的顺序 (),
我们告诉它启动两个 worker 来从磁盘加载数据。DataLoader
trainset
shuffle=True
最好将您服务的批次可视化:DataLoader
import matplotlib.pyplot as plt
import numpy as np
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
# get some random training images
dataiter = iter(trainloader)
images, labels = next(dataiter)
# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Got range [-0.49473685..1.5632443].
ship car horse ship
运行上述单元格应该会显示一条包含四张图像的条带,并且 正确的标签。
训练 PyTorch 模型¶
按照 17:10 开始的视频进行操作。
让我们把所有部分放在一起,训练一个模型:
#%matplotlib inline
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
首先,我们需要训练和测试数据集。如果您尚未这样做, 运行下面的单元格以确保已下载数据集。(可能需要 一分钟。
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
Files already downloaded and verified
Files already downloaded and verified
我们将对 :DataLoader
import matplotlib.pyplot as plt
import numpy as np
# functions to show an image
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
# get some random training images
dataiter = iter(trainloader)
images, labels = next(dataiter)
# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
cat cat deer frog
这就是我们将训练的模型。如果它看起来很熟悉,那是因为它 LeNet 的变体 - 在本视频前面讨论过 - 适用于 3 色图像。
class Net(nn.Module):
def __init__(self):
super(Net, self).__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 = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
我们需要的最后一个要素是损失函数和优化器:
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
如本视频前面所述,损失函数是 模型预测与我们的理想输出相差多远。交叉熵 loss 是像我们这样的分类模型的典型损失函数。
优化器是驱动学习的动力。在这里,我们创建了一个
优化器实现随机梯度下降,则是
简单的优化算法。除了
算法,就像 Learning Rate () 和 Momentum 一样,我们也传入 ,它是所有学习权重的集合
在 Model 中 - 这是 Optimizer 调整的内容。lr
net.parameters()
最后,所有这些都被组装到训练循环中。来吧 运行此单元格,因为它可能需要几分钟才能执行:
for epoch in range(2): # loop over the dataset multiple times
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# get the inputs
inputs, labels = data
# zero the parameter gradients
optimizer.zero_grad()
# forward + backward + optimize
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# print statistics
running_loss += loss.item()
if i % 2000 == 1999: # print every 2000 mini-batches
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
[1, 2000] loss: 2.195
[1, 4000] loss: 1.876
[1, 6000] loss: 1.655
[1, 8000] loss: 1.576
[1, 10000] loss: 1.519
[1, 12000] loss: 1.466
[2, 2000] loss: 1.421
[2, 4000] loss: 1.376
[2, 6000] loss: 1.336
[2, 8000] loss: 1.335
[2, 10000] loss: 1.326
[2, 12000] loss: 1.270
Finished Training
在这里,我们只执行 2 个训练 epoch(第 1 行)——即 2 个 传递训练数据集。每个传递都有一个内部循环,用于迭代训练数据(第 4 行),为批量 转换后的输入图像及其正确的标签。
将梯度归零(第 9 行)是一个重要的步骤。渐变是 在一批中积累;如果我们不为每个批次重置它们,它们 将不断累积,这将提供不正确的梯度值, 使学习变得不可能。
在第 12 行中,我们要求模型对此批次进行预测。在
以下第 (13) 行,我们计算损失 - (模型预测)和(正确输出)之间的差值。outputs
labels
在第 14 行中,我们执行传递并计算梯度
这将指导学习。backward()
在第 15 行中,优化器执行一个学习步骤 - 它使用
gradients 的调用,将学习权重微移到
它认为会减少损失的方向。backward()
循环的其余部分对纪元数 (EPOCH number) 进行一些简单的报告 已完成多少个训练实例,以及收集了什么 loss 在训练循环中。
运行上面的单元格时,您应该会看到如下内容:
[1, 2000] loss: 2.235
[1, 4000] loss: 1.940
[1, 6000] loss: 1.713
[1, 8000] loss: 1.573
[1, 10000] loss: 1.507
[1, 12000] loss: 1.442
[2, 2000] loss: 1.378
[2, 4000] loss: 1.364
[2, 6000] loss: 1.349
[2, 8000] loss: 1.319
[2, 10000] loss: 1.284
[2, 12000] loss: 1.267
Finished Training
请注意,损失是单调递减的,这表明我们的 Model 正在继续提高其在训练数据集上的性能。
作为最后一步,我们应该检查模型是否真的在进行一般学习,而不是简单地 “记住” 数据集。这是 称为过拟合,通常表示数据集太 small (没有足够的示例进行一般学习),或者模型具有 比正确建模数据集所需的学习参数更多。
这就是数据集被拆分为训练和测试子集的原因 - 为了测试模型的通用性,我们要求它对 它尚未训练的数据:
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('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))
Accuracy of the network on the 10000 test images: 54 %
如果您按照 50% 的进度操作,您应该会看到模型大约为 50% 在这一点上是准确的。这并不完全是最先进的,但它确实是 远优于我们期望的随机输出的 10% 准确率。这 表明模型中确实发生了一些常规学习。
脚本总运行时间:(1 分 59.132 秒)