【问题标题】:Pytorch preferred way to copy a tensorPytorch 复制张量的首选方式
【发布时间】:2019-08-11 11:09:53
【问题描述】:

似乎有几种方法可以在 Pytorch 中创建张量的副本,包括

y = tensor.new_tensor(x) #a

y = x.clone().detach() #b

y = torch.empty_like(x).copy_(x) #c

y = torch.tensor(x) #d

根据如果我执行ad 得到的用户警告,b 明显优于ad。为什么首选?表现?我认为它的可读性较差。

有什么理由支持/反对使用c

【问题讨论】:

  • b 的一个优点是它明确表明y 不再是计算图的一部分,即不需要梯度。 c 与所有 3 个不同,y 仍然需要 grad。
  • torch.empty_like(x).copy_(x).detach()a/b/d 一样吗?我认识到这不是一个聪明的方法,我只是想了解 autograd 是如何工作的。 docs for clone() 说“与 copy_() 不同,此函数记录在计算图中”让我感到困惑,这让我认为 copy_() 不需要 grad。
  • 文档中有一个非常明确的注释:When data is a tensor x, new_tensor() reads out ‘the data’ from whatever it is passed, and constructs a leaf variable. Therefore tensor.new_tensor(x) is equivalent to x.clone().detach() and tensor.new_tensor(x, requires_grad=True) is equivalent to x.clone().detach().requires_grad_(True). The equivalents using clone() and detach() are recommended.
  • Pytorch '1.1.0' 现在推荐 #b 并在 #d 中显示警告
  • @ManojAcharya 可能会考虑在此处添加您的评论作为答案。

标签: copy pytorch tensor


【解决方案1】:

检查张量是否被复制的一个例子:

import torch
def samestorage(x,y):
    if x.storage().data_ptr()==y.storage().data_ptr():
        print("same storage")
    else:
        print("different storage")
a = torch.ones((1,2), requires_grad=True)
print(a)
b = a
c = a.data
d = a.detach()
e = a.data.clone()
f = a.clone()
g = a.detach().clone()
i = torch.empty_like(a).copy_(a)
j = torch.tensor(a) # UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).


print("a:",end='');samestorage(a,a)
print("b:",end='');samestorage(a,b)
print("c:",end='');samestorage(a,c)
print("d:",end='');samestorage(a,d)
print("e:",end='');samestorage(a,e)
print("f:",end='');samestorage(a,f)
print("g:",end='');samestorage(a,g)
print("i:",end='');samestorage(a,i)

输出:

tensor([[1., 1.]], requires_grad=True)
a:same storage
b:same storage
c:same storage
d:same storage
e:different storage
f:different storage
g:different storage
i:different storage
j:different storage

如果出现不同的存储,则张量被复制。 PyTorch 有近 100 种不同的构造函数,因此您可以添加更多方法。

如果我需要复制张量,我会使用 copy(),这也会复制 AD 相关信息,所以如果我需要删除 AD 相关信息,我会使用:

y = x.clone().detach()

【讨论】:

    【解决方案2】:

    TL;DR

    使用.clone().detach()(或者最好使用.detach().clone()

    如果你先分离张量然后克隆它,计算路径不会被复制,反之则被复制然后放弃。因此,.detach().clone() 的效率要稍微高一些。-- pytorch forums

    因为它的功能略快且明确。


    使用perflot,我绘制了复制 pytorch 张量的各种方法的时序。

    y = tensor.new_tensor(x) # method a
    
    y = x.clone().detach() # method b
    
    y = torch.empty_like(x).copy_(x) # method c
    
    y = torch.tensor(x) # method d
    
    y = x.detach().clone() # method e
    

    x 轴是创建张量的维度,y 轴显示时间。该图是线性比例的。如您所见,tensor()new_tensor() 与其他三种方法相比需要更多时间。

    注意: 在多次运行中,我注意到在 b、c、e 中,任何方法的时间都最短。对于 a 和 d 也是如此。但是,方法 b、c、e 的时序始终低于 a 和 d。

    import torch
    import perfplot
    
    perfplot.show(
        setup=lambda n: torch.randn(n),
        kernels=[
            lambda a: a.new_tensor(a),
            lambda a: a.clone().detach(),
            lambda a: torch.empty_like(a).copy_(a),
            lambda a: torch.tensor(a),
            lambda a: a.detach().clone(),
        ],
        labels=["new_tensor()", "clone().detach()", "empty_like().copy()", "tensor()", "detach().clone()"],
        n_range=[2 ** k for k in range(15)],
        xlabel="len(a)",
        logx=False,
        logy=False,
        title='Timing comparison for copying a pytorch tensor',
    )
    

    【讨论】:

    【解决方案3】:

    根据Pytorch documentation#a 和#b 是等价的。它还说

    推荐使用 clone() 和 detach() 的等效项。

    所以如果你想复制一个张量并从你应该使用的计算图中分离出来

    y = x.clone().detach()
    

    因为它是最干净和最易读的方式。所有其他版本都有一些隐藏的逻辑,也不是 100% 清楚计算图和梯度传播会发生什么。

    关于#c:实际完成的工作似乎有点复杂,并且还可能引入一些开销,但我不确定。

    编辑:既然在 cmets 中被问到为什么不直接使用 .clone()

    来自pytorch docs

    与 copy_() 不同,此函数记录在计算图中。传播到克隆张量的梯度将传播到原始张量。

    因此,当.clone() 返回数据的副本时,它会保留计算图并在其中记录克隆操作。如前所述,这将导致梯度传播到克隆张量也传播到原始张量。这种行为可能会导致错误并且并不明显。由于这些可能的副作用,只有在明确需要此行为时,才应通过.clone() 克隆张量。为了避免这些副作用,添加了.detach() 以断开计算图与克隆张量的连接。

    由于通常对于复制操作,人们想要一个不会导致不可预见的副作用的干净副本,因此复制张量的首选方法是.clone().detach()

    【讨论】:

    • 为什么需要detach()
    • 来自文档“与 copy_() 不同,此函数记录在计算图中。传播到克隆张量的梯度将传播到原始张量。”。所以要真正复制你想要分离的张量,或者你可能会得到一些不需要的梯度更新,你不知道它们来自哪里。
    • .clone() 本身呢?
    • 我添加了一些文字来解释为什么不自己克隆。希望这能回答问题。
    【解决方案4】:

    Pytorch '1.1.0' 现在推荐 #b 并显示 #d 警告

    【讨论】:

    • .clone() 本身呢?
    • 单独克隆也会使变量附加到原始图形
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-11
    • 2021-02-07
    • 2020-05-02
    • 2022-10-17
    • 2019-07-08
    • 2020-05-18
    • 1970-01-01
    相关资源
    最近更新 更多