问题:PyTorch中的“视图”方法如何工作?
我对方法感到困惑 view()
对以下代码片段中。
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2,2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16*5*5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
我的困惑是关于以下几行。
x = x.view(-1, 16*5*5)
是什么 tensor.view()
函数有作用?我已经在很多地方看到了它的用法,但是我不明白它是如何解释其参数的。
如果我给负值作为参数,会发生什么? view()
函数怎样?例如,如果我打电话给我tensor_variable.view(1, 1, -1)
怎么办?
谁能view()
用一些例子解释功能的主要原理?
回答 0
视图功能旨在重塑张量。
说你有张量
import torch
a = torch.range(1, 16)
a
是具有16个元素(从1到16(包括))的张量。如果要重塑该张量以使其成为4 x 4
张量,则可以使用
a = a.view(4, 4)
现在a
将是4 x 4
张量。请注意,在重塑后,元素总数必须保持不变。重塑张a
到3 x 5
张量是不恰当的。
参数-1是什么意思?
如果在某些情况下您不知道要多少行,但是确定了列数,则可以将其指定为-1。(请注意,您可以将其扩展到具有更大尺寸的张量。轴值之一只能是-1)。这是一种告诉库的方法:“给我一个具有这么多列的张量,然后您就可以计算出实现此目的所需的适当行数”。
可以在上面给出的神经网络代码中看到。在x = self.pool(F.relu(self.conv2(x)))
前进功能中的线之后,您将具有16深度特征图。您必须将其展平以将其分配给完全连接的层。因此,您告诉pytorch重塑所获得的张量,使其具有特定的列数,并告诉它自己决定行数。
在numpy和pytorch之间绘制相似之处, view
类似于numpy的重塑功能。
回答 1
让我们做一些例子,从简单到困难。
该
view
方法返回的张量具有与张量相同的数据self
(这意味着返回的张量具有相同数量的元素),但形状不同。例如:a = torch.arange(1, 17) # a's shape is (16,) a.view(4, 4) # output below 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [torch.FloatTensor of size 4x4] a.view(2, 2, 4) # output below (0 ,.,.) = 1 2 3 4 5 6 7 8 (1 ,.,.) = 9 10 11 12 13 14 15 16 [torch.FloatTensor of size 2x2x4]
假设这
-1
不是参数之一,则将它们相乘时,结果必须等于张量中的元素数量。如果您执行以下操作:a.view(3, 3)
,它将引发一个RuntimeError
原因,因为形状(3 x 3)不适用于具有16个元素的输入。换句话说:3 x 3不等于16而是9。您可以将其
-1
用作传递给函数的参数之一,但只能使用一次。所有发生的事情是该方法将为您完成如何填充该维度的数学运算。例如a.view(2, -1, 4)
等于a.view(2, 2, 4)
。[16 /(2 x 4)= 2]请注意,返回的张量共享相同的数据。如果您在“视图”中进行了更改,那么您正在更改原始张量的数据:
b = a.view(4, 4) b[0, 2] = 2 a[2] == 3.0 False
现在,对于更复杂的用例。该文档说,每个新视图维必须是原始维的子空间,或者只能是跨度d,d + 1,…,d + k,它们满足以下所有i = 0,…的连续性条件。 ..,k-1,stride [i] = stride [i +1] x size [i +1]。否则,
contiguous()
需要先调用才能查看张量。例如:a = torch.rand(5, 4, 3, 2) # size (5, 4, 3, 2) a_t = a.permute(0, 2, 3, 1) # size (5, 3, 2, 4) # The commented line below will raise a RuntimeError, because one dimension # spans across two contiguous subspaces # a_t.view(-1, 4) # instead do: a_t.contiguous().view(-1, 4) # To see why the first one does not work and the second does, # compare a.stride() and a_t.stride() a.stride() # (24, 6, 2, 1) a_t.stride() # (24, 2, 1, 6)
请注意,对于
a_t
,因为24!= 2 x 3,所以stride [0]!= stride [1] x size [1]
回答 2
torch.Tensor.view()
简而言之,torch.Tensor.view()
受numpy.ndarray.reshape()
或启发numpy.reshape()
,创建了一个新视图,只要新形状与原始张量的形状兼容张量。
让我们通过一个具体的例子来详细了解这一点。
In [43]: t = torch.arange(18)
In [44]: t
Out[44]:
tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17])
有了这个张量t
的形状(18,)
,新观点可以只为以下形状创建:
(1, 18)
或等效 (1, -1)
或 或等效 或 或等效 或 或等效 或 或等效 或或等效 或(-1, 18)
(2, 9)
(2, -1)
(-1, 9)
(3, 6)
(3, -1)
(-1, 6)
(6, 3)
(6, -1)
(-1, 3)
(9, 2)
(9, -1)
(-1, 2)
(18, 1)
(18, -1)
(-1, 1)
正如我们可以从已经上述形状元组观察,形状元组(例如中的元素的乘法运算2*9
,3*6
等)必须始终等于在原始张量元素的总数(18
在我们的例子)。
要观察的另一件事是,我们-1
在每个形状元组的一个位置中使用了a 。通过使用a -1
,我们懒于自己进行计算,而是将任务委托给PyTorch来在形状创建新视图时对该形状进行该值的计算。需要注意的重要一件事是,我们只能-1
在形状元组中使用单个。其余值应由我们明确提供。其他PyTorch会抱怨RuntimeError
:
RuntimeError:只能推断一个维度
因此,使用上述所有形状,PyTorch将始终返回原始张量的新视图t
。这基本上意味着,它只是针对所请求的每个新视图更改张量的步幅信息。
下面是一些示例,说明每个新视图如何改变张量的步幅。
# stride of our original tensor `t`
In [53]: t.stride()
Out[53]: (1,)
现在,我们将看到新视图的大步前进:
# shape (1, 18)
In [54]: t1 = t.view(1, -1)
# stride tensor `t1` with shape (1, 18)
In [55]: t1.stride()
Out[55]: (18, 1)
# shape (2, 9)
In [56]: t2 = t.view(2, -1)
# stride of tensor `t2` with shape (2, 9)
In [57]: t2.stride()
Out[57]: (9, 1)
# shape (3, 6)
In [59]: t3 = t.view(3, -1)
# stride of tensor `t3` with shape (3, 6)
In [60]: t3.stride()
Out[60]: (6, 1)
# shape (6, 3)
In [62]: t4 = t.view(6,-1)
# stride of tensor `t4` with shape (6, 3)
In [63]: t4.stride()
Out[63]: (3, 1)
# shape (9, 2)
In [65]: t5 = t.view(9, -1)
# stride of tensor `t5` with shape (9, 2)
In [66]: t5.stride()
Out[66]: (2, 1)
# shape (18, 1)
In [68]: t6 = t.view(18, -1)
# stride of tensor `t6` with shape (18, 1)
In [69]: t6.stride()
Out[69]: (1, 1)
这就是view()
功能的魔力。它只是改变(原始)张量的步幅为每个新的观点,只要新的形状视图是与原来的形状相容。
从跨步元组可能会观察到的另一件有趣的事情是,在形状元组的第0 个位置的元素的值等于在形状元组的第一个位置的元素的值。
In [74]: t3.shape
Out[74]: torch.Size([3, 6])
|
In [75]: t3.stride() |
Out[75]: (6, 1) |
|_____________|
这是因为:
In [76]: t3
Out[76]:
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17]])
步幅(6, 1)
说,从一个元素到下一个元素沿0 个维度,我们要跳或采取6个步骤。(即从去0
到6
,人们必须采取6个步骤。)但是,从一个元素去的1个一个元素ST层面,我们只需要只差一步(例如,用于从去2
到3
)。
因此,步幅信息是如何从存储器访问元素以执行计算的核心。
torch.reshape()
此函数将返回一个视图,并且与使用完全相同torch.Tensor.view()
只要新形状与原始张量的形状兼容,与之。否则,它将返回一个副本。
但是,注意事项torch.reshape()
警告:
连续的输入和具有兼容步幅的输入可以在不复制的情况下进行重塑,但其中一个不应依赖于复制与查看行为。
回答 3
我发现它x.view(-1, 16 * 5 * 5)
等效于x.flatten(1)
,其中参数1指示扁平化过程从第一维开始(而不是扁平化“样本”维),如您所见,后者的用法在语义上更加清晰并且易于使用,因此我喜欢flatten()
。
回答 4
参数-1是什么意思?
您可以读取-1
为动态数量的参数或“任何内容”。正因为如此,只能有一个参数-1
在view()
。
如果您要求,x.view(-1,1)
将输出张量形状,[anything, 1]
具体取决于中的元素数量x
。例如:
import torch
x = torch.tensor([1, 2, 3, 4])
print(x,x.shape)
print("...")
print(x.view(-1,1), x.view(-1,1).shape)
print(x.view(1,-1), x.view(1,-1).shape)
将输出:
tensor([1, 2, 3, 4]) torch.Size([4])
...
tensor([[1],
[2],
[3],
[4]]) torch.Size([4, 1])
tensor([[1, 2, 3, 4]]) torch.Size([1, 4])
回答 5
weights.reshape(a, b)
将返回一个新的张量,该张量的数据与权重为(a,b)的权重相同,因为它会将数据复制到内存的另一部分。
weights.resize_(a, b)
返回具有不同形状的相同张量。但是,如果新形状导致的元素数量少于原始张量,则某些元素将从张量中删除(但不会从内存中删除)。如果新形状导致的元素数量多于原始张量,则新元素将在内存中未初始化。
weights.view(a, b)
将返回与具有权重(a,b)的权重相同的数据的新张量
回答 6
我真的很喜欢@Jadiel de Armas的例子。
我想对.view(…)的元素排序方式有一点了解
- 对于形状为(a,b,c)的张量,其元素的顺序由编号系统确定:其中第一个数字为 数字,第二个数字为b数字,第三个数字为c数字。
- .view(…)返回的新Tensor中的元素映射将保留原始Tensor的此顺序。
回答 7
让我们尝试通过以下示例了解视图:
a=torch.range(1,16)
print(a)
tensor([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14.,
15., 16.])
print(a.view(-1,2))
tensor([[ 1., 2.],
[ 3., 4.],
[ 5., 6.],
[ 7., 8.],
[ 9., 10.],
[11., 12.],
[13., 14.],
[15., 16.]])
print(a.view(2,-1,4)) #3d tensor
tensor([[[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.]],
[[ 9., 10., 11., 12.],
[13., 14., 15., 16.]]])
print(a.view(2,-1,2))
tensor([[[ 1., 2.],
[ 3., 4.],
[ 5., 6.],
[ 7., 8.]],
[[ 9., 10.],
[11., 12.],
[13., 14.],
[15., 16.]]])
print(a.view(4,-1,2))
tensor([[[ 1., 2.],
[ 3., 4.]],
[[ 5., 6.],
[ 7., 8.]],
[[ 9., 10.],
[11., 12.]],
[[13., 14.],
[15., 16.]]])
如果我们知道y,z的值,则将-1作为参数值是计算x值的一种简便方法;在3d的情况下,反之亦然;对于2d,它又是计算x值的一种简便方法知道y的值,反之亦然。