使用列表选择器就地添加

Inplace add with list selectors

根据索引是列表还是整数,我遇到了 pythorch 的某种不一致行为。看看这段代码:

# First example, integer selector ==> Ok
t = torch.tensor([[0, 1], [1, 0]])
t[0, 0].add_(10)
print(t)
tensor([[10,  1],
        [ 1,  0]])

# Second example, list selector ==> ???
t = torch.tensor([[0, 1], [1, 0]])
t[[0], [0]].add_(10) # notice the list selector
print(t)
tensor([[0, 1],
        [1, 0]])

#Third example, list selector with inplace add operator ==> Ok
t = torch.tensor([[0, 1], [1, 0]])
t[[0], [0]] += 10
print(t)
tensor([[10,  1],
        [ 1,  0]])

我不明白为什么 pytorch 在第二个例子中无法更新 t

查看两种索引的区别:

In []: t[0, 0].shape
Out[]: torch.Size([])
In []: t[[0], [0]].shape
Out[]: torch.Size([1])

当您直接索引 t 的第 (0, 0) 个元素时,您可以引用该条目并可以将 add_ 放入其中。 t[0,0] 的形状是 [] - 即你得到一个标量 - (0,0) 条目的内容。
但是,当您使用列表索引 ([0], [0]) 时,您会得到一个 1 维张量,形状为 [1]。也就是说,您得到 t 的 sub-tensor 的 copy。然后,您将 add_ 放置到 sub-tensor 的副本中,您对原始 t:

没有任何影响
In []: r = t[[0], [0]].add_(10)
In []: t
Out[]:
tensor([[0, 1],
        [1, 0]])

In []: r
Out[]: tensor([10])

也许您想研究 index_add_() 来完成您的任务。

更新 当您使用列表索引分配给 t 时,您并不是在创建副本(这没有任何意义。因此,

t[[0], [0]] += 10

转换为

t[[0], [0]] = t[[0], [0]] + 10

也就是说,在右侧我们有 t(0,0) sub-tensor 的 copy 并且我们添加 10到 sub-tensor,结果是形状 [1] 张量,值为 [10]。在左侧,我们将此 [10] 分配给 t(0,0) sub-tensor(不是它的副本 - 它没有意义)。
因此 t[[0], [0]] += 10 的输出是

tensor([[10,  1],
        [ 1,  0]])

那是因为花式索引(即使用列表索引)returns 副本,而直接索引 returns 原始张量的视图。检查这种情况的一种简单方法是比较基础 storage

In [16]: a = torch.arange(3)

In [17]: a.storage()
Out[17]:
 0
 1
 2
[torch.LongStorage of size 3]

In [18]: a[0].storage()
Out[18]:
 0
 1
 2
[torch.LongStorage of size 3]

In [19]: a[[0]].storage()
Out[19]:
 0
[torch.LongStorage of size 1]

注意 a[0] 是单个元素,但它的存储仍然是完整数组,因为它只是原始张量的视图。