PyTorch内存模型:“torch.from_numpy()”vs“torch.Tensor()”

gblwokeq  于 2023-10-19  发布在  其他
关注(0)|答案(5)|浏览(164)

我试图深入了解PyTorch Tensor内存模型的工作原理。

# input numpy array
In [91]: arr = np.arange(10, dtype=float32).reshape(5, 2)

# input tensors in two different ways
In [92]: t1, t2 = torch.Tensor(arr), torch.from_numpy(arr)

# their types
In [93]: type(arr), type(t1), type(t2)
Out[93]: (numpy.ndarray, torch.FloatTensor, torch.FloatTensor)

# ndarray 
In [94]: arr
Out[94]: 
array([[ 0.,  1.],
       [ 2.,  3.],
       [ 4.,  5.],
       [ 6.,  7.],
       [ 8.,  9.]], dtype=float32)

我知道PyTorchTensor * 共享NumPy ndarrays的内存缓冲区 *。因此,改变一个将反映在另一个。所以,这里我切片并更新Tensort2中的一些值

In [98]: t2[:, 1] = 23.0

正如预期的那样,它在t2arr中更新,因为它们共享相同的内存缓冲区。

In [99]: t2
Out[99]: 

  0  23
  2  23
  4  23
  6  23
  8  23
[torch.FloatTensor of size 5x2]

In [101]: arr
Out[101]: 
array([[  0.,  23.],
       [  2.,  23.],
       [  4.,  23.],
       [  6.,  23.],
       [  8.,  23.]], dtype=float32)

但是,t1也更新了。请记住,t1是使用torch.Tensor()构造的,而t2是使用torch.from_numpy()构造的

In [100]: t1
Out[100]: 

  0  23
  2  23
  4  23
  6  23
  8  23
[torch.FloatTensor of size 5x2]

因此,无论我们使用torch.from_numpy()还是torch.Tensor()从ndarray构造Tensor,所有这样的Tensor和ndarray共享相同的内存缓冲区。
基于这种理解,我的问题是,当torch.Tensor()可以完成这项工作时,为什么要存在一个专用函数torch.from_numpy()
我看了PyTorch文档,但它没有提到任何关于这一点的事情。任何想法/建议?

im9ewurl

im9ewurl1#

from_numpy()自动继承输入数组dtype。另一方面,torch.Tensortorch.FloatTensor的别名。
因此,如果将int64数组传递给torch.Tensor,输出Tensor是浮点Tensor,它们不会共享存储。torch.from_numpy会像预期的那样提供torch.LongTensor

a = np.arange(10)
ft = torch.Tensor(a)  # same as torch.FloatTensor
it = torch.from_numpy(a)

a.dtype  # == dtype('int64')
ft.dtype  # == torch.float32
it.dtype  # == torch.int64
lmyy7pcs

lmyy7pcs2#

在Pytorch中构建Tensor的推荐方法是使用以下两个工厂函数:torch.tensortorch.as_tensor
torch.tensor始终复制数据。例如,torch.tensor(x)等价于x.clone().detach()
torch.as_tensor总是尝试避免数据的副本。as_tensor避免复制数据的一种情况是,原始数据是一个numpy数组。

zd287kbt

zd287kbt3#

这来自_torch_docs.py;这里也有可能讨论“为什么”。

def from_numpy(ndarray): # real signature unknown; restored from __doc__
    """
    from_numpy(ndarray) -> Tensor

    Creates a :class:`Tensor` from a :class:`numpy.ndarray`.

    The returned tensor and `ndarray` share the same memory. 
    Modifications to the tensor will be reflected in the `ndarray` 
    and vice versa. The returned tensor is not resizable.

    Example::

        >>> a = numpy.array([1, 2, 3])
        >>> t = torch.from_numpy(a)
        >>> t
        torch.LongTensor([1, 2, 3])
        >>> t[0] = -1
        >>> a
        array([-1,  2,  3])
    """
    pass

引用numpy文档:
不同的ndarrays可以共享相同的数据,因此在一个ndarray中所做的更改可能在另一个ndarray中可见。也就是说,ndarray可以是另一个ndarray的“视图”,它所引用的数据由“基础”ndarray负责。
Pytorch docs
如果给定numpy.ndarraytorch.Tensortorch.Storage,则返回共享相同数据的新Tensor。如果给定了一个Python序列,则会从序列的副本创建一个新的Tensor。

omjgkv6w

omjgkv6w4#

答案中没有人提到的是,NumPy数组和torchTensor的问题**“共享相同的内存”**只发生在你在CPU上运行代码时,但如果你在GPU上运行,如下代码每个NumPy,Tensor将有它的内存位置

