我们已经看到的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)
我们在这篇文章中的任务是探索这些选项之间的区别,并为我们的张量创建需求提供最佳选择。
dtype
Behavior On Different Systems根据您的机器和操作系统,您的dtype
可能不同于此处和视频中显示的dtype。
Numpy根据它是在32位
还是64位
系统上运行来设置其默认dtype,并且该行为在Windows系统上也有所不同。
该链接提供了进一步的信息,可重新定义Windows系统上的差异。 受影响的方法是:tensor
,as_tensor
和from_numpy
。
让我们开始找出这些差异的全部含义。
torch.Tensor()
Vs torch.tensor()
请注意,第一个选项torch.Tensor()
如何具有大写字母T,而第二个选项torch.tensor()
如何具有小写字母t。 有什么区别吗?
大写字母T的第一个选项是torch.Tensor
类的构造函数,第二个选项是我们所谓的工厂函数,该函数构造torch.Tensor
对象并将其返回给调用者。
您可以将torch.tensor()
函数看作是在给定一些参数输入的情况下构建张量的工厂。 工厂功能是用于创建对象的软件设计模式。 如果您想了解更多关于它的信息,请点击这里。
好的。 那就是大写字母T和小写字母t之间的区别,但是两者之间哪种更好? 答案是可以使用其中之一。 但是,工厂函数torch.tensor()
具有更好的文档和更多的配置选项,因此目前可以赢得胜利。
dtype
Vs Inferred 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()
torch.float32
我们可以使用torch.get_default_dtype()
方法验证默认的dtype
:
> 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
之后,我们可以看到一些张量发生了变化。 对于索引0,前两个o1
和o2
仍具有原始值1,而对于索引0,后两个o3
和o4
仍具有新值0。
发生这种情况是因为torch.Tensor()
和torch.tensor()
复制了它们的输入数据,而torch.as_tensor()
和torch.from_numpy()
与原始输入对象共享了它们在内存中的输入数据。
Share Data | Copy Data |
---|---|
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.ndarrays
,而torch.as_tensor()
函数则接受各种各样的类似于数组的对象,包括其他PyTorch
张量。 因此,torch.as_tensor()
是内存共享游戏中的获胜选择。
考虑到所有这些细节,这两个是最佳选择:
1.torch.tensor()
2. 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张量创建选项有了更好的了解。 我们了解了工厂功能,并且了解了内存共享与复制如何影响性能和程序行为。 下一个再见!