繁体   English   中英

使用 PyTorch 进行就地操作

[英]In-place operations with PyTorch

我想知道如何处理 PyTorch 中的就地操作。 我记得在 autograd 中使用就地操作一直存在问题。

实际上,我很惊讶下面的这段代码可以工作,尽管我还没有测试过它,但我相信这段代码会在0.3.1版中引发错误。

基本上我想做的是将张量向量的某个位置设置为某个值,例如:

my_tensor[i] = 42

工作示例代码:

# test parameter a
a = torch.rand((2), requires_grad=True)
print('a ', a)
b = torch.rand(2)

# calculation
c = a + b

# performing in-place operation
c[0] = 0
print('c ', c)
s = torch.sum(c)
print('s ', s)

# calling backward()
s.backward()

# optimizer step
optim = torch.optim.Adam(params=[a], lr=0.5)
optim.step()

# changed parameter a
print('changed a', a)

输出:

a  tensor([0.2441, 0.2589], requires_grad=True)
c  tensor([0.0000, 1.1511], grad_fn=<CopySlices>)
s  tensor(1.1511, grad_fn=<SumBackward0>)
changed a tensor([ 0.2441, -0.2411], requires_grad=True)

所以很明显在0.4.1版本中。 这工作得很好,没有警告或错误。

参考文档中的这篇文章: autograd-mechanics

在 autograd 中支持就地操作是一件困难的事情,我们不鼓励在大多数情况下使用它们 Autograd 积极的缓冲区释放和重用使其非常高效,并且就地操作实际上很少显着降低内存使用量。 除非您在内存压力很大的情况下运行,否则您可能永远不需要使用它们。

但即使它有效,在大多数情况下也不鼓励使用就地操作。


所以我的问题是:

  • 就地操作的使用对性能有多大影响?

  • 在我想将张量的一个元素设置为某个值的情况下,如何使用就地操作?

提前致谢!

我不确定就地操作对性能的影响有多大,但我可以解决第二个查询。 您可以使用掩码代替就地操作。

a = torch.rand((2), requires_grad=True)
print('a ', a)
b = torch.rand(2)

# calculation
c = a + b

# performing in-place operation
mask = np.zeros(2)
mask[1] =1
mask = torch.tensor(mask)
c = c*mask
...

这可能不是对您问题的直接回答,而只是为了提供信息。

就地操作适用于计算图中的非叶张量。

叶张量是作为计算图“末端”的张量。 正式(来自is_leaf属性在这里),

对于 requires_grad 为 True 的张量,如果它们是由用户创建的,它们将是叶张量。 这意味着它们不是操作的结果,因此 grad_fn 为 None。

没有错误的例子:

a = torch.tensor([3.,2.,7.], requires_grad=True)
print(a)   # tensor([3., 2., 7.], requires_grad=True)
b = a**2
print(b)   # tensor([ 9.,  4., 49.], grad_fn=<PowBackward0>)
b[1] = 0
print(b)   # tensor([ 9.,  0., 49.], grad_fn=<CopySlices>)
c = torch.sum(2*b)
print(c)   # tensor(116., grad_fn=<SumBackward0>)
c.backward()
print(a.grad)  # tensor([12.,  0., 28.])

另一方面,就地操作不适用于张量。

导致错误的示例:

a = torch.tensor([3.,2.,7.], requires_grad=True)
print(a) # tensor([3., 2., 7.], requires_grad=True)
a[1] = 0
print(a) # tensor([3., 0., 7.], grad_fn=<CopySlices>)
b = a**2
print(b) # tensor([ 9.,  0., 49.], grad_fn=<PowBackward0>)
c = torch.sum(2*b)
print(c) # tensor(116., grad_fn=<SumBackward0>)
c.backward()  # Error occurs at this line. 

# RuntimeError: leaf variable has been moved into the graph interior

我想b[1]=0操作,在上面的第一个例子中,并不是真正的就地操作。 我想它会创建一个带有“CopySlices”操作的新张量。 就地操作之前的“旧 b”可能会保留在内部(只是它的名称被“新 b”覆盖)。 我在这里找到了一个不错的人物。

旧 b ---(CopySlices)----> 新 b

另一方面,张量a是叶张量。 在 CopySlices 操作a[1]=0 ,它变成了一个中间张量。 为了避免反向传播时叶张量和中间张量之间的这种复杂混合,禁止对叶张量进行 CopySlices 操作与向后共存。

以上仅为个人观点,请以官方文档为准。

笔记:

尽管就地操作适用于中间张量,但在执行一些就地操作时,尽可能多地使用克隆和分离是安全的,以明确创建独立于计算图的新张量。

对于您的第二个查询,当您执行c[i] = i或类似操作时,通常会调用__setitem__ 为了就地进行该操作,您可以尝试调用__setitem__函数(如果这是执行c[i] = i操作的原因。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM