【自然语言处理】PyTorch 基础入门(必备基础知识)

PyTorch 基础实践

PyTorch 基础

在本书中,我们广泛地使用 PyTorch 来实现我们的深度学习模型。PyTorch 是一个开源、社区驱动的深度学习框架。与 Theano、Caffe 和 TensorFlow 不同,PyTorch 实现了一种“基于磁带的自动微分”方法,允许我们动态定义和执行计算图形。这对于调试和用最少的努力构建复杂的模型非常有帮助。


动态 VS 静态计算图 像 Theano、Caffe 和 TensorFlow 这样的静态框架需要首先声明、编译和执行计算图。虽然这会导致非常高效的实现(在生产和移动设置中非常有用),但在研究和开发过程中可能会变得非常麻烦。

像 Chainer、DyNet 和 PyTorch 这样的现代框架实现了动态计算图,从而支持更灵活的命令式开发风格,而不需要在每次执行之前编译模型。

动态计算图在建模 NLP 任务时特别有用,每个输入可能导致不同的图结构。


PyTorch 是一个优化的张量操作库,它提供了一系列用于深度学习的包。

这个库的核心是张量,它是一个包含一些多维数据的数学对象。

0 阶张量就是一个数字,或者标量

一阶张量(一阶张量)是一个数字数组,或者说是一个向量。类似地,二阶张量是一个向量数组,或者说是一个矩阵

因此,张量可以推广为标量的n维数组。

在以下部分中,我们将使用 PyTorch 学习以下内容:

  • 创建张量
  • 操作与张量
  • 索引、切片和与张量连接
  • 用张量计算梯度
  • 使用带有 gpu 的 CUDA 张量

在本节的其余部分中,我们将首先使用 PyTorch 来熟悉各种 PyTorch 操作。我们建议您现在已经安装了 PyTorch 并准备好了 Python 3.5+ 笔记本,并按照本节中的示例进行操作。我们还建议您完成本节后面的练习。

安装 PyTorch

第一步是通过在 pytorch.org 上选择您的系统首选项在您的机器上安装 PyTorch。选择您的操作系统,然后选择包管理器(我们推荐conda/pip),然后选择您正在使用的 Python 版本(我们推荐 3.5+)。这将生成命令,以便您执行安装 PyTorch。在撰写本文时,conda 环境的安装命令如下:

conda install pytorch torchvision -c pytorch


注意:如果您有一个支持 CUDA 的图形处理器单元(GPU),您还应该选择合适的 CUDA 版本。要了解更多细节,请参考 pytorch.org 上的安装说明。


请参考: PyTorch 最新安装教程(2021-07-27)

创建张量

首先,我们定义一个辅助函数,描述(x),它总结了张量x的各种性质,例如张量的类型、张量的维数和张量的内容:

Input[0]:
def describe(x):
  print("Type: {}".format(x.type()))
  print("Shape/size: {}".format(x.shape))
  print("Values: \n{}".format(x))

PyTorch 允许我们使用torch包以许多不同的方式创建张量。创建张量的一种方法是通过指定一个随机张量的维数来初始化它,如例 1-3 所示。

示例 1-3:在 PyTorch 中使用torch.Tensor创建张量

Input[0]:
import torch
describe(torch.Tensor(2, 3))
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 3.2018e-05,  4.5747e-41,  2.5058e+25],
        [ 3.0813e-41,  4.4842e-44,  0.0000e+00]])

我们还可以创建一个张量通过随机初始化值区间上的均匀分布(0,1)或标准正态分布(从均匀分布随机初始化张量,说,是很重要的,正如您将看到的在第三章和第四章),见示例 1-4。

示例 1-4:创建随机初始化的张量

Input[0]:
import torch
describe(torch.rand(2, 3))   # uniform random
describe(torch.randn(2, 3))  # random normal
Output[0]:
Type:  torch.FloatTensor
Shape/size:  torch.Size([2, 3])
Values:
 tensor([[ 0.0242,  0.6630,  0.9787],
        [ 0.1037,  0.3920,  0.6084]])

Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[-0.1330, -2.9222, -1.3649],
        [ 2.3648,  1.1561,  1.5042]])

我们还可以创建张量,所有张量都用相同的标量填充。对于创建 0 或 1 张量,我们有内置函数,对于填充特定值,我们可以使用fill_()方法。

任何带有下划线(_)的 PyTorch 方法都是指就地(in place)操作;也就是说,它在不创建新对象的情况下就地修改内容,如示例 1-5 所示。

