¿Qué hace .view() en PyTorch?

10 minutos de lectura

avatar de usuario
Wasi Ahmed

Que hace .view() hacer a un tensor x? ¿Qué significan los valores negativos?

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

avatar de usuario
Kashyap

view() remodela el tensor sin copiar la memoria, similar a numpy reshape().

Dado un tensor a con 16 elementos:

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

Para remodelar este tensor para convertirlo en un 4 x 4 tensor, usa:

a = a.view(4, 4)

Ahora a será un 4 x 4 tensor. Tenga en cuenta que después de la remodelación, el número total de elementos debe permanecer igual. Reformando el tensor a a un 3 x 5 tensor no sería apropiado.

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

Si hay alguna situación en la que no sabe cuántas filas desea pero está seguro de la cantidad de columnas, puede especificar esto con -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 tantas columnas y calcule el número apropiado de filas necesarias para que esto suceda”.

Esto se puede ver en este código de definición de modelo. 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 dárselo a la capa completamente conectada. Así que le dices a PyTorch que reforme el tensor que obtuviste para tener un número específico de columnas y le dices que decida el número de filas por sí mismo.

  • “la vista es similar a la remodelación de numpy” — ¿Por qué no lo llamaron simplemente? reshape en PyTorch?!

    – MWB

    30 de abril de 2017 a las 1:53


  • @MaxB A diferencia de la remodelación, el nuevo tensor devuelto por “vista” comparte los datos subyacentes con el tensor original, por lo que en realidad es una vista del tensor anterior en lugar de crear uno nuevo.

    – qihqi

    4 de mayo de 2017 a las 5:28

  • @blckbird “remodelar siempre copia la memoria. Ver nunca copia la memoria”. github.com/torch/cutorch/issues/98

    – devinbost

    26 de julio de 2017 a las 16:37


  • @devinbost Antorcha remodelar siempre copia la memoria. NumPy reformar no.

    –Tavian Barnes

    2 de agosto de 2018 a las 19:48

  • La remodelación de la antorcha no siempre devuelve una copia. Esa es una verificación muy fácil. Lo mismo con numpy

    – Alleo

    22 de diciembre de 2020 a las 2:33

avatar de usuario
Jadiel de Armas

