我对以下代码片段中的方法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()
函数的主要原理?
视图函数旨在重塑张量。
说你有一个张量
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)。这是告诉库的一种方式:“给我一个具有这么多列的张量,并计算实现这一点所需的适当行数”。
这可以在您上面给出的神经网络代码中看到。在前向函数中的x = self.pool(F.relu(self.conv2(x)))
行之后,您将拥有一个16深度的特征映射。您必须将其展平以将其提供给完全连接的图层。因此,您告诉pytorch重新构造您获得的具有特定列数的张量,并告诉它自己决定行数。
在numpy和pytorch之间绘制相似性,view
类似于numpy的reshape函数。
让我们举一些例子,从简单到更难。
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
因为shape(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
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
,stride [0]!= stride [1] x size [1],因为24!= 2 x 3我发现x.view(-1, 16 * 5 * 5)
相当于x.flatten(1)
,其中参数1表示展平过程从第一维开始(不展平'样本'维度)正如您所看到的,后一种用法在语义上更清晰,更易于使用,所以我更喜欢flatten()
。
参数-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])
weights.reshape(a, b)
将返回一个新的张量,其数据与带有大小(a,b)的权重相同,因为它将数据复制到内存的另一部分。
weights.resize_(a, b)
返回具有不同形状的相同张量。但是,如果新形状导致的元素少于原始张量,则一些元素将从张量中移除(但不会从内存中移除)。如果新形状导致的元素多于原始张量,则新元素将在内存中未初始化。
weights.view(a, b)
将返回一个新的张量,其数据与大小相同的数据(a,b)