示例 1-5:创建填充的张量

Input[0]:
import torch
describe(torch.zeros(2, 3))
x = torch.ones(2, 3)
describe(x)
x.fill_(5)
describe(x)
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.,  0.,  0.],
        [ 0.,  0.,  0.]])

Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 1.,  1.,  1.],
        [ 1.,  1.,  1.]])

Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 5.,  5.,  5.],
        [ 5.,  5.,  5.]])

示例 1-6 演示了如何通过使用 Python 列表以声明的方式创建张量。

示例 1-6:从列表创建和初始化张量

Input[0]:
x = torch.Tensor([[1, 2, 3],  
                  [4, 5, 6]])
describe(x)
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 1.,  2., 3.],
        [ 4.,  5., 6.]])

值可以来自列表(如前面的示例),也可以来自 NumPy 数组。当然,我们也可以从 PyTorch 张量变换到 NumPy 数组。

注意,这个张量的类型是一个double张量,而不是默认的FloatTensor。这对应于 NumPy 随机矩阵的数据类型float64,如示例 1-7 所示。

示例 1-7:从 NumPy 创建和初始化张量

Input[0]:
import torch
import numpy as np
npy = np.random.rand(2, 3)
describe(torch.from_numpy(npy))
Output[0]:
Type: torch.DoubleTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.8360,  0.8836,  0.0545],
        [ 0.6928,  0.2333,  0.7984]], dtype=torch.float64)

在处理使用 Numpy 格式数值的遗留库(legacy libraries)时,在 NumPy 和 PyTorch 张量之间切换的能力变得非常重要。

张量类型和大小

每个张量都有一个相关的类型和大小。使用torch时的默认张量类型。张量构造函数是torch.FloatTensor。但是,可以在初始化时指定张量,也可以在以后使用类型转换方法将张量转换为另一种类型(floatlongdouble等)。有两种方法可以指定初始化类型,一种是直接调用特定张量类型(如FloatTensorLongTensor)的构造函数,另一种是使用特殊的方法torch.tensor,并提供dtype,如例 1-8 所示。

示例 1-8:张量属性

Input[0]:
x = torch.FloatTensor([[1, 2, 3],  
                       [4, 5, 6]])
describe(x)
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 1.,  2.,  3.],
        [ 4.,  5.,  6.]])
Input[1]:
x = x.long()
describe(x)
Output[1]:
Type: torch.LongTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 1,  2,  3],
        [ 4,  5,  6]])
Input[2]:
x = torch.tensor([[1, 2, 3],
                  [4, 5, 6]], dtype=torch.int64)
describe(x)
Output[2]:
Type: torch.LongTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 1,  2,  3],
        [ 4,  5,  6]])
Input[3]:
x = x.float()
describe(x)
Output[3]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 1.,  2.,  3.],
        [ 4.,  5.,  6.]])

我们利用张量对象的形状特性和尺寸方法来获取其尺寸的测量值。访问这些度量的两种方法基本上是相同的。在调试 PyTorch 代码时,检查张量的形状成为必不可少的工具。

张量操作

在创建了张量之后,可以像处理传统编程语言类型(如+-*/)那样对它们进行操作。除了操作符,我们还可以使用.add()之类的函数,如示例 1-9 所示,这些函数对应于符号操作符。

示例 1-9:张量操作:加法

Input[0]:
import torch
x = torch.randn(2, 3)
describe(x)
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.0461,  0.4024, -1.0115],
        [ 0.2167, -0.6123,  0.5036]])
Input[1]:
describe(torch.add(x, x))
Output[1]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.0923,  0.8048, -2.0231],
        [ 0.4335, -1.2245,  1.0072]])
Input[2]:
describe(x + x)
Output[2]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.0923,  0.8048, -2.0231],
        [ 0.4335, -1.2245,  1.0072]])

还有一些运算可以应用到张量的特定维数上。正如您可能已经注意到的,对于 2D 张量,我们将行表示为维度 0,列表示为维度 1,如示例 1-10 所示。

示例 1-10:基于维度的张量操作

Input[0]:
import torch
x = torch.arange(6)
describe(x)
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([6])
Values:
tensor([ 0.,  1.,  2.,  3.,  4.,  5.])
Input[1]:
x = x.view(2, 3)
describe(x)
Output[1]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.,  1.,  2.],
        [ 3.,  4.,  5.]])
