2.1 PyTorch中的数据类型

在本书中,我们会用到各种来源和格式的数据集,而深度学习的第一步就是将输入转换为数字数组。为此,我们会在本节中介绍PyTorch如何将不同格式的数据转换为张量(tensor)这种代数结构。张量可以表示为多维数字数组,它与NumPy数组较为相似,但也有几个重要区别,其中最主要的区别在于GPU加速训练的能力。根据其最终用途,张量可以分为多种类型,本节将介绍如何创建不同类型的张量,以及何时使用每种类型的张量,并以美国46任总统(截至2023年12月)的身高为例,讨论PyTorch中的数据结构。

按照附录A中的说明创建虚拟环境,并在计算机上安装PyTorch和Jupyter Notebook。随后在虚拟环境中打开Jupyter Notebook应用,并在新单元格中运行如下代码:

!pip install matplotlib

上述命令会在计算机上安装Matplotlib库,随后就可以在Python中绘制图像了。

2.1.1 创建PyTorch张量

在训练深度神经网络时,我们需要将数字数组作为输入传给模型。根据生成模型要创建的内容类型,这些数字也分为不同类型。例如,在生成图像时,输入是以0~255的整数形式表示的原始像素,但我们会将它们转换成-1~1的浮点数;在生成文本时,有一个类似于词典的“词汇表”,输入是一串整数,模型告诉我们这个词对应了词典中的哪个条目。

假设要用PyTorch计算46任美国总统的平均身高。我们首先要收集他们的身高数据(以厘米为单位),并将数据存储在一个Python列表中:

heights = [189, 170, 189, 163, 183, 171, 185,
           168, 173, 183, 173, 173, 175, 178,
           183, 193, 178, 173, 174, 183, 183,
           180, 168, 180, 170, 178, 182, 180,
           183, 178, 182, 188, 175, 179, 183,
           193, 182, 183, 177, 185, 188, 188,
182, 185, 191, 183]

这些数字按时间顺序排列,列表中的第一个值“189”代表美国第一任总统乔治·华盛顿身高189厘米;最后一个值代表约瑟夫·拜登身高183厘米。我们可以使用PyTorch中的tensor()方法将Python列表转换为PyTorch张量:

import torch
heights_tensor = torch.tensor(heights,    ➊
dtype=torch.float64)    ➋

➊ #将Python列表转换为PyTorch张量

➋ #指定PyTorch张量中的数据类型

我们在tensor()方法中使用dtype参数来指定数据类型。PyTorch张量的默认数据类型是float32,即32位浮点数。上述代码示例已将数据类型转换为float64,即双精度浮点数。float64能提供比float32更精确的结果,但计算耗时更长。精度和计算成本需要权衡,使用哪种数据类型取决于具体任务。

表2.1列出了不同数据类型和相应的PyTorch张量类型,其中包括不同精度的整数和浮点数。整数也可以是有符号的或无符号的。

表2.1 PyTorch中的数据类型和张量类型

图片表格

要创建具有特定数据类型的张量,方法有两种。第一种是使用表2.1的第一列中指定的PyTorch类;第二种是使用torch.tensor()方法,并使用dtype参数指定数据类型(参数值见表2.1的第二列)。例如,要将Python列表[1, 2, 3]转换成一个包含32位整数的PyTorch张量,可以使用清单2.1中的两种方法。

清单2.1 指定张量类型的两种方法

t1=torch.IntTensor([1, 2, 3])    ➊
t2=torch.tensor([1, 2, 3],
             dtype=torch.int)    ➋
print(t1)
print(t2)

➊ #用torch.IntTensor()指定张量类型

➋ #用dtype=torch.int指定张量类型

运行上述代码,得到如下输出:

tensor([1, 2, 3], dtype=torch.int32)
tensor([1, 2, 3], dtype=torch.int32)

练习2.1

使用两种不同方法,将Python列表[5, 8, 10]转换为含有64位浮点数的PyTorch张量。本题可参考表2.1的第三行。

很多时候,可能需要创建一个所有值均为0的PyTorch张量。例如,在GAN中,我们创建一个“0”张量作为虚假样本的标签,具体实例可参考第3章使用PyTorch中的zeros()方法生成具有特定形状“0”张量的操作。在PyTorch中,张量是一个n维数组,它的形状是一个元组(tuple),表示其每一维的大小。下列代码会生成一个2行3列的“0”张量:

tensor1 = torch.zeros(2, 3)
print(tensor1)

输出如下:

tensor([[0., 0., 0.],
[0., 0., 0.]])

该张量的形状为(2, 3),这意味着它是一个二维数组,第一维有2个元素,第二维有3个元素。在这里,我们并没有指定数据类型,输出的默认数据类型是float32。

有时,我们需要创建一个所有值均为1的PyTorch张量。例如,在GAN中,我们创建一个“1”张量作为真实样本的标签。在下列代码中,我们使用ones()方法创建一个所有值均为1的三维张量:

tensor2 = torch.ones(1,4,5)
print(tensor2)

输出如下:

tensor([[[1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.]]])

这样就生成了一个三维PyTorch张量,张量的形状是(1, 4, 5)。

练习2.2

创建一个值为0的三维PyTorch张量,使张量的形状为(2, 3, 4)。

我们还可以在张量构造函数中使用一个NumPy数组而非Python列表,如下所示:

import numpy as np
 
nparr=np.array(range(10))
pt_tensor=torch.tensor(nparr, dtype=torch.int)
print(pt_tensor)

输出如下:

tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=torch.int32)

2.1.2 对PyTorch张量进行索引和切片

