为深度学习创建PyTorch张量 - 最佳选项
正如我们所看到的,PyTorch张量是torch.Tensor
PyTorch类的实例。张量的抽象概念与PyTorch张量之间的区别在于,PyTorch张量为我们提供了一个可以在代码中操作的具体实现。
在上一篇文章中,我们看到了如何使用数据(如Python列表、序列和NumPy ndarrays)在PyTorch中创建张量。给定一个numpy.ndarray
,我们发现有四种方法可以创建一个torch.Tensor
对象。
这里是一个快速回顾:
> data = np.array([1,2,3])
> type(data)
numpy.ndarray> o1 = torch.Tensor(data)
> o2 = torch.tensor(data)
> o3 = torch.as_tensor(data)
> o4 = torch.from_numpy(data)
> print(o1)
tensor([1., 2., 3.])
> print(o2)
tensor([1, 2, 3], dtype=torch.int32)
> print(o3)
tensor([1, 2, 3], dtype=torch.int32)
> print(o4)
tensor([1, 2, 3], dtype=torch.int32)
我们在这篇文章中的任务是探索这些选项之间的区别,并为我们创建张量的需求提出最佳选项。
不同系统上的Numpy dtype
行为
根据你的机器和操作系统,你的dtype
可能与这里和视频中显示的不同。
Numpy根据它是在32位还是64位系统上运行来设置其默认dtype
,并且在Windows系统上的行为也有所不同。
这个链接提供了关于在Windows系统上看到的差异的更多信息。受影响的方法是:tensor
、as_tensor
和from_numpy
。
感谢hivemind的David找出了这一点!
张量创建操作:有什么区别?
让我们开始并找出这些差异都是关于什么的。
大写/小写:torch.Tensor()
与torch.tensor()
注意第一个选项torch.Tensor()
有一个大写的T
,而第二个选项torch.tensor()
有一个小写的t
。这个区别是怎么回事?
第一个选项带有大写的T
是torch.Tensor
类的构造函数,第二个选项是我们所说的_工厂函数_,它构建torch.Tensor
对象并返回给调用者。
你可以将torch.tensor()
函数视为一个工厂,它根据一些参数输入构建张量。工厂函数是一种创建对象的软件设计模式。如果你想了解更多,可以查看这里。
好的,这就是大写T
和小写t
之间的区别,但在这两种方式中,哪一种更好?答案是使用任何一个都可以。然而,工厂函数torch.tensor()
有更好的文档和更多的配置选项,所以它目前是胜出的选择。
默认dtype
与推断的dtype
好吧,在我们从使用列表中删除torch.Tensor()
构造函数之前,让我们回顾一下我们在打印的张量输出中观察到的区别。
区别在于每个张量的dtype
。让我们看看:
> print(o1.dtype)
torch.float32> print(o2.dtype)
torch.int32> print(o3.dtype)
torch.int32> print(o4.dtype)
torch.int32
这里的区别在于,torch.Tensor()
构造函数在构建张量时使用默认的dtype
。我们可以使用torch.get_default_dtype()
方法验证默认的dtype
:
> torch.get_default_dtype()
torch.float32
通过代码验证,我们可以这样做:
> o1.dtype == torch.get_default_dtype()
True
其他调用根据传入的数据选择dtype
。这被称为类型推断。dtype
是根据传入的数据推断的。请注意,也可以通过将dtype
作为参数指定,为这些调用显式设置dtype
:
> torch.tensor(data, dtype=torch.float32)
> torch.as_tensor(data, dtype=torch.float32)
使用torch.Tensor()
,我们无法向构造函数传递dtype
。这是torch.Tensor()
构造函数缺乏配置选项的一个例子。这是选择torch.tensor()
工厂函数来创建张量的另一个原因。
让我们看看这些替代创建方法之间的最后一个隐藏区别。
为了性能共享内存:复制与共享
第三个区别隐藏在幕后。为了揭示这个区别,我们需要在用ndarray
创建我们的张量后,改变原始输入数据在numpy.ndarray
中。
让我们这样做,看看我们得到什么:
> print('old:', data)
old: [1 2 3]> data[0] = 0> print('new:', data)
new: [0 2 3]> print(o1)
tensor([1., 2., 3.])> print(o2)
tensor([1, 2, 3], dtype=torch.int32)> print(o3)
tensor([0, 2, 3], dtype=torch.int32)> print(o4)
tensor([0, 2, 3], dtype=torch.int32)
注意,最初我们有data[0]=1
,还要注意我们只改变了原始numpy.ndarray
中的数据。注意我们没有明确地对我们的张量(o1
,o2
,o3
,o4
)进行任何更改。
然而,在设置data[0]=0
之后,我们可以看到我们的一些张量发生了变化。前两个o1
和o2
仍然在索引0
处有原始值1
,而后两个o3
和o4
在索引0
处有新值0
。
这是因为torch.Tensor()
和torch.tensor()
在输入数据时_复制_它们,而torch.as_tensor()
和torch.from_numpy()
在内存中与原始输入对象_共享_它们的输入数据。
共享数据 | 复制数据 |
---|---|
torch.as_tensor() | torch.tensor() |
torch.from_numpy() | torch.Tensor() |
这种共享只是意味着内存中的实际数据存在于一个地方。因此,对底层数据发生的任何更改都将反映在两个对象中,即torch.Tensor
和numpy.ndarray
。
共享数据比复制数据更有效,使用的内存更少,因为数据不会写入内存中的两个位置。
如果我们有一个torch.Tensor
,我们想将其转换为numpy.ndarray
,我们可以这样做:
> print(o3.numpy())
[0 2 3]> print(o4.numpy())
[0 2 3]
这给出了:
> print(type(o3.numpy()))
<class 'numpy.ndarray'>> print(type(o4.numpy()))
<class 'numpy.ndarray'>
这确立了torch.as_tensor()
和torch.from_numpy()
都与它们的输入数据共享内存。然而,我们应该使用哪一个,它们之间有什么区别?
torch.from_numpy()
函数只接受numpy.ndarray
,而torch.as_tensor()
函数接受各种数组式对象,包括其他PyTorch张量。因此,torch.as_tensor()
是在内存共享游戏中的胜出选择。
那为什么要这么多种函数呢?
在PyTorch中创建张量的最佳选项
鉴于所有这些细节,这两个是最佳选项:
-
torch.tensor()
-
torch.as_tensor()
torch.tensor()
调用是主要的选择,而torch.as_tensor()
应该在调整我们的代码以提高性能时使用。
关于内存共享(在可能的地方工作)的一些注意事项:
- 由于
numpy.ndarray
对象分配在CPU上,当使用GPU时,as_tensor()
函数必须将数据从CPU复制到GPU。 -
as_tensor()
的内存共享不适用于内置的Python数据结构,如列表。 -
as_tensor()
调用需要开发人员了解共享功能。这是必要的,这样我们就不会在不知不觉中对底层数据进行了不想要的更改,而没有意识到更改会影响到多个对象。 - 如果
numpy.ndarray
对象和张量对象之间有很多来回操作,as_tensor()
的性能提升将更大。然而,如果只是一个单一的加载操作,从性能角度来看不应该有太大影响。
总结
此时,我们应该对PyTorch的tensor
创建选项有了更好的理解。我们学习了工厂函数,并且看到了内存_共享与复制_如何影响性能和程序行为。下次见!