Input[2]:
describe(torch.sum(x, dim=0))
Output[2]:
Type: torch.FloatTensor
Shape/size: torch.Size([3])
Values:
tensor([ 3.,  5.,  7.])
Input[3]:
describe(torch.sum(x, dim=1))
Output[3]:
Type: torch.FloatTensor
Shape/size: torch.Size([2])
Values:
tensor([  3.,  12.])
Input[4]:
describe(torch.transpose(x, 0, 1))
Output[4]:
Type: torch.FloatTensor
Shape/size: torch.Size([3, 2])
Values:
tensor([[ 0.,  3.],
        [ 1.,  4.],
        [ 2.,  5.]])

通常,我们需要执行更复杂的操作,包括索引、切片、连接和突变(indexing,slicing,joining and mutation)的组合。与 NumPy 和其他数字库一样,PyTorch 也有内置函数,可以使此类张量操作非常简单。

索引,切片和连接

如果您是一个 NumPy 用户,那么您可能非常熟悉示例 1-11 中所示的 PyTorch 的索引和切片方案。

示例 1-11:切片和索引张量

Input[0]:
import torch
x = torch.arange(6).view(2, 3)
describe(x)
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.,  1.,  2.],
        [ 3.,  4.,  5.]])
Input[1]:
describe(x[:1, :2])
Output[1]:
Type: torch.FloatTensor
Shape/size: torch.Size([1, 2])
Values:
tensor([[ 0.,  1.]])
Input[2]:
describe(x[0, 1])
Output[2]:
Type: torch.FloatTensor
Shape/size: torch.Size([])
Values:
1.0

示例 1-12 演示了 PyTorch 还具有用于复杂索引和切片操作的函数,您可能对有效地访问张量的非连续位置感兴趣。

示例 1-12:复杂索引:张量的非连续索引

Input[0]:
indices = torch.LongTensor([0, 2])
describe(torch.index_select(x, dim=1, index=indices))
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 2])
Values:
tensor([[ 0.,  2.],
        [ 3.,  5.]])
Input[1]:
indices = torch.LongTensor([0, 0])
describe(torch.index_select(x, dim=0, index=indices))
Output[1]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.,  1.,  2.],
        [ 0.,  1.,  2.]])
Input[2]:
row_indices = torch.arange(2).long()
col_indices = torch.LongTensor([0, 1])
describe(x[row_indices, col_indices])
Output[2]:
Type: torch.FloatTensor
Shape/size: torch.Size([2])
Values:
tensor([ 0.,  4.])

注意索引(indices)是一个长张量;这是使用 PyTorch 函数进行索引的要求。我们还可以使用内置的连接函数连接张量,如示例 1-13 所示,通过指定张量和维度。

示例 1-13:连接张量

Input[0]:
import torch
x = torch.arange(6).view(2,3)
describe(x)
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.,  1.,  2.],
        [ 3.,  4.,  5.]])
Input[1]:
describe(torch.cat([x, x], dim=0))
Output[1]:
Type: torch.FloatTensor
Shape/size: torch.Size([4, 3])
Values:
tensor([[ 0.,  1.,  2.],
        [ 3.,  4.,  5.],
        [ 0.,  1.,  2.],
        [ 3.,  4.,  5.]])
Input[2]:
describe(torch.cat([x, x], dim=1))
Output[2]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 6])
Values:
tensor([[ 0.,  1.,  2.,  0.,  1.,  2.],
        [ 3.,  4.,  5.,  3.,  4.,  5.]])
Input[3]:
describe(torch.stack([x, x]))
Output[3]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 2, 3])
Values:
tensor([[[ 0.,  1.,  2.],
         [ 3.,  4.,  5.]],

        [[ 0.,  1.,  2.],
         [ 3.,  4.,  5.]]])

PyTorch 还在张量上实现了高效的线性代数操作,如乘法、逆和迹,如示例 1-14 所示。

示例 1-14:张量上的线性代数:乘法

Input[0]:
import torch
x1 = torch.arange(6).view(2, 3)
describe(x1)
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.,  1.,  2.],
        [ 3.,  4.,  5.]])
Input[1]:
x2 = torch.ones(3, 2)
x2[:, 1] += 1
describe(x2)
Output[1]:
Type: torch.FloatTensor
Shape/size: torch.Size([3, 2])
Values:
tensor([[ 1.,  2.],
        [ 1.,  2.],
        [ 1.,  2.]])
Input[2]:
describe(torch.mm(x1, x2))
Output[2]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 2])
Values:
tensor([[  3.,   6.],
        [ 12.,  24.]])

