3.2.3 张量的实质
Python列表或数字元组是在内存中单独分配的Python对象的集合,如图3.3左侧所示。 另一方面,PyTorch张量或NumPy数组是(通常)包含未装箱的C数值类型的连续内存块上的视图而不是Python对象。 在这种情况下,每个元素都是32位(4字节)浮点数,如图3.3右侧所示。 这意味着存储1,000,000个浮点数的一维张量将恰好需要4,000,000个连续字节,再加上少量的元数据开销(例如维和数字类型)。
假设我们有一个要用于表示几何对象的坐标列表:也许是一个2D三角形,其顶点的坐标为(4,1),(5,3)和(2,1)。 该示例与深度学习并不特别相关,但是很容易理解。 除了像以前那样在Python列表中将坐标作为数字之外,我们还可以通过将Xs存储在偶数索引中并将Ys存储在奇数索引中来使用一维张量,如下所示:
# In[8]:
points = torch.zeros(6) # 使用.zeros只是获取适当大小的数组的一种方法。
points[0] = 4.0 # 我们用我们实际想要的值覆盖这些零。
points[1] = 1.0
points[2] = 5.0
points[3] = 3.0
points[4] = 2.0
points[5] = 1.0
我们还可以将Python列表传递给构造函数,以达到相同的效果:
# In[9]:
points = torch.tensor([4.0, 1.0, 5.0, 3.0, 2.0, 1.0])
points
# Out[9]:
tensor([4., 1., 5., 3., 2., 1.])
要获取第一点的坐标,请执行以下操作:
# In[10]:
float(points[0]), float(points[1])
# Out[10]:
(4.0, 1.0)
可以,尽管让第一个索引引用单个2D点而不是点坐标是可行的。 为此,我们可以使用2D张量:
# In[11]:
points = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]])
points
# Out[11]:
tensor([[4., 1.],
[5., 3.],
[2., 1.]])
在这里,我们将列表列表传递给构造函数。 我们可以询问张量其形状:
# In[12]:
points.shape
# Out[12]:
torch.Size([3, 2])
这告诉我们关于每个维度的张量的大小。 我们还可以使用零或一个来初始化张量,将大小提供为元组:
# In[13]:
points = torch.zeros(3, 2)
points
# Out[13]:
tensor([[0., 0.],
[0., 0.],
[0., 0.]])
现在我们可以使用两个索引访问张量中的单个元素:
# In[14]:
points = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]])
points
# Out[14]:
tensor([[4., 1.],
[5., 3.],
[2., 1.]])
# In[15]:
points[0, 1]
# Out[15]:
tensor(1.)
这将返回我们数据集中第零个点的Y坐标。 我们还可以像以前一样访问张量中的第一个元素,以获取第一个点的2D坐标:
# In[16]:
points[0]
# Out[16]:
tensor([4., 1.])
输出是另一个张量,它表示相同基础数据的不同视图。新张量是大小为2的一维张量,引用点张量中的第一行的值。 这是否意味着分配了新的内存块,将值复制到其中,并将新的内存包装在新的张量对象中返回了? 不,因为那将是非常低效的,尤其是如果我们拥有数百万个点。 当我们在第3.7节中介绍张量的视图时,我们将在本章后面重新讨论如何存储张量。