我们可以像处理Python列表那样,使用方括号([])来索引和切片PyTorch张量。索引和切片允许我们对张量中的一个或多个元素(而非所有元素)执行操作。依然以46任美国总统的身高为例,如果我们想评估第三任总统托马斯·杰斐逊的身高,可以这样做:

height = heights_tensor[2]
print(height)

输出如下:

tensor(189., dtype=torch.float64)

输出显示,托马斯·杰斐逊的身高为189厘米。

我们还可以使用负索引从张量的末尾开始计数。例如,要找出倒数第二任总统唐纳德·特朗普的身高,即可使用“-2”这个索引,如下所示:

height = heights_tensor[-2]
print(height)

输出如下:

tensor(191., dtype=torch.float64)

输出显示,特朗普的身高为191厘米。

如果我们想知道张量heights_tensor中最近的5任总统的身高,该怎么办?可以这样获取张量的切片:

five_heights = heights_tensor[-5:]
print(five_heights)

冒号(:)用于分隔起始索引和结束索引。如果未提供起始索引,默认值为0;如果未提供结束索引,则可以直接涵盖到张量中的最后一个元素(就像我们在上述代码示例中所做的那样)。负索引表示从结尾处开始计数。输出如下:

tensor([188., 182., 185., 191., 183.], dtype=torch.float64)

输出显示,张量中最近的5任总统(克林顿、布什、奥巴马、特朗普和拜登)身高分别为188厘米、182厘米、185厘米、191厘米和183厘米。

练习2.3

使用切片在张量heights_tensor中查询最初的5任美国总统的身高。

2.1.3 PyTorch张量的形状

PyTorch张量有一个属性shape,它告诉我们张量的维数。了解PyTorch张量的形状非常重要,因为在对张量执行操作时,不匹配的形状会导致错误。例如,如果想知道张量heights_tensor的形状,可以这样做:

print(heights_tensor.shape)

输出如下:

torch.Size([46])

这告诉我们,heights_tensor是一个包含46个值的一维张量。

我们还可以改变PyTorch张量的形状。首先,让我们把身高单位从厘米转换成英尺。由于1英尺约等于30.48厘米,因此可以将原本的张量数据除以30.48来实现这一转换:

heights_in_feet = heights_tensor / 30.48
print(heights_in_feet)

输出如下(为节省篇幅,这里省略了一些值;完整的输出见本书的配套资源):

tensor([6.2008, 5.5774, 6.2008, 5.3478, 6.0039, 5.6102, 6.0696, …
6.0039], dtype=torch.float64)

新张量heights_in_feet以英尺为单位存储了身高数据。例如,张量中的最后一个值表示约瑟夫·拜登身高6.0039英尺。

我们可以使用PyTorch中的cat()方法来连接两个张量:

heights_2_measures = torch.cat(
    [heights_tensor,heights_in_feet], dim=0)
print(heights_2_measures.shape)

在各种张量运算中,dim参数可用于指定执行运算的维度。在上述代码示例中,dim的值设置为0表示我们将沿第一维连接两个张量。其输出如下:

torch.Size([92])

最后获得的张量是一维的,有92个值,其中一些值以厘米为单位,另一些值以英尺为单位。因此我们需要将其重塑为两行46列,使第一行表示以厘米为单位的身高,第二行表示以英尺为单位的身高:

heights_reshaped = heights_2_measures.reshape(2, 46)

获得的新张量heights_reshaped是二维的,形状为(2, 46)。我们还可以使用方括号对多维张量进行索引和切片。例如,要以英尺为单位输出特朗普的身高,可以这样做:

print(heights_reshaped[1,-2])

结果如下:

tensor(6.2664, dtype=torch.float64)

命令heights_reshaped[1,-2]告诉Python查找第二行倒数第二列中的值,从而返回以英尺为单位的特朗普身高:6.2664。

提示

引用张量中标量值所需的索引数量与张量的维数相同。这就是我们只使用一个索引来查找一维张量heights_tensor中的值,而使用两个索引来查找二维张量heights_reshaped中的值的原因。

练习2.4

使用索引获取张量heights_reshaped中以厘米为单位的约瑟夫·拜登的身高。

2.1.4 PyTorch张量的数学运算

我们可以使用不同方法对PyTorch张量进行数学运算,这些方法包括mean()、median()、sum()、max()等。例如,要求得46任总统身高的中位数(以厘米为单位),可以这样做:

print(torch.median(heights_reshaped[0,:]))

代码片段heights_reshaped[0,:]返回张量heights_reshaped的第一行的所有列。上述代码返回第一行中的值的中位数,输出如下:

tensor(182., dtype=torch.float64)

这意味着美国总统的身高中位数为182厘米。

要找出两行数据的平均身高,可以在mean()方法中使用dim=1:

print(torch.mean(heights_reshaped,dim=1))

参数dim=1表示平均值是通过对列(索引为1的维度)进行折叠计算出来的,实际上等于是沿索引为0的维度(行)求平均值。输出如下:

tensor([180.0652,   5.9077], dtype=torch.float64)

结果显示,两行的平均值分别为180.0652厘米和5.9077英尺。

要找出身高最高的总统,可以这样做:

values, indices = torch.max(heights_reshaped, dim=1)
print(values)
print(indices)

输出如下:

tensor([193.0000,   6.3320], dtype=torch.float64)
tensor([15, 15])

torch.max()方法会返回两个张量:一个张量values包含总统的最高身高(以厘米和英尺为单位),另一个张量indices对应着身高最高的总统的索引。结果显示,第16任总统(林肯)身高最高,为193厘米,即6.332英尺。

练习2.5

使用torch.min()方法找出身高最矮的美国总统对应的索引和身高数值。