到目前为止,我们已经研究了创建和操作恒定 PyTorch 张量对象的方法。就像编程语言(如 Python)变量封装一块数据,关于数据的额外信息(如内存地址存储,例如),PyTorch 张量处理构建计算图时所需的记账(bookkeeping)所需构建计算图对机器学习只是在实例化时通过启用一个布尔标志。

张量和计算图

PyTorch 张量类封装了数据(张量本身)和一系列操作,如代数操作、索引操作和整形操作。

然而,1-15 所示的例子,当requires_grad布尔标志被设置为True的张量,记账操作启用,可以追踪的梯度张量以及梯度函数,这两个需要基于促进梯度学习讨论“监督学习范式”。

示例 1-15:为梯度记录创建张量

Input[0]:
import torch
x = torch.ones(2, 2, requires_grad=True)
describe(x)
print(x.grad is None)
Output[0]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 2])
Values:
tensor([[ 1.,  1.],
        [ 1.,  1.]])
True
Input[1]:
y = (x + 2) * (x + 5) + 3
describe(y)
print(x.grad is None)
Output[1]:
Type: torch.FloatTensor
Shape/size: torch.Size([2, 2])
Values:
tensor([[ 21.,  21.],
        [ 21.,  21.]])
True
Input[2]:
z = y.mean()
describe(z)
z.backward()
print(x.grad is None)
Output[2]:
Type: torch.FloatTensor
Shape/size: torch.Size([])
Values:
21.0
False

当您使用requires_grad=True创建张量时,您需要 PyTorch 来管理计算梯度的 bookkeeping 信息。

首先,PyTorch 将跟踪向前传递的值。然后,在计算结束时,使用单个标量来计算向后传递。

反向传递是通过对一个张量使用backward()方法来初始化的,这个张量是由一个损失函数的求值得到的。向后传递为参与向前传递的张量对象计算梯度值。

一般来说,梯度是一个值,它表示函数输出相对于函数输入的斜率。

在计算图形设置中,模型中的每个参数都存在梯度,可以认为是该参数对误差信号的贡献。在 PyTorch 中,可以使用.grad成员变量访问计算图中节点的梯度。优化器使用.grad变量更新参数的值。

到目前为止,我们一直在 CPU 内存上分配张量。在做线性代数运算时,如果你有一个 GPU,那么利用它可能是有意义的。

要利用 GPU,首先需要分配 GPU 内存上的张量。对 gpu 的访问是通过一个名为 CUDA 的专门 API 进行的。

CUDA API 是由 NVIDIA 创建的,并且仅限于在 NVIDIA gpu 上使用。PyTorch 提供的 CUDA 张量对象在使用中与常规 cpu 绑定张量没有区别,除了内部分配的方式不同。

CUDA 张量

PyTorch 使创建这些 CUDA 张量变得非常容易(示例 1-16),它将张量从 CPU 传输到 GPU,同时维护其底层类型。PyTorch 中的首选方法是与设备无关,并编写在 GPU 或 CPU 上都能工作的代码。

在下面的代码片段中,我们首先使用torch.cuda.is_available()检查 GPU 是否可用,然后使用torch.device检索设备名。然后,将实例化所有未来的张量,并使用.to(device)方法将其移动到目标设备。

示例 1-16:创建 CUDA 张量

Input[0]:
import torch
print (torch.cuda.is_available())
Output[0]:
True
Input[1]:
# preferred method: device agnostic tensor instantiation
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print (device)
Output[1]:
cuda
Input[2]:
x = torch.rand(3, 3).to(device)
describe(x)
Output[2]:
Type: torch.cuda.FloatTensor
Shape/size: torch.Size([3, 3])
Values:
tensor([[ 0.9149,  0.3993,  0.1100],
        [ 0.2541,  0.4333,  0.4451],
        [ 0.4966,  0.7865,  0.6604]], device='cuda:0')

要对 CUDA 和非 CUDA 对象进行操作,我们需要确保它们在同一设备上。如果我们不这样做,计算就会中断,如下面的代码片段所示。

例如,在计算不属于计算图的监视指标时,就会出现这种情况。当操作两个张量对象时,确保它们在同一个设备上。例子 1-17 所示。

示例 1-17:混合 CUDA 张量和 CPU 绑定的张量

Input[0]
y = torch.rand(3, 3)
x + y
Output[0]
----------------------------------------------------------------------
RuntimeError                         Traceback (most recent call last)
      1 y = torch.rand(3, 3)
