使用列表选择器就地添加
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]
是单个元素,但它的存储仍然是完整数组,因为它只是原始张量的视图。
根据索引是列表还是整数,我遇到了 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]
是单个元素,但它的存储仍然是完整数组,因为它只是原始张量的视图。