¿Cómo funciona el método de “vista” en PyTorch?

Estoy confundido acerca de la view() del método view() en el siguiente fragmento de código.

 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() 

Mi confusión es con respecto a la siguiente línea.

 x = x.view(-1, 16*5*5) 

¿Qué hace la función tensor.view() ? He visto su uso en muchos lugares, pero no puedo entender cómo interpreta sus parámetros.

¿Qué sucede si le doy valores negativos como parámetros a la función view() ? Por ejemplo, ¿qué pasa si llamo tensor_variable.view(1, 1, -1) ?

¿Alguien puede explicar el principio principal de la función view() con algunos ejemplos?

    La función de vista está destinada a remodelar el tensor.

    Digamos que tienes un tensor

     import torch a = torch.range(1, 16) 

    a es un tensor que tiene 16 elementos de 1 a 16 (incluidos). Si desea remodelar este tensor para convertirlo en un tensor de 4 x 4 , puede usar

     a = a.view(4, 4) 

    Ahora será un tensor 4 x 4 . Tenga en cuenta que después de la remodelación, el número total de elementos debe seguir siendo el mismo. Cambiar la forma del tensor a a un tensor de 3 x 5 no sería apropiado.

    ¿Cuál es el significado del parámetro -1?

    Si existe alguna situación en la que no sepa cuántas filas desea pero está seguro del número de columnas, puede especificar esto con un -1. ( Tenga en cuenta que puede extender esto a tensores con más dimensiones. Solo uno de los valores del eje puede ser -1 ). Esta es una forma de decirle a la biblioteca: “dame un tensor que tenga estas muchas columnas y calcules el número apropiado de filas que es necesario para que esto suceda”.

    Esto se puede ver en el código de neural network que has dado anteriormente. Después de la línea x = self.pool(F.relu(self.conv2(x))) en la función de avance, tendrá un mapa de características de 16 profundidades. Tienes que aplanar esto para darle a la capa totalmente conectada. Entonces le dice a pytorch que modifique el tensor que obtuvo para que tenga un número específico de columnas y le dice que decida el número de filas por sí mismo.

    Al dibujar una similitud entre numpy y pytorch, la view es similar a la función de reformar de numpy.

    Vamos a hacer algunos ejemplos, de los más simples a los más difíciles.

    1. El método de view devuelve un tensor con los mismos datos que el tensor self (lo que significa que el tensor devuelto tiene el mismo número de elementos), pero con una forma diferente. Por ejemplo:

       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] 
    2. Suponiendo que -1 no es uno de los parámetros, cuando los multiplica juntos, el resultado debe ser igual al número de elementos en el tensor. Si lo hace: a.view(3, 3) , generará un RuntimeError porque la forma (3 x 3) no es válida para la entrada con 16 elementos. En otras palabras: 3 x 3 no es igual a 16 sino a 9.

    3. Puede usar -1 como uno de los parámetros que pasa a la función, pero solo una vez. Todo lo que sucede es que el método hará las matemáticas por usted sobre cómo llenar esa dimensión. Por ejemplo, a.view(2, -1, 4) es equivalente a a.view(2, 2, 4) . [16 / (2 x 4) = 2]

    4. Observe que el tensor devuelto comparte los mismos datos . Si realiza un cambio en la “vista”, está cambiando los datos del tensor original:

       b = a.view(4, 4) b[0, 2] = 2 a[2] == 3.0 False 
    5. Ahora, para un caso de uso más complejo. La documentación dice que cada nueva dimensión de vista debe ser un subespacio de una dimensión original, o solo abarcar d, d + 1, …, d + k que cumpla con la siguiente condición de contigüidad que para todo i = 0,. .., k – 1, zancada [i] = zancada [i + 1] x tamaño [i + 1] . De lo contrario, se debe llamar a contiguous() antes de poder ver el tensor. Por ejemplo:

       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) 

      Note que para a_t , stride [0]! = Stride [1] x tamaño [1] desde 24! = 2 x 3

    Descubrí que x.view(-1, 16 * 5 * 5) es equivalente a x.flatten(1) , donde el parámetro 1 indica que el proceso de aplanamiento comienza desde la primera dimensión (no aplanando la dimensión ‘muestra’) Como puede ver, este último uso es semánticamente más claro y más fácil de usar, así que prefiero flatten() .

    weights.reshape(a, b) devolverá un nuevo tensor con los mismos datos que los pesos con tamaño (a, b) a veces, y algunas veces un clon, ya que en ellos se copian los datos en otra parte de la memoria.

    weights.resize(a, b) devuelve el mismo tensor con una forma diferente. Sin embargo, si la nueva forma produce menos elementos que el tensor original, algunos elementos se eliminarán del tensor (pero no de la memoria). Si la nueva forma produce más elementos que el tensor original, los nuevos elementos quedarán sin inicializar en la memoria.

    weights.view(a, b) devolverá un nuevo tensor con los mismos datos que los pesos con tamaño (a, b)