----> 2 x + y

RuntimeError: Expected object of type torch.cuda.FloatTensor but found type torch.FloatTensor for argument #3 'other'
Input[1]
cpu_device = torch.device("cpu")
y = y.to(cpu_device)
x = x.to(cpu_device)
x + y
Output[1]
tensor([[ 0.7159,  1.0685,  1.3509],
        [ 0.3912,  0.2838,  1.3202],
        [ 0.2967,  0.0420,  0.6559]])

请记住,将数据从 GPU 来回移动是非常昂贵的。因此,典型的过程包括在 GPU 上执行许多并行计算,然后将最终结果传输回 CPU。这将允许您充分利用 gpu。如果您有几个 CUDA 可见的设备(即,最佳实践是在执行程序时使用CUDA_VISIBLE_DEVICES环境变量,如下图所示:

CUDA_VISIBLE_DEVICES=0,1,2,3 python main.py

在本书中我们不涉及并行性和多 gpu 训练,但是它们在缩放实验中是必不可少的,有时甚至在训练大型模型时也是如此。我们建议您参考 PyTorch 文档和讨论论坛,以获得关于这个主题的更多帮助和支持。

练习

掌握一个主题的最好方法是解决问题。这里有一些热身运动。许多问题将涉及到查阅官方文件[1]和寻找有用的功能。 .

  1. Create a 2D tensor and then add a dimension of size 1 inserted at dimension 0.

  2. Remove the extra dimension you just added to the previous tensor.

  3. Create a random tensor of shape 5x3 in the interval [3, 7)

  4. Create a tensor with values from a normal distribution (mean=0, std=1).

  5. Retrieve the indexes of all the nonzero elements in the tensor torch.Tensor([1, 1, 1, 0, 1]).

  6. Create a random tensor of size (3,1) and then horizontally stack 4 copies together.

  7. Return the batch matrix-matrix product of two 3-dimensional matrices (a=torch.rand(3,4,5), b=torch.rand(3,5,4)).

  8. Return the batch matrix-matrix product of a 3D matrix and a 2D matrix (a=torch.rand(3,4,5), b=torch.rand(5,4)).

    Solutions

  9. a = torch.rand(3, 3) a.unsqueeze(0)

  10. a.squeeze(0)

  11. 3 + torch.rand(5, 3) * (7 - 3)

  12. a = torch.rand(3, 3) a.normal_()

  13. a = torch.Tensor([1, 1, 1, 0, 1]) torch.nonzero(a)

  14. a = torch.rand(3, 1) a.expand(3, 4)

  15. a = torch.rand(3, 4, 5) b = torch.rand(3, 5, 4) torch.bmm(a, b)

  16. a = torch.rand(3, 4, 5) b = torch.rand(5, 4) torch.bmm(a, b.unsqueeze(0).expand(a.size(0), * b.size()))

总结

在这一章中,我们介绍了本书的目标——自然语言处理(NLP)和深度学习——并对监督学习范式进行了详细的理解。

在本章的最后,您现在应该熟悉或至少了解各种术语,例如观察、目标、模型、参数、预测、损失函数、表示、学习/训练和推理。您还了解了如何使用单热编码对学习任务的输入(观察和目标)进行编码。

我们还研究了基于计数的表示,如 TF 和 TF-IDF。我们首先了解了什么是计算图,静态和动态计算图,以及 PyTorch 张量操纵操作。在第二章中,我们对传统的 NLP 进行了概述。第二章,这一章应该为你奠定必要的基础,如果你对这本书的主题是新的,并为你的书的其余部分做准备。

重点是 TF-IDF

词频(TF)= 某个词在文章中出现的次数 / 文章中的总词数

逆文档频率(IDF)= log(语料库的文档总数 / (包含该词的文档数 + 1))

TF 应该很容易理解就是计算词频,IDF 衡量词的常见程度.为了计算 IDF 我们需要事先准备一个语料库用来模拟语言的使用环境,如果一个词越是常见,那么式子中分母越大,逆文档频率越接近 0.这里分母+1是为了避免分母为 0 的情况出现

TF-IDF = 词频(TF)× 逆文档频率(IDF)

TF-IDF 可以很好的实现提取文章中关键词的目的.

本人出于学习的目的,引用本书内容,非商业用途,推荐大家阅读此书,一起学习!!!


加油!

感谢!

努力!

上一篇:hdu1540 区间合并模板(点修改及查询)


下一篇:01.数据操作