这是Stanford AI 课程 CS224N之前的PyTorch教程,初学的同学可以用这个快速深入了解PyTorch。本文通过一个简单但完整的示例,讲解了从数据准备、模型创建、到训练和预测,如何使用PyTorch作为解决框架的全过程。我认为很值得翻译出来分享给大家。本译文一共四篇,可在我的博客目录里查找其他几篇。如果你想看原文,地址如下:CS224N_PyTorch_Tutorialhttps://web.stanford.edu/class/cs224n/materials/CS224N_PyTorch_Tutorial.html译者注:原文的所有代码实验都是在jupyter notebook中完成的,如果你不了解安装和使用过程,可以参考我另一篇博文的开篇介绍:
斯坦福CS224N课程_Python简明复习教程_cs224n-python-review-code-updated (第一部分)_放肆荒原的博客-CSDN博客
CS224N: PyTorch Tutorial (Winter '21)
作者:Dilara Soylu
在本notebook中,我们将对PyTorch进行基本介绍,并完成一项Toy NLP任务。在编写本notebook时使用了以下资源:
- Matt Lamm编写的“单词窗口分类”教程notebook,从2020年冬季开始由CS224N提供。
- PyTorch官方文档:Soumith Chintala的深度学习与PyTorch:60分钟闪电教程
- PyTorch教程notebook,构建基本生成对抗网络(GANs)|Coursera,由Sharon Zhou编写,在Coursera上提供
非常感谢Angelica Sun和John Hewitt的反馈。
介绍
PyTorch是一个机器学习框架,在学术界和各行业都有广泛的应用。PyTorch一开始是作为TensorFlow的更灵活的替代品出现的,这是另一种流行的机器学习框架。PyTorch一经发布,就以其用户友好性吸引了用户:与在TensorFlow中执行操作之前定义静态图不同,PyTorch允许用户在执行操作时定义其操作,TensorFlow在其后续版本中也集成了该方法。虽然TensorFlow在业界更受欢迎,但PyTorch常常是研究人员首选的机器学习框架。如果你想了解更多关于两者之间的差异,你可以查看这篇博客文章。
现在我们已经对PyTorch的背景有了足够的了解,让我们从将其导入notebook开始。要安装PyTorch,您可以按照此处的说明进行操作。或者,您可以使用Google Colab打开此笔记本,它的基本内核中已经安装了PyTorch。完成安装过程后,运行以下单元格:
In [1]:
import torch
import torch.nn as nn
# 引入pprint, 使打印语句更美观的模块
import pprint
pp = pprint.PrettyPrinter()
一切就绪,我们开始吧!
Tensors(张量)
张量是PyTorch中最基本的构造块。张量类似于矩阵,但它们有额外的属性,可以表示更高的维度。例如,两面都有256个像素的正方形图像可以用3x256x256张量表示,其中前3个维度表示颜色通道,即红色、绿色和蓝色。
Tensor Initialization(张量初始化)
在PyTorch中有几种实例化张量的方法,我们将在下面逐一介绍。
从Python列表中初始化
我们可以从Python列表(list)中初始化张量(tensor),该列表可以包含子列表(sublist)。当我们使用torch.tensor()时,PyTorch将自动推断维度和数据类型。
In [2]:
# Initialize a tensor from a Python List
# 从Python列表初始化张量
data = [
[0, 1],
[2, 3],
[4, 5]
]
x_python = torch.tensor(data)
# Print the tensor
# 打印张量
x_python
Out [2]:
tensor([[0, 1],
[2, 3],
[4, 5]])
我们还可以使用可选的dtype参数调用torch.tensor(),该参数将设置数据类型。一些有用数据类型需要熟悉一下:torch.bool、torch.float和torch.long。
In [3]:
# We are using the dtype to create a tensor of particular type
# 我们使用dtype来创建特定类型的张量
x_float = torch.tensor(data, dtype=torch.float)
x_float
Out [3]:
tensor([[0., 1.],
[2., 3.],
[4., 5.]])
In [4]:
# We are using the dtype to create a tensor of particular type
# 我们使用dtype来创建特定类型的张量
x_bool = torch.tensor(data, dtype=torch.bool)
x_bool
Out [4]:
tensor([[False, True],
[ True, True],
[ True, True]])
我们还可以使用float()、long()等方法在指定的数据类型中获得相同的张量。
In [5]:
x_python.float()
Out [5]:
tensor([[0., 1.],
[2., 3.],
[4., 5.]])
我们还可以使用tensor.FloatTensor、tensor.LongTensor、tensor.Tensor类来实例化特定类型的张量。LongTensor
在NLP中尤其重要,因为许多处理索引的方法都要求将索引作为LongTensor传递,LongTensor是64位整型数。
In [6]:
# `torch.Tensor` defaults to float
# Same as torch.FloatTensor(data)
# `torch.Tensor`默认是浮点数型,与torch.FloatTensor(data)相同
x = torch.Tensor(data)
x
Out [6]:
tensor([[0., 1.],
[2., 3.],
[4., 5.]])
从NumPy数组初始化
我们还可以从NumPy数组初始化张量。
In [7]:
import numpy as np
# Initialize a tensor from a NumPy array
# 从NumPy数组初始化张量
ndarray = np.array(data)
x_numpy = torch.from_numpy(ndarray)
# Print the tensor
# 打印张量
x_numpy
Out [7]:
tensor([[0, 1],
[2, 3],
[4, 5]])
从张量初始化
我们还可以使用以下方法从另一个张量初始化一个张量:
- torch.ones_like(old_tensor):初始化1的张量。
- torch.zeros_like(old_tensor):初始化0的张量。
- torch.rand_like(old_tensor):初始化一个张量,其中所有元素都从0到1之间的均匀分布中采样。
- torch.randn_like(old_tensor):初始化一个张量,其中所有元素都从正态分布中采样。
所有这些方法都保留了传入的原始张量的张量属性,例如形状和设备,我们将在后面介绍。
In [8]:
# Initialize a base tensor
# 初始化基张量
x = torch.tensor([[1., 2.], [3., 4.]])
x
Out [8]:
tensor([[1., 2.],
[3., 4.]])
In [9]:
# Initialize a tensor of 0s
# 初始化0的张量
x_zeros = torch.zeros_like(x)
x_zeros
Out [9]:
tensor([[0., 0.],
[0., 0.]])
In [10]:
# Initialize a tensor of 1s
# 初始化1的张量
x_ones = torch.ones_like(x)
x_ones
Out [10]:
tensor([[1., 1.],
[1., 1.]])
In [11]:
# Initialize a tensor where each element is sampled from a uniform distribution
# between 0 and 1
# 初始化张量,其中每个元素从0和1之间的均匀分布中采样
x_rand = torch.rand_like(x)
x_rand
Out [11]:
tensor([[0.8979, 0.7173],
[0.3067, 0.1246]])
In [12]:
# Initialize a tensor where each element is sampled from a normal distribution
# 初始化张量,其中每个元素从正态分布采样
x_randn = torch.randn_like(x)
x_randn
Out [12]:
tensor([[-0.6749, -0.8590],
[ 0.6666, 1.1185]])
通过指定形状初始化
我们还可以通过指定张量的形状来实例化它们(稍后我们将更详细地介绍)。我们可以使用的方法如下所述:
- torch.zeros()
- torch.ones()
- torch.rand()
- torch.randn()
In [13]:
# Initialize a 2x3x2 tensor of 0s
# 初始化一个2x3x2的全0张量
shape = (4, 2, 2)
x_zeros = torch.zeros(shape) # x_zeros = torch.zeros(4, 3, 2) 是另一种做法
x_zeros
Out [13]:
tensor([[[0., 0.],
[0., 0.]],
[[0., 0.],
[0., 0.]],
[[0., 0.],
[0., 0.]],
[[0., 0.],
[0., 0.]]])
用torch.arange()初始化
我们还可以使用torch.arange(end)创建一个张量,它返回一个1-D张量,元素范围从0到end-1。我们可以使用可选的开始和步骤参数来创建具有不同范围的张量。
In [14]:
# Create a tensor with values 0-9
# 创建一个值为0-9的张量
x = torch.arange(10)
x
Out [14]:
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Tensor张量属性
张量有一些对我们来说很重要的性质。即形状(shape)和设备(device)属性。
数据类型
dtype属性允许我们查看张量的数据类型。
In [15]:
# Initialize a 3x2 tensor, with 3 rows and 2 columns
# 用3行2列初始化一个3x2的张量
x = torch.ones(3, 2)
x.dtype
Out [15]:
torch.float32
形状(Shape)
形状(Shape)属性告诉我们张量的形状。这可以帮助我们确定张量是多少维,以及每个维中存在多少元素。
In [16]:
# Initialize a 3x2 tensor, with 3 rows and 2 columns
# 用3行2列初始化一个3x2的张量
x = torch.Tensor([[1, 2], [3, 4], [5, 6]])
x
Out [16]:
tensor([[1., 2.],
[3., 4.],
[5., 6.]])
In [17]:
# Print out its shape
# Same as x.size()
# 打印它的shape,与x.size()是一样的
x.shape
Out [17]:
torch.Size([3, 2])
In [18]:
# Print out the number of elements in a particular dimension
# 0th dimension corresponds to the rows
# 打印出特定维度中的元素数,第0维度对应于行
x.shape[0]
Out [18]:
3
我们还可以使用size()方法获取特定维度的大小。
In [19]:
# Get the size of the 0th dimension
# 获取第0维度的大小
x.size(0)
Out [19]:
3
我们可以用view()方法改变张量的形状。
In [20]:
# Example use of view()
# x_view shares the same memory as x, so changing one changes the other
# view()的示例用法
# x_view与x共享相同的内存,因此更改其中一个会更改另一个
x_view = x.view(3, 2)
x_view
Out [20]:
tensor([[1., 2.],
[3., 4.],
[5., 6.]])
In [21]:
# We can ask PyTorch to infer the size of a dimension with -1
# 我们可以让PyTorch用 -1 来推断维度的大小
x_view = x.view(-1, 3)
x_view
Out [21]:
tensor([[1., 2., 3.],
[4., 5., 6.]])
我们也可以将 torch.reshape() 方法用于类似的目的。 reshape() 和 view() 之间有一个细微的区别:view() 要求数据连续存储在内存中。 您可以参考此 StackOverflow 答案以获取更多信息。 简单来说,连续意味着我们的数据在内存中的布局方式与我们从中读取元素的方式相同。 发生这种情况是因为某些方法,例如 transpose() 和 view(),实际上并没有改变我们的数据在内存中的存储方式。 它们只是改变了关于输出张量的元信息,所以当我们使用它时,我们会按照我们期望的顺序看到元素。
如果数据是连续存储的,reshape() 在内部调用 view(),如果不是,则返回一个副本。 这里的区别对于基本张量来说不是很重要,但是如果您执行的操作使数据的底层存储不连续(例如进行转置),则使用 view() 时会遇到问题。 如果您想将张量存储在内存中的方式与其使用方式相匹配,您可以使用 contiguous() 方法。
In [22]:
# Change the shape of x to be 3x2
# x_reshaped could be a reference to or copy of x
#将x的形状更改为3x2,x_reshaped可以是对x的引用或复制
x_reshaped = torch.reshape(x, (2, 3))
x_reshaped
Out [22]:
tensor([[1., 2., 3.],
[4., 5., 6.]])
我们可以使用 torch.unsqueeze(x, dim) 函数将大小为 1 的维度添加到 dim 中,其中 x 是张量。 我们也可以使用对应的 torch.squeeze(x),它去除了大小为 1 的维度。
In [23]:
# Initialize a 5x2 tensor, with 5 rows and 2 columns
# 用5行2列初始化一个5x2的张量
x = torch.arange(10).reshape(5, 2)
x
Out [23]:
tensor([[0, 1],
[2, 3],
[4, 5],
[6, 7],
[8, 9]])
In [24]:
# Add a new dimension of size 1 at the 1st dimension
# 在第 1 个维度添加一个大小为 1 的新维度
x = x.unsqueeze(1)
x.shape
Out [24]:
torch.Size([5, 1, 2])
In [25]:
# Squeeze the dimensions of x by getting rid of all the dimensions with 1 element
# 通过删除所有具有 1 个元素的维度来压缩 x 的维度?????
x = x.squeeze()
x.shape
Out [25]:
torch.Size([5, 2])
如果我们想获取张量中元素的总数,我们可以使用 numel() 方法。
In [26]:
x
Out [26]:
tensor([[0, 1],
[2, 3],
[4, 5],
[6, 7],
[8, 9]])
In [27]:
# Get the number of elements in tensor.
# 获取张量中的元素数。
x.numel()
Out [27]:
10
设备(Device)
设备属性告诉 PyTorch 在哪里存储我们的张量。 张量的存储位置决定了哪个设备(GPU 或 CPU)将处理涉及它的计算。 我们可以用设备属性找到张量的设备。
In [28]:
# Initialize an example tensor
# 初始化一个示例张量
x = torch.Tensor([[1, 2], [3, 4]])
x
Out [28]:
tensor([[1., 2.],
[3., 4.]])
In [29]:
# Get the device of the tensor
# 获取张量的设备
x.device
Out [29]:
device(type='cpu')
我们可以使用 to(device) 方法将张量从一个设备移动到另一个设备。
In [30]:
# Check if a GPU is available, if so, move the tensor to the GPU
# 检查GPU是否可用,如果可用,将张量移动到GPU
if torch.cuda.is_available():
x.to('cuda')
张量索引(Tensor Indexing)
在 PyTorch 中,我们可以索引张量,类似于 NumPy。
In [31]:
# Initialize an example tensor
# 初始化一个示例张量
x = torch.Tensor([
[[1, 2], [3, 4]],
[[5, 6], [7, 8]],
[[9, 10], [11, 12]]
])
x
Out [31]:
tensor([[[ 1., 2.],
[ 3., 4.]],
[[ 5., 6.],
[ 7., 8.]],
[[ 9., 10.],
[11., 12.]]])
In [32]:
x.shape
Out [32]:
torch.Size([3, 2, 2])
In [33]:
# Access the 0th element, which is the first row
# 访问第 0 个元素,即第一行
x[0] # 等价于 to x[0, :]
Out [33]:
tensor([[1., 2.],
[3., 4.]])
我们还可以使用 : 索引到多个维度。
In [34]:
# Get the top left element of each element in our tensor
# 获取张量中每个元素的左上角元素
x[:, 0, 0]
Out [34]:
tensor([1., 5., 9.])
我们还可以访问每个维度中的任意元素。
In [35]:
# Print x again to see our tensor
# 再次打印 x 以查看张量
x
Out [35]:
tensor([[[ 1., 2.],
[ 3., 4.]],
[[ 5., 6.],
[ 7., 8.]],
[[ 9., 10.],
[11., 12.]]])
In [36]:
# Let's access the 0th and 1st elements, each twice
# 让我们访问第 0 个和第 1 个元素,每个元素两次
i = torch.tensor([0, 0, 1, 1])
x[i]
Out [36]:
tensor([[[1., 2.],
[3., 4.]],
[[1., 2.],
[3., 4.]],
[[5., 6.],
[7., 8.]],
[[5., 6.],
[7., 8.]]])
In [37]:
# Let's access the 0th elements of the 1st and 2nd elements
# 让我们访问第 1 个和第 2 个元素的第 0 个元素
i = torch.tensor([1, 2])
j = torch.tensor([0])
x[i, j]
Out [37]:
tensor([[ 5., 6.],
[ 9., 10.]])
我们可以使用 item() 从张量中获取 Python 标量值。
In [38]:
x[0, 0, 0]
Out [38]:
tensor(1.)
In [39]:
x[0, 0, 0].item()
Out [39]:
1.0
前往 第二部分