1.引言
PyTorch专辑的知识源于2023年阿姆斯特丹大学深度学习课程的PyTorch入门教程,旨在为程序员提供PyTorch基础的简洁介绍,并帮助您配置环境,以便编写自己的神经网络。PyTorch是一个开源的机器学习框架,它允许您自定义神经网络,并有效地进行优化。尽管PyTorch并非唯一的机器学习框架,但它有其独特的优势。其他类似的框架包括TensorFlow、JAX和Caffe。我们选择介绍PyTorch的相关知识,主要是因为它已经非常成熟,拥有庞大的开发者社群(最初由Facebook开发),并且因其灵活性和在研究领域的广泛应用而受到青睐。许多当前的研究论文都使用PyTorch进行代码发布,因此,熟悉PyTorch对您来说将是一个宝贵的技能。与此同时,TensorFlow(由Google开发)通常被认为是一个适合生产环境的深度学习库。然而,一旦您深入理解了一个机器学习框架,学习另一个框架就会变得相对容易,因为它们之间共享许多相同的概念和思想。例如,TensorFlow的第二版在很多方面都受到了PyTorch的启发,使得这两个框架在某些方面更加相似。
我们选择分享此专辑旨在为您提供特别为实践课程设计的基础知识,同时深入理解PyTorch的内部工作原理。在接下来的几周里,我们还将继续通过一系列关于深度学习的博文,探索PyTorch的新特性。
1.1.导入程序包
# 导入标准库
import os # 用于与操作系统交互,如文件路径操作
import math # 提供数学函数和常量
import numpy as np # NumPy库,提供大量的数学函数操作以及高性能的多维数组对象
import time # 提供时间相关的函数
# 导入绘图库
import matplotlib.pyplot as plt # Matplotlib的pyplot模块,提供了类似于MATLAB的绘图系统
%matplotlib inline # 在Jupyter Notebook中使matplotlib绘制的图形能够内嵌显示
from IPython.display import set_matplotlib_formats # IPython的显示模块,用于设置图形的输出格式
set_matplotlib_formats('svg', 'pdf') # 设置图形输出的格式为SVG和PDF,便于导出和高质量打印
from matplotlib.colors import to_rgba # 用于颜色转换的函数,将颜色名称或颜色代码转换为RGBA格式
import seaborn as sns # Seaborn库,基于matplotlib的高级绘图库,提供了更多样化的绘图样式和调色板
sns.set() # 设置Seaborn的默认样式,使其绘制的图形更加美观
# 导入进度条库
from tqdm.notebook import tqdm # tqdm的notebook版本,用于在Jupyter Notebook中显示进度条
# 注意:tqdm中文注释本身是不必要的,因为tqdm本身就是一个进度条工具,这里的注释只是说明其用途
2.Pytorch基础
我们将从回顾PyTorch的非常基础的概念开始。作为先决条件,我们建议您熟悉numpy包,因为大多数机器学习框架都基于非常相似的概念。如果您还不熟悉numpy,不用担心,后续章节将进行详细介绍。
那么,让我们从导入PyTorch开始。该包称为torch,基于其原始框架Torch。作为第一步,我们可以检查其版本:
import torch
print("Using torch", torch.__version__)
Using torch 2.1.0
在编写本文时,最新的版本是2.1。因此,您应该看到输出为"Using torch 2.1.0"或"Using torch 2.0.0",最终可能在Colab上有一些CUDA版本的扩展。如果您使用dl2023环境,您应该看到"Using torch 2.1.0"。通常,建议将PyTorch版本保持更新到最新版本。如果您看到的版本号低于2.0,请确保您已安装正确的环境。如果在本文发布之后,PyTorch 2.2或更新版本,你也不用担心,PyTorch版本之间的接口变化不大,因此所有代码也应该可以与新版本一起运行。
正如在每个机器学习框架中一样,PyTorch提供了诸如生成随机数等随机函数。然而,一个非常好的实践是设置您的代码,以便使用完全相同的随机数进行可重复性。这就是为什么我们在下面设置了一个种子。
torch.manual_seed(42) # 设置种子
<torch._C.Generator at 0x7fa327d6da90>
2.1.张量
张量是PyTorch对numpy数组的等价物,另外还支持GPU加速。名称“tensor”是对您已经知道的概念的概括。例如,向量是一维张量,矩阵是二维张量。在使用神经网络时,我们将使用各种形状和不同维度数量的张量。
您从numpy知道的大多数常用函数也可以在张量上使用。实际上,由于numpy数组与张量非常相似,我们可以将大多数张量转换为numpy数组(反之亦然),但我们不需要经常这样做。
2.1.1.初始化
让我们首先看看以不同方式创建张量。有许多可能的选项,最简单的是调用torch.Tensor并传递所需的形状作为输入参数:
x = torch.Tensor(2, 3, 4)
print(x)
该函数torch.Tensor为所需的张量分配内存,但会重用已经在内存中的任何值。要在初始化期间直接为张量分配值,有许多替代方案,包括:
torch.zeros
: 创建一个填充零的张量torch.ones
: 创建一个填充一的张量torch.rand
: 创建一个在0和1之间均匀采样的随机值张量torch.randn
: 从均值为0,方差为1的正态分布中采样随机值的张量torch.arange
: 创建一个包含值 N , N + 1 , N + 2 , . . . , M N, N+1, N+2, ..., M N,N+1,N+2,...,M的张量torch.Tensor (input list)
: 根据您提供的列表元素创建一个张量
从嵌套列表创建张量
x = torch.Tensor([[1, 2], [3, 4]])
print(x)
tensor([[1., 2.],
[3., 4.]])
创建一个形状为[2, 3, 4]的0到1之间的随机值张量
x = torch.rand(2, 3, 4)
print(x)
您可以像在numpy中一样(x.shape)获取张量的形状,或者使用.size方法:
shape = x.shape
print("Shape:", x.shape)
size = x.size()
print("Size:", size)
dim1, dim2, dim3 = x.size()
print("Size:", dim1, dim2, dim3)
Shape: torch.Size([2, 3, 4])
Size: torch.Size([2, 3, 4])
Size: 2 3 4
2.1.2.张量与Numpy数组的转换
张量可以转换为Numpy数组,反之亦然。要将Numpy数组转换为张量,我们可以使用torch.from_numpy
函数:
np_arr = np.array([[1, 2], [3, 4]])
tensor = torch.from_numpy(np_arr)
print("Numpy 数组:", np_arr)
print("PyTorch 张量:", tensor)
Numpy 数组: [[1 2]
[3 4]]
PyTorch 张量: tensor([[1, 2],
[3, 4]])
要将PyTorch张量转换回Numpy数组,我们可以在张量上使用.numpy()
方法:
tensor = torch.arange(4)
np_arr = tensor.numpy()
print("PyTorch 张量:", tensor)
print("Numpy 数组:", np_arr)
PyTorch 张量: tensor([0, 1, 2, 3])
Numpy 数组: [0 1 2 3]
将张量转换为Numpy需要确保张量位于CPU上,而不是GPU上(关于GPU支持的更多信息将在后面的部分介绍)。如果您有一个在GPU上的张量,您需要先调用.cpu()
方法。因此,您会看到类似np_arr = tensor.cpu().numpy()
的代码行。
2.1.3.操作
大多数在numpy中存在的操作,在PyTorch中也同样存在。可以在PyTorch文档中找到操作的完整列表,但我们将在这里回顾最重要的一些操作。
最简单的操作是将两个张量相加:
x1 = torch.rand(2, 3)
x2 = torch.rand(2, 3)
y = x1 + x2
print("X1", x1)
print("X2", x2)
print("Y", y)
X1 tensor([[0.1053, 0.2695, 0.3588],
[0.1994, 0.5472, 0.0062]])
X2 tensor([[0.9516, 0.0753, 0.8860],
[0.5832, 0.3376, 0.8090]])
Y tensor([[1.0569, 0.3448, 1.2448],
[0.7826, 0.8848, 0.8151]])
调用x1 + x2
会创建一个新的张量,包含两个输入的和。然而,我们也可以进行就地操作,这些操作直接在张量的内存上应用。因此,我们可以在操作前改变x2
的值,而没有机会重新访问操作前x2
的值。下面展示了一个例子:
x1 = torch.rand(2, 3)
x2 = torch.rand(2, 3)
print("X1(之前)", x1)
print("X2(之前)", x2)
x2.add_(x1)
print("X1(之后)", x1)
print("X2(之后)", x2)
X1(之前) tensor([[0.5779, 0.9040, 0.5547],
[0.3423, 0.6343, 0.3644]])
X2(之前) tensor([[0.7104, 0.9464, 0.7890],
[0.2814, 0.7886, 0.5895]])
X1(之后) tensor([[0.5779, 0.9040, 0.5547],
[0.3423, 0.6343, 0.3644]])
X2(之后) tensor([[1.2884, 1.8504, 1.3437],
[0.6237, 1.4230, 0.9539]])
就地操作通常以下划线后缀标记(例如,使用add_
而不是add
)。
另一个常见操作是改变张量的形状。大小为(2,3)的张量可以重新组织为任何其他具有相同元素数量的形状(例如,大小为(6)或(3,2)等)。在PyTorch中,这个操作称为view
:
x = torch.arange(6)
print("X", x)
X tensor([0, 1, 2, 3, 4, 5])
x = x.view(2, 3)
print("X", x)
X tensor([[0, 1, 2],
[3, 4, 5]])
x = x.permute(1, 0) # 交换第0维和第1维
print("X", x)
X tensor([[0, 3],
[1, 4],
[2, 5]])
在神经网络的构建中,矩阵乘法是一项不可或缺的操作。想象一下,我们手头有一个输入向量 x \mathbf{x} x,它通过一个训练得到的权重矩阵 W \mathbf{W} W 被转换。实现矩阵乘法有多种方法和函数,以下是一些常用的方法:
torch.matmul
:对两个张量执行矩阵乘积操作,其行为依据张量的维度而定。若两个输入均为矩阵(即二维张量),它将执行标准的矩阵乘法。对于更高维度的张量,该函数支持广播机制(详见官方文档)。此外,也可以使用a @ b
的形式,与numpy中的操作类似。torch.mm
:对两个矩阵执行矩阵乘法,但不提供广播支持(更多信息见官方文档)。torch.bmm
:支持批次(batch)维度的矩阵乘法。假设第一个张量 T T T 的形状为 ( b × n × m ) (b \times n \times m) (b×n×m),第二个张量 R R R 的形状为 ( b × m × p ) (b \times m \times p) (b×m×p),则输出 O O O 的形状为 ( b × n × p ) (b \times n \times p) (b×n×p),它是通过对 T T T 和 R R R 的子矩阵执行 b b b 次矩阵乘法来计算的: O i = T i @ R i O_i = T_i @ R_i Oi=Ti@Ri。torch.einsum
:使用爱因斯坦求和约定执行矩阵乘法及其他操作(例如乘积的和)。关于爱因斯坦求和的详细解释可以在作业1中找到。
通常情况下,我们会选择使用 torch.matmul
或者 torch.bmm
。下面我们将通过 torch.matmul
来演示一次矩阵乘法。
首先,我们创