device = "cuda" if torch.cuda.is_available() else "cpu"
device #will be cuda means it run in GPU
a = np.ones(5)
print(a)
b = torch.from_numpy(a)
print(b)
a = a * 5
print(a)
print(b)

输出

[1. 1. 1. 1. 1.]
tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
[5. 5. 5. 5. 5.]
tensor([1., 1., 1., 1., 1.], dtype=torch.float64)

编辑

需要说明的是,NumPy数组和PyTorchTensor之间的内存共享只有在它们都位于CPU内存中时才会发生,但这种共享不会扩展到GPU内存。
当数据移动到GPU时,会为GPU上的Tensor分配新的内存,对CPU上原始NumPy数组所做的更改不会反映在GPUTensor中,反之亦然。

CPU内存:

当两者都在CPU内存中时,NumPy数组和PyTorchTensor之间可以发生内存共享。
对其中一个所做的更改将反映在另一个中,因为它们共享内存。

GPU内存:

当NumPy阵列和PyTorchTensor在GPU上时,它们之间不会发生内存共享。
对原始数组的任何更改都不会影响GPU上的Tensor,反之亦然。

CPU示例

import numpy as np
import torch

device = "cpu"
print("The device is :", device)

a_np = np.array([1, 2, 3])
print("Initial NumPy array 'a_np':", a_np)

a_torch = torch.from_numpy(a_np).to(device)
print("Initial PyTorch tensor 'a_torch':", a_torch)


a_np *= 2
print("\n")
print("After modifying NumPy array:")
print("NumPy array 'a_np':", a_np)
print("PyTorch tensor 'a_torch':", a_torch)
print("")
#print(np.shares_memory(a_torch,a_np))
#print("as you see a_np*2 here, change also a_torch because it shares the memory with a_torch")

输出CPU

The device is : cpu 
Initial NumPy array 'a_np': [1 2 3] 
Initial PyTorch tensor 'a_torch': tensor([1, 2, 3], dtype=torch.int32)

After modifying NumPy array: NumPy array 'a_np': [2 4 6] 
PyTorch tensor 'a_torch': tensor([2, 4, 6], dtype=torch.int32)

GPU示例

device = "cuda"
print("The device is :", device)

a_np = np.array([1, 2, 3])
print("Initial NumPy array 'a_np':", a_np)

a_torch = torch.from_numpy(a_np).to(device)
print("Initial PyTorch tensor 'a_torch':", a_torch)

a_np *= 2
print("\n")
print("After modifying NumPy array:")
print("NumPy array 'a_np':", a_np)
print("PyTorch tensor 'a_torch':", a_torch)

输出GPU

The device is : cuda 
Initial NumPy array 'a_np': [1 2 3] 
Initial PyTorch tensor 'a_torch': tensor([1, 2, 3], device='cuda:0', dtype=torch.int32)

After modifying NumPy array: NumPy array 'a_np': [2 4 6] 
PyTorch tensor 'a_torch': tensor([1, 2, 3], device='cuda:0', dtype=torch.int32)
oxalkeyp

oxalkeyp5#

我试着做你说的,它的工作如预期:Torch 1.8.1、Numpy 1.20.1、python 3.8.5

x = np.arange(8, dtype=np.float64).reshape(2,4)
y_4mNp = torch.from_numpy(x)
y_t = torch.tensor(x)
print(f"x={x}\ny_4mNp={y_4mNp}\ny_t={y_t}")

所有变量现在都具有与预期相同的值:

x=[[0. 1. 2. 3.]
 [4. 5. 6. 7.]]
y_4mNp=tensor([[0., 1., 2., 3.],
        [4., 5., 6., 7.]], dtype=torch.float64)
y_t=tensor([[0., 1., 2., 3.],
        [4., 5., 6., 7.]], dtype=torch.float64)

From_numpy使用与np变量相同的底层内存。因此,更改np或.from_numpy变量会相互影响,但不会影响Tensor变量。但是对y_t的更改只影响它本身,而不影响numpy或from_numpy变量。

x[0,1] = 111       ## changed the numpy variable itself directly
y_4mNp[1,:] = 500  ## changed the .from_numpy variable
y_t[0,:] = 999     ## changed the tensor variable
print(f"x={x}\ny_4mNp={y_4mNp}\ny_t={y_t}")

当前输出:

x=[[  0. 111.   2.   3.]
 [500. 500. 500. 500.]]
y_4mNp=tensor([[  0., 111.,   2.,   3.],
        [500., 500., 500., 500.]], dtype=torch.float64)
y_t=tensor([[999., 999., 999., 999.],
        [  4.,   5.,   6.,   7.]], dtype=torch.float64)

不知道这是否是早期版本的问题?

相关问题