第 1 节 PyTorch Fundamentals
本节主要讲解 PyTorch 中的基本单位 Tensor(也称为张量),及一系列与 Tensor 有关的函数。
Tensor、维度与形状
函数 | 作用 | 举例 | 输出结果 | 结果描述 |
---|---|---|---|---|
torch.tensor() | 创建一个 Tensor 对象 | - torch.tensor(7) - torch.tensor([7, 7]) | - tensor(7) - tensor([7, 7]) | 将数值 7 和一维数组 [7, 7] 转换为 Tensor 对象 |
torch.tensor().item() | 将 Tensor 对象转换为 Python 数据类型 | - torch.tensor(7).item() - torch.tensor([7, 7]).item() | - 7 - ERROR | 只能转换单个值,不能转换数组 |
torch.tensor().ndim | 获取 Tensor 对象的维度 | - torch.tensor(7).ndim - torch.tensor([7, 7]).ndim - torch.tensor([7, 8], [9, 10]).ndim | - 0 - 1 - 2 | ndim 函数的作用可以看作计算有几对 嵌套 大括号(平级大括号不计入) |
torch.tensor().shape | 获取 Tensor 对象的形状 | - torch.tensor(7).shape - torch.tensor([7, 7]).shape - torch.tensor([[1, 2, 3], [4, 5, 6]]).shape - torch.tensor([ | - torch.Size([]) - torch.Size([2]) - torch.Size([2, 3]) - torch.Size([1, 4, 3]) # 表示第 1 维长度为 1,第 2 维长度为 4,第 3 维长度为 3。此处第 1 维长度为 1 的原因是最外层的大括号只包含 1 个元素即 [[...]] | shape 实际上是一个长度等于 Tensor 维度数的元组,第 i 个元素表示第 i+1 维的大小如果 Tensor 对象只包含一个数值而非数组,则没有任何维度, shape 值为空;如果 Tensor 对象包含一个一维数组,则包含 1 个维度, shape 值为该维度的长度,也就是数组的列数;如果 Tensor 对象包含一个二维数组,则包含 2 个维度, shape 长度为 2,shape[0] 为该数组的行数,shape[1] 为该数组的列数;如果 Tensor 对象包含一个多维数组,则包含 N 个维度, shape 长度为 N |
随机 Tensor、随机数种子、全 0 和全 1 Tensor
神经网络的学习方法通常是随机生成一些初始 Tensors,然后对其进行拟合,使其更准确地描述数据。
函数 | 作用 | 举例 | 输出结果 | 结果描述 |
---|---|---|---|---|
torch.rand(row, col) | 创建形状为 (row, col) 的随机 Tensor | torch.rand(3, 4) | tensor([ | 每次的输出结果是随机的 |
torch.rand(size=(width, height, channel)) | 创建形状为 (width, height, channel) 的随机 Tensor | torch.rand(size=(224, 224, 3)) | tensor([[ | size 参数可以隐性传入。该函数通常用于处理图像,channel 是图像的颜色通道,设置为 3 表示 RGB。channel 也可以置于参数第一位,即 size=(3, 224, 224) |
torch.zeros(size=(row, col)) | 创建形状为 (row, col) 的全 0 Tensor | torch.zeros(size=(3, 4)) | tensor([ | size 参数可以隐性传入。torch.ones() 函数同理创建形状为 (row, col) 的全 1 Tensor |
torch.manual_seed(int) | 设置随机数种子 | torch.manual_seed(42) torch.rand(3, 4) | tensor([ | 每次生成随机 Tensor 前都需要声明一次该函数 |
Tensor 数据类型与运算
PyTorch 中的 Tensor 对于整型数组默认使用 torch.int64
类型,对于浮点型数组默认使用 torch.float32
类型,而 Numpy 的数组默认使用 float64
类型,在互相转换时默认类型以转换源为准。
机器学习和深度学习中,使用 Tensor 时有三大易错点:
- Tensor 数据类型错误,可使用
tensor.dtype
查看; - Tensor 形状错误,可使用
tensor.shape
或tensor.size()
查看; - Tensor 设备错误,可使用
tensor.device
查看。
函数 | 作用 | 举例 | 输出结果 | 结果描述 |
---|---|---|---|---|
torch.tensor(data, dtype, device, requires_grad=False) | 创建一个 Tensor 对象 | torch.tensor([3.0, 6.0, 9.0], | tensor([3., 6., 9.]) | dtype 参数指定 Tensor 的数据类型,如 float32 或 float16 ;device 参数指定处理 Tensor 使用的设备,如 cpu 或 cuda ;requires_grad 参数设定是否使用该 Tensor 进行梯度跟踪,默认为 False |
torch.tensor().type() | 设置 Tensor 对象的数据类型 | torch.tensor([1., 2., 3.]).type(torch.float16) | tensor([1., 2., 3.], dtype=torch.float16) | 浮点型 Tensor 可与整型 Tensor 相互运算,最终结果为浮点型 |
tensor.matmul(tensorA, tensorB) tensor.mm(tensorA, tensorB) tensorA @ tensorB | 对两个 Tensor 进行矩阵乘法运算 | tensor = torch.tensor([1, 2, 3]) torch.matmul(tensor, tensor) | tensor([14]) | 矩阵乘法详细描述见表后 |
矩阵乘法涉及到维度至少为 1 的两个 Tensor,同时需要满足两个条件。设 TensorA = torch.tensor([A1, A2])
,TensorB = torch.tensor([B1, B2])
。使用 torch.tensor().T
可以转置 Tensor 的维度。
- 两个 Tensor 的内部维度必须相同。在上述条件中,
A2
和B1
为内部维度,即需要满足A2 = B1
; - 结果矩阵的
shape
等于两个 Tensor 的外部维度。在上述条件中,A1
和B2
为外部维度,则结果矩阵的shape = (A1, B2)
。
对于两个一维 Tensor,结果矩阵就是对应项相乘的和;
对于两个二维及以上多维 Tensor,可视化举例如下说明。
tensorA = torch.tensor([[1, 2],
[3, 4],
[5, 6]])
tensorB = torch.tensor([[7, 8],
[9, 10],
[11, 12]])
# tensorA.shape = torch.Size([3, 2]), tensorB.shape = torch.Size([3, 2])
# 两个 Tensor 的内部维度不同,不能运算。此时需要对其中一个 Tensor 转置,如 tensorB.T。
tensorB = tensorB.T # tensorB = torch.tensor([[7, 8, 9],
# [10, 11, 12]])
# tensorB.shape = [2, 3]
# 现在做 tensor.matmul(tensorA, tensorB) 运算。可将 tensorB 先逆时针旋转 90° 并置于 tensorA 上方,得到:
# [9, 12] -> tensorB 第一行
# [8, 11] -> tensorB 第二行
# [7, 10] -> tensorB 第三行
# -------
# [1, 2] -> tensorA 第一行
# [3, 4] -> tensorA 第二行
# [5, 6] -> tensorA 第三行
# 第一步,将 tensorB 向下移动一次,使第三行与 tensorA 的第一行相乘,得到 1*7+2*10=27,放入结果矩阵的 [0, 0] 处
# 第二步,将 tensorB 向下移动一次,使第三行与 tensorA 的第二行相乘,得到 3*7+4*10=61,放入结果矩阵的 [1, 0] 处;同时,第二行与 tensorA 的第一行相乘,得到 1*8+2*11=30,放入结果矩阵的 [0, 1] 处
# 第三步,将 tensorB 向下移动一次,使第三行与 tensorA 的第三行相乘,得到 5*7+6*10=95,放入结果矩阵的 [2, 0]处;同时,第二行与 tensorA 的第二行相乘,得到 3*8+4*11=68,放入结果矩阵的 [1, 1] 处;第一行与 tensorA 的第一行相乘,得到 1*9+2*12=33,放入结果矩阵的 [0, 2]处
# 第四步,将 tensorB 向下移动一次,使第二行与 tensorA 的第三行相乘,得到 5*8+6*11=106,放入结果矩阵的 [2, 1]处;同时,第一行与 tensorA 的第二行相乘,得到 3*9+4*12=75,放入结果矩阵的 [1, 2] 处
# 第五步,将 tensorB 向下移动一次,使第一行与 tensorA 的第三行相乘,得到 5*9+6*12=117,放入结果矩阵的 [2, 2] 处
结果矩阵可视化步骤如下:
[ - - - ] [ 27 - - ] [ 27 30 - ] [ 27 30 33 ] [ 27 30 33 ] [ 27 30 33 ]
[ - - - ] -> [ - - - ] -> [ 61 - - ] -> [ 61 68 - ] -> [ 61 68 75 ] -> [ 61 68 75 ]
[ - - - ] [ - - - ] [ - - - ] [ 95 - - ] [ 95 106 - ] [ 95 106 117 ]
Tensor 最小值、最大值及其索引、平均值、和
函数 | 作用 | 举例 | 输出结果 | 结果描述 |
---|---|---|---|---|
torch.arange(start, end, step) | 以 step 为步长创建 [start, end) 序列 | torch.arange(0, 100, 10) | tensor([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90]) | 结果包括 start 端点,不包括 end 端点 |
torch.min() | 获取一个 Tensor 序列中的最小值 | torch.min(torch.arange(0, 100, 10)) | tensor(0) | 也可使用 torch.arange().min() 的形式,torch.max() 同理 |
torch.mean() | 获取一个 Tensor 序列中所有值的平均值 | torch.mean(torch.arange(0, 100, 10).type(torch.float32)) | tensor(45.) | 也可使用类似于 torch.arange(int).type(torch.float32).mean() 的形式,该函数的参数只能是浮点数类型或复数类型 |
torch.sum() | 获取一个 Tensor 序列中所有值的和 | torch.sum(torch.arange(0, 100, 10)) | tensor(450) | 也可使用 torch.arange().sum() 的形式 |
argmin() | 获取 Tensor 最小值的索引 | torch.arange(0, 100, 10).argmin() | tensor(0) | |
argmax() | 获取 Tensor 最大值的索引 | torch.arange(0, 100, 10).argmax() | tensor(9) |
Reshape, View, Stack, Squeeze, Unsqueeze 和 Permute
- Reshape:将一个 Tensor 重塑到定义的
shape
,重塑后的 Tensor 与原 Tensor 各自独立。 - View:返回一个特定形状的 Tensor 的副本,和原 Tensor 共用内存。修改该副本会修改原 Tensor 的值。
- Stack:将多个 Tensor 拼接,类型包括
vstack
和hstack
。使用dim
参数指定新维度拼接的位置 - Squeeze:移除 Tensor 中所有维数为 1 的维度。
- Unsqueeze:添加一个维数为 1 的维度到指定 Tensor 中,使用
dim
参数指定添加的位置。 - Permute:返回一个输入 Tensor 的副本,与原 Tensor 共用内存,其中维度以特定方式排列。
示例见如下代码块:
x = torch.arange(1., 10.)
# -> x = tensor([1., 2., 3., 4., 5., 6., 7., 8., 9.]), x.shape = torch.Size([9]), x.ndim = 1
# Reshape
x_reshaped = x.reshape(1, 9) # 将 x 重塑为一个 size=(1, 9) 的二维张量
# -> x_reshaped = tensor([[1., 2., 3., 4., 5., 6., 7., 8., 9.]]), x_reshaped.shape = torch.Size([1, 9])
# View
z = x.view(1, 9) # -> z = tensor([1., 2., 3., 4., 5., 6., 7., 8., 9.])
z[:, 0] = 5 # 将 z 中所有行第 1 列的值设置为 5
# -> z = tensor([5., 2., 3., 4., 5., 6., 7., 8., 9.]), x = tensor([5., 2., 3., 4., 5., 6., 7., 8., 9.])
# Stack
x_stacked = torch.stack([x, x, x, x], dim=0) # torch.stack 的作用是把一组形状相同的张量沿着一个新的维度拼起来,dim 就是用来指定这个“新维度”要放在哪个位置。此处的 x.shape = [9],将 dim 置 0 等价于 x.unsqueeze(0),x.shape 变为 [1, 9],再将 4 个 x 在第 0 维拼起来,得到 x_stacked.shape = torch.Size([4, 9]);如果置 1,则等价于 x.unsqueeze(1),x.shape 变为 [9, 1],最终结果为 x_stacked.shape = torch.Size([9, 4])。对于一维张量,dim 只能置 0 或 1,更高维度的张量才能使用更大的 dim 值
# -> x_stacked = tensor([[5., 2., 3., 4., 5., 6., 7., 8., 9.],
# [5., 2., 3., 4., 5., 6., 7., 8., 9.],
# [5., 2., 3., 4., 5., 6., 7., 8., 9.],
# [5., 2., 3., 4., 5., 6., 7., 8., 9.]]), x_squeezed.ndim = 2
# Squeeze
x_squeezed = x_reshaped.squeeze()
# -> x_squeezed = tensor([5., 2., 3., 4., 5., 6., 7., 8., 9.]), x_squeezed.shape = torch.Size([9])
# Unsqueeze
x_unsqueezed = x_squeezed.unsqueeze(dim=1) # 此处 dim 的作用与 torch.stack 中的 dim 相同
# -> x_unsqueezed = tensor([[5.],
# [2.],
# [3.],
# [4.],
# [5.],
# [6.],
# [7.],
# [8.],
# [9.]]), x_unsqueezed.shape = torch.Size([9, 1])
# Permute
x_original = torch.rand(size=(224, 224, 3))
x_permuted = x_original.permute(2, 0, 1) # 将 x_original 的第 0 位,即第 1 个 224 放入置 0 的位,以此类推
# -> x_original.shape = torch.Size([224, 224, 3]), x_permuted = torch.Size([3, 224, 224])
PyTorch 和 Numpy
函数 | 作用 | 举例 | 输出结果 | 结果描述 |
---|---|---|---|---|
torch.from_numpy(ndarray) | 将 Numpy 数据转换到 Tensor | torch.from_numpy(np.arange(1.0, 8.0)) | tensor([1., 2., 3., 4., 5., 6., 7.], dtype=torch.float64) | Numpy 数组的默认数据类型为 float64 ,转换为 Tensor 保留类型为 torch.float64 |
torch.Tensor.numpy() | 将 Tensor 转换到 Numpy 数据 | torch.ones(7).numpy() | array([1., 1., 1., 1., 1., 1.], dtype=float32) | Tensor 默认数据类型为 torch.float32 ,转换为 Numpy 数组保留类型为 float32 |
CPU 和 GPU
函数 | 作用 | 举例 | 输出结果 | 结果描述 |
---|---|---|---|---|
torch.cuda.is_available() # bool | 返回 CUDA 是否可用 | device = “cuda” if torch.cuda.is_available() else “cpu” | 'cuda' | 如果环境中 CUDA 可用则输出 cuda ,否则输出 cpu |
torch.cuda.device_count() | 返回当前可用的 CUDA 设备数量 | torch.cuda.device_count() | 1 | |
torch.tensor().to(device) | 将 Tensor 放入 GPU 处理 | tensor_on_gpu = torch.tensor([1, 2, 3]).to(device) | tensor([1, 2, 3], device='cuda:0') | |
torch.tensor().to(device).cpu() | 将工作在 GPU 上的 Tensor 放入 CPU 处理 | tensor_on_gpu.cpu().numpy() | array([1, 2, 3]) | Numpy 工作在 CPU 上,TensorFlow 和 PyTorch 工作在 GPU 上,转换为 Numpy 需要首先将 GPU 中的 Tensor 放回 CPU |