Pytorch张量中的detach,clone和deepcopy有什么区别?

Cha*_*ker 12 python machine-learning pytorch

我一直在努力理解.clone(),.detach()copy.deepcopy使用 Pytorch 时的区别。特别是 Pytorch 张量。

我试着写下我所有关于它们的差异和用例的问题,很快就不知所措,并意识到也许拥有 Pytorch 张量的 4 个主要属性会更好地阐明在每个小问题中使用哪个。我意识到需要跟踪的 4 个主要属性是:

  1. 如果有一个指向张量的新指针/引用
  2. 如果有一个新的张量对象实例(因此很可能这个新实例有它自己的元数据,例如require_grads,形状is_leaf等)
  3. 如果它为张量数据分配了新的内存(即,如果这个新张量是不同张量的视图)
  4. 如果它正在跟踪操作历史记录(或者即使它正在跟踪一个全新的操作历史记录或在深度复制的情况下相同的旧操作历史记录)

根据从 Pytorch 论坛和文档中挖掘出来的内容,这是我目前在张量上使用时的区别:

克隆

对于克隆:

x_cloned = x.clone()
Run Code Online (Sandbox Code Playgroud)

我相信这就是它根据主要 4 个属性的行为方式:

  1. 克隆x_cloned 有自己的python引用/指向新对象的指针
  2. 它创建了自己的新张量对象实例(带有单独的元数据)
  3. 它已经为x_new与相同的数据分配了一个新的内存x
  4. 它跟踪操作的原始历史记录,此外还包括此clone操作作为.grad_fn=<CloneBackward>

据我所知,它的主要用途似乎是创建事物的副本,以便inplace_操作安全。此外,加上.detachas .detach().clone()(顺便说一句,“更好”的顺序)它创建了一个全新的张量,该张量已与旧历史分离,从而阻止梯度流过该路径。

分离

x_detached = x.detach()
Run Code Online (Sandbox Code Playgroud)
  1. 创建一个新的 python 引用(x_new = x当然唯一没有的)。id我相信可以使用这个
  2. 它创建了自己的新张量对象实例(带有单独的元数据)
  3. 没有x_detached与 x 相同的数据分配新的内存
  4. 它切断了梯度的历史,不允许它流过它。我认为认为它没有历史是正确的,作为一个全新的张量。

我相信我所知道的唯一明智的用法是在与.clone()as结合时使用它自己的内存创建新副本.detach().clone()。否则,我不确定它有什么用。由于它指向原始数据,因此执行就地操作可能存在潜在危险(因为它更改了旧数据,但在较早的计算图中 autograd知道对旧数据的更改)。

深拷贝

x_deepcopy = copy.deepcopy(x)
Run Code Online (Sandbox Code Playgroud)
  1. 如果有一个指向张量的新指针/引用
  2. 它使用自己的元数据创建一个新的张量实例(所有元数据都应该指向深层副本,因此如果按照我希望的方式实现新对象)。
  3. 它有自己的内存分配给张量数据
  4. 如果它真的是一个深拷贝,我会期待一个历史的深拷贝。所以它应该对历史做一个深度的复制。虽然这看起来非常昂贵,但至少在语义上与深拷贝应该是一致的。

我真的没有看到这个用例。我假设任何尝试使用它的人实际上意味着 1).detach().clone()或仅 2).clone()本身,这取决于人们是想用 1 停止梯度流到较早的图,还是只想用新的内存复制数据 2)。

因此,这是我目前必须了解差异的最佳方式,而不是询问人们可能会使用它们的所有不同场景。

这是对的吗?有没有人看到任何需要纠正的重大缺陷?

我自己担心的是我给深拷贝的语义,并想知道深拷贝历史是否正确。

我认为每个用例的常见用例列表会很棒。


资源

这些是我为得出这个问题的结论而阅读和参与的所有资源:

uke*_*emi 4

注意:自从发布此问题以来,这些函数的行为和文档页面已更新。


torch.clone()

复制张量,同时维护自动梯度图中的链接。如果您想要将张量复制为神经网络中的操作(例如,将中级表示传递到两个不同的头以计算不同的损失),则可以使用:

返回输入的副本。

注意:该函数是可微分的,因此梯度将从该操作的结果流回输入。要创建与输入没有自动求导关系的张量,请参阅detach()

torch.tensor.detach()

返回没有自动求导历史记录的原始张量的视图。如果您想要操纵张量的值(不在适当位置)而不影响计算图(例如在前向传递过程中报告值),则可以使用。

返回一个与当前图分离的新张量。

结果永远不需要梯度。

此方法还会影响前向模式 AD 梯度,并且结果永远不会有前向模式 AD 梯度。

注意:返回的张量与原始张量共享相同的存储。对其中任何一个的就地修改都会被看到,并且可能会触发正确性检查中的错误。1

copy.deepcopy

deepcopy是库中的通用 python 函数copy,它复制现有对象(如果对象本身包含对象,则递归地复制)。

当您希望复制的基础对象是可变的(或包含可变的)并且容易受到镜像其中所做的更改的影响时,可以使用此方法(与更常见的分配相反):

Python 中的赋值语句不会复制对象,而是在目标和对象之间创建绑定。对于可变或包含可变项的集合,有时需要一份副本,以便可以更改一个副本而不更改另一个副本。

正如您所说,在 PyTorch 设置中,如果您希望在完全不同的设置中使用张量对象的新副本,并且对其父对象没有关系或影响,则应该使用.detach().clone().


  1. 重要提示:以前,对返回张量的就地大小/步长/存储更改(例如resize_// resize_as_/ )也会更新原始张量。现在,这些就地更改将不再更新原始张量,而是会触发错误。对于稀疏张量:对返回张量的就地索引/值更改(例如// )将不再更新原始张量,而是会触发错误。set_transpose_zero_copy_add_