Je suis confus à propos de la méthode view()
dans l'extrait de code suivant.
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()
Ma confusion concerne la ligne suivante.
x = x.view(-1, 16*5*5)
Que fait la fonction tensor.view()
? J'ai vu son utilisation dans de nombreux endroits, mais je ne comprends pas comment il interprète ses paramètres.
Que se passe-t-il si je donne des valeurs négatives comme paramètres de la fonction view()
? Par exemple, que se passe-t-il si j'appelle, tensor_variable.view(1, 1, -1)
?
Quelqu'un peut-il expliquer le principe de la fonction view()
avec quelques exemples?
La fonction de visualisation a pour but de remodeler le tenseur.
Dis que tu as un tenseur
_import torch
a = torch.range(1, 16)
_
a
est un tenseur qui a 16 éléments de 1 à 16 (inclus). Si vous souhaitez remodeler ce tenseur pour en faire un tenseur _4 x 4
_, vous pouvez utiliser
_a = a.view(4, 4)
_
Maintenant, a
sera un tenseur _4 x 4
_. Notez qu'après le remodelage, le nombre total d'éléments doit rester identique. Remodeler le tenseur a
en un tenseur _3 x 5
_ ne serait pas approprié.
Si vous ne connaissez pas le nombre de lignes que vous souhaitez mais que vous êtes certain du nombre de colonnes, vous pouvez le spécifier avec la valeur -1. ( Notez que vous pouvez l'étendre aux tenseurs ayant plus de dimensions. Une seule des valeurs d'axe peut être -1 ). C'est une façon de dire à la bibliothèque: "donnez-moi un tenseur qui a ces nombreuses colonnes et calculez le nombre approprié de lignes nécessaires pour que cela se produise".
Ceci peut être vu dans le code de réseau de neurones que vous avez donné ci-dessus. Après la ligne x = self.pool(F.relu(self.conv2(x)))
dans la fonction de transfert, vous aurez une carte de caractéristiques de 16 profondeurs. Vous devez aplatir ceci pour le donner à la couche entièrement connectée. Donc, vous dites à pytorch de remodeler le tenseur obtenu pour avoir un nombre spécifique de colonnes et lui dites de décider lui-même le nombre de lignes.
Dessinant une similitude entre numpy et pytorch, view
est similaire à la fonction remodeler de numpy.
Faisons quelques exemples, du plus simple au plus difficile.
La méthode view
renvoie un tenseur avec les mêmes données que le tenseur self
(ce qui signifie que le tenseur renvoyé a le même nombre d'éléments), mais de forme différente. Par exemple:
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]
En supposant que -1
ne soit pas l'un des paramètres, lorsque vous les multipliez, le résultat doit être égal au nombre d'éléments du tenseur. Si vous le faites: a.view(3, 3)
, il y aura une RuntimeError
car la forme (3 x 3) n'est pas valide pour une entrée avec 16 éléments. En d'autres termes: 3 x 3 n'est pas égal à 16 mais 9.
Vous pouvez utiliser -1
comme l'un des paramètres que vous transmettez à la fonction, mais une seule fois. Tout ce qui se passe, c'est que la méthode fera le calcul pour vous afin de remplir cette dimension. Par exemple, a.view(2, -1, 4)
est équivalent à a.view(2, 2, 4)
. [16/(2 x 4) = 2]
Notez que le tenseur retourné partage les mêmes données. Si vous modifiez la "vue", vous modifiez les données d'origine du tenseur:
b = a.view(4, 4)
b[0, 2] = 2
a[2] == 3.0
False
Maintenant, pour un cas d'utilisation plus complexe. La documentation indique que chaque nouvelle dimension de vue doit être soit un sous-espace d’une dimension originale, soit uniquement une étendue d, d + 1, ..., d + k qui satisfont à la condition de contiguïté suivante: pour tous i = 0, ..., k - 1, stride [i] = foulée [i + 1] x taille [i + 1] . Sinon, contiguous()
doit être appelé avant que le tenseur puisse être visualisé. Par exemple:
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)
Notez que pour a_t
, () stride [0]! = Stride [1] x taille [1] depuis 24! = 2 x 3
Quelle est la signification du paramètre -1?
Vous pouvez lire -1
sous forme de nombre dynamique de paramètres ou "tout". De ce fait, il ne peut y avoir qu'un seul paramètre -1
dans view()
.
Si vous demandez x.view(-1,1)
, la forme du tenseur [anything, 1]
sera générée en fonction du nombre d'éléments dans x
. Par exemple:
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)
Est-ce que la sortie:
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])
Je l'ai compris que x.view(-1, 16 * 5 * 5)
équivaut à x.flatten(1)
, où le paramètre 1 indique que le processus d'aplatissement commence à partir de la 1ère dimension (pas d'aplatissement de la dimension 'échantillon'). Comme vous pouvez le constater, le dernier usage est sémantiquement plus clair et plus facile à utiliser, je préfère donc flatten()
.
weights.reshape(a, b)
renverra un nouveau tenseur avec les mêmes données que les poids de taille (a, b) car il y copie les données dans une autre partie de la mémoire.
weights.resize_(a, b)
renvoie le même tenseur avec une forme différente. Cependant, si la nouvelle forme produit moins d'éléments que le tenseur d'origine, certains éléments seront supprimés du tenseur (mais pas de la mémoire). Si la nouvelle forme produit plus d'éléments que le tenseur d'origine, les nouveaux éléments ne seront pas initialisés en mémoire.
weights.view(a, b)
retournera un nouveau tenseur avec les mêmes données que des poids de taille (a, b)