¿Qué hace .contiguous() en PyTorch?

Resuelto MBT asked hace 6 años • 8 respuestas

¿ Qué hace x.contiguous()por un tensor x?

MBT avatar Feb 22 '18 04:02 MBT
Aceptado

Hay algunas operaciones sobre tensores en PyTorch que no cambian el contenido de un tensor, pero cambian la forma en que se organizan los datos. Estas operaciones incluyen:

narrow(), view(), expand()ytranspose()

Por ejemplo: cuando llamas transpose(), PyTorch no genera un nuevo tensor con un nuevo diseño, simplemente modifica la metainformación en el objeto Tensor para que el desplazamiento y la zancada describan la nueva forma deseada. En este ejemplo, el tensor transpuesto y el tensor original comparten la misma memoria:

x = torch.randn(3,2)
y = torch.transpose(x, 0, 1)
x[0, 0] = 42
print(y[0,0])
# prints 42

Aquí es donde entra en juego el concepto de contiguo . En el ejemplo anterior, xes contiguo pero yno lo es porque su diseño de memoria es diferente al de un tensor de la misma forma hecho desde cero. Tenga en cuenta que la palabra "contiguo" es un poco engañosa porque no significa que el contenido del tensor esté distribuido alrededor de bloques de memoria desconectados. Aquí los bytes todavía están asignados en un bloque de memoria, ¡pero el orden de los elementos es diferente!

Cuando llamas contiguous(), en realidad hace una copia del tensor de modo que el orden de sus elementos en la memoria es el mismo que si se hubiera creado desde cero con los mismos datos.

Normalmente no necesitas preocuparte por esto. En general, es seguro asumir que todo funcionará y esperar hasta llegar a un RuntimeError: input is not contiguouspunto donde PyTorch espera que un tensor contiguo agregue una llamada contiguous().

Shital Shah avatar Sep 07 '2018 21:09 Shital Shah

De la documentación de pytorch :

contiguous() → Tensor
Devuelve un tensor contiguo que contiene los mismos datos que el tensor propio. Si el autotensor es contiguo, esta función devuelve el autotensor.

Donde contiguousaquí significa no solo contiguos en la memoria, sino también en el mismo orden en la memoria que el orden de los índices: por ejemplo, hacer una transposición no cambia los datos en la memoria, simplemente cambia el mapa de índices a punteros de memoria, si luego aplicarlo contiguous()cambiará los datos en la memoria para que el mapa desde los índices hasta la ubicación de la memoria sea el canónico.

patapouf_ai avatar Feb 22 '2018 08:02 patapouf_ai

tensor.contiguous() creará una copia del tensor y el elemento de la copia se almacenará en la memoria de forma contigua. La función contigua() generalmente se requiere cuando primero transponemos() un tensor y luego le remodelamos (vemos). Primero, creemos un tensor contiguo:

aaa = torch.Tensor( [[1,2,3],[4,5,6]] )
print(aaa.stride())
print(aaa.is_contiguous())
#(3,1)
#True

El retorno de stride() (3,1) significa que: al movernos a lo largo de la primera dimensión en cada paso (fila por fila), necesitamos mover 3 pasos en la memoria. Al movernos a lo largo de la segunda dimensión (columna por columna), necesitamos movernos 1 paso en la memoria. Esto indica que los elementos del tensor se almacenan de forma contigua.

Ahora intentamos aplicar funciones come al tensor:

bbb = aaa.transpose(0,1)
print(bbb.stride())
print(bbb.is_contiguous())

#(1, 3)
#False


ccc = aaa.narrow(1,1,2)   ## equivalent to matrix slicing aaa[:,1:3]
print(ccc.stride())
print(ccc.is_contiguous())

#(3, 1)
#False


ddd = aaa.repeat(2,1)   # The first dimension repeat once, the second dimension repeat twice
print(ddd.stride())
print(ddd.is_contiguous())

#(3, 1)
#True


## expand is different from repeat.
## if a tensor has a shape [d1,d2,1], it can only be expanded using "expand(d1,d2,d3)", which
## means the singleton dimension is repeated d3 times
eee = aaa.unsqueeze(2).expand(2,3,3)
print(eee.stride())
print(eee.is_contiguous())

#(3, 1, 0)
#False


fff = aaa.unsqueeze(2).repeat(1,1,8).view(2,-1,2)
print(fff.stride())
print(fff.is_contiguous())

#(24, 2, 1)
#True

Ok, podemos encontrar que transpose (), estrecha () y corte tensor, y expandir () hará que el tensor generado no sea contiguo. Curiosamente, repetir() y ver() no lo hacen no contiguo. Entonces ahora la pregunta es: ¿ qué pasa si uso un tensor no contiguo?

La respuesta es que la función view() no se puede aplicar a un tensor no contiguo. Probablemente esto se deba a que view() requiere que el tensor se almacene de forma contigua para que pueda realizar una remodelación rápida en la memoria. p.ej:

bbb.view(-1,3)

obtendremos el error:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-63-eec5319b0ac5> in <module>()
----> 1 bbb.view(-1,3)

RuntimeError: invalid argument 2: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Call .contiguous() before .view(). at /pytorch/aten/src/TH/generic/THTensor.cpp:203

Para resolver esto, simplemente agregue contiguo() a un tensor no contiguo, para crear una copia contigua y luego aplique view()

bbb.contiguous().view(-1,3)
#tensor([[1., 4., 2.],
        [5., 3., 6.]])
avatar avatar Aug 30 '2019 08:08 avatar

Como en la respuesta anterior contigous() asigna fragmentos de memoria contigous , será útil cuando pasemos tensor al código backend c o c++ donde los tensores se pasan como punteros.

p. vignesh avatar Aug 29 '2018 05:08 p. vignesh