Hagamos algunos ejemplos, de más simple a más difícil.

  1. los view método devuelve un tensor con los mismos datos que el self tensor (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. Asumiendo que -1 no es uno de los parámetros, cuando los multiplica, el resultado debe ser igual al número de elementos en el tensor. Si lo haces: a.view(3, 3)levantará 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. Puedes 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á los cálculos 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 re, re + 1, …, re + k que satisfacen la siguiente condición de contigüidad de que para todo i = 0, …, k – 1, paso[i] = zancada[i + 1] talla x[i + 1]. De lo contrario, contiguous() debe llamarse antes de que se pueda 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, paso[0] != zancada[1] talla x[1] ya que 24 != 2×3

avatar de usuario
iacob

view() cambia la forma de un tensor ‘estirando’ o ‘comprimiendo’ sus elementos en la forma que especifique:

ingrese la descripción de la imagen aquí


Cómo view() ¿trabajar?

Primero veamos qué es un tensor debajo del capó:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí
Tensor y su subyacente storage por ejemplo, el tensor de la derecha (forma (3,2)) se puede calcular a partir del de la izquierda con t2 = t1.view(3,2)

Aquí puede ver que PyTorch crea un tensor al convertir un bloque subyacente de memoria contigua en un objeto similar a una matriz al agregar un shape y stride atributo:

  • shape establece la longitud de cada dimensión
  • stride indica cuántos pasos debe tomar en la memoria hasta llegar al siguiente elemento en cada dimensión

view(dim1,dim2,...) devuelve un vista de la misma información subyacente, pero remodelada a un tensor de forma dim1 x dim2 x ... (modificando el shape y stride atributos).

Tenga en cuenta que esto asume implícitamente que las dimensiones nuevas y antiguas tienen el mismo producto (es decir, el tensor antiguo y el nuevo tienen el mismo volumen).


PyTorch -1

-1 es un alias de PyTorch para “inferir esta dimensión dado que se han especificado todas las demás” (es decir, el cociente del producto original por el nuevo producto). Es una convención tomada de numpy.reshape().

Por eso t1.view(3,2) en nuestro ejemplo sería equivalente a t1.view(3,-1) o t1.view(-1,2).

torch.Tensor.view()

Simplemente pon, torch.Tensor.view() que está inspirado en numpy.ndarray.reshape() o numpy.reshape()crea un nueva vista del tensor, siempre que la nueva forma sea compatible con la forma del tensor original.

Entendamos esto en detalle usando un ejemplo concreto.

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

Con este tensor t de forma (18,)nuevo puntos de vista pueden solamente ser creado para las siguientes formas:

(1, 18) o equivalente (1, -1) o (-1, 18)
(2, 9) o equivalente (2, -1) o (-1, 9)
(3, 6) o equivalente (3, -1) o (-1, 6)
(6, 3) o equivalente (6, -1) o (-1, 3)
(9, 2) o equivalente (9, -1) o (-1, 2)
(18, 1) o equivalente (18, -1) o (-1, 1)

Como ya podemos observar en las tuplas de forma anteriores, la multiplicación de los elementos de la tupla de forma (p. ej. 2*9, 3*6 etc.) debe siempre ser igual al número total de elementos en el tensor original (18 en nuestro ejemplo).

Otra cosa a observar es que usamos un -1 en uno de los lugares en cada una de las tuplas de forma. usando un -1estamos siendo perezosos al hacer el cálculo nosotros mismos y preferimos delegar la tarea a PyTorch para que haga el cálculo de ese valor para la forma cuando crea la nueva vista. Una cosa importante a tener en cuenta es que podemos solamente usar un solo -1 en la forma de tupla. Los valores restantes deben ser proporcionados explícitamente por nosotros. De lo contrario, PyTorch se quejará lanzando un RuntimeError:

RuntimeError: solo se puede inferir una dimensión

Entonces, con todas las formas mencionadas anteriormente, PyTorch siempre devolverá un nueva vista del tensor original t. Básicamente, esto significa que solo cambia la información de paso del tensor para cada una de las nuevas vistas que se solicitan.

A continuación se muestran algunos ejemplos que ilustran cómo cambian los pasos de los tensores con cada nuevo vista.

# stride of our original tensor `t`
In [53]: t.stride() 
Out[53]: (1,)

Ahora, veremos los avances para el nuevo puntos de vista:

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

Así que esa es la magia de la view() función. Simplemente cambia los pasos del tensor (original) para cada uno de los nuevos puntos de vistasiempre que la forma del nuevo vista es compatible con la forma original.

Otra cosa interesante que uno podría observar de las tuplas strides es que el valor del elemento en el 0el posición es igual al valor del elemento en el 1S t posición de la tupla de forma.

In [74]: t3.shape 
Out[74]: torch.Size([3, 6])
                        |
In [75]: t3.stride()    |
Out[75]: (6, 1)         |
          |_____________|

Esto es porque:

In [76]: t3 
Out[76]: 
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11],
        [12, 13, 14, 15, 16, 17]])

la zancada (6, 1) dice que para ir de un elemento al siguiente elemento a lo largo del 0el dimensión, tenemos que salto o dar 6 pasos. (es decir, ir de 0 a 6uno tiene que dar 6 pasos.) Pero para ir de un elemento al siguiente elemento en el 1S t dimensión, solo necesitamos un paso (por ejemplo, para ir de 2 a 3).

Por lo tanto, la información de pasos está en el centro de cómo se accede a los elementos desde la memoria para realizar el cálculo.


antorcha.reformar()

Esta función devolvería un vista y es exactamente lo mismo que usar torch.Tensor.view() siempre que la nueva forma sea compatible con la forma del tensor original. De lo contrario, devolverá una copia.

Sin embargo, las notas de torch.reshape() advierte que:

las entradas contiguas y las entradas con pasos compatibles se pueden remodelar sin copiar, pero uno no debe depender del comportamiento de copia frente a visualización.

Me di cuenta de 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 (sin aplanar la dimensión ‘muestra’) Como puede ver, el último uso es semánticamente más claro y más fácil de usar, por lo que prefiero flatten().

avatar de usuario
lija alex

Tratemos de entender la vista con los siguientes ejemplos:

    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.]]])

-1 como valor de argumento es una manera fácil de calcular el valor de, por ejemplo, x siempre que conozcamos los valores de y, z o al revés en el caso de 3d y para 2d nuevamente, una manera fácil de calcular el valor de, por ejemplo, x siempre que sepamos conocer los valores de y o viceversa..

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

Puedes leer -1 como número dinámico de parámetros o “cualquier cosa”. Por eso solo puede haber un parámetro. -1 en view().

Si preguntas x.view(-1,1) esto generará la forma del tensor [anything, 1] dependiendo del número de elementos en x. Por ejemplo:

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)

Saldrá:

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

¿Ha sido útil esta solución?