Estaba revisando este ejemplo de un modelo de lenguaje LSTM en github (enlace) . Lo que hace en general me queda bastante claro. Pero todavía estoy luchando por entender qué contiguous()
hace la llamada , lo que ocurre varias veces en el código.
Por ejemplo, en la línea 74/75 de la entrada de código y se crean las secuencias de destino del LSTM. Los datos (almacenados en ids
) son bidimensionales, donde la primera dimensión es el tamaño del lote.
for i in range(0, ids.size(1) - seq_length, seq_length):
# Get batch inputs and targets
inputs = Variable(ids[:, i:i+seq_length])
targets = Variable(ids[:, (i+1):(i+1)+seq_length].contiguous())
Entonces, como un ejemplo simple, cuando se usa el tamaño de lote 1 y seq_length
10 inputs
y se targets
ve así:
inputs Variable containing:
0 1 2 3 4 5 6 7 8 9
[torch.LongTensor of size 1x10]
targets Variable containing:
1 2 3 4 5 6 7 8 9 10
[torch.LongTensor of size 1x10]
Entonces, en general, mi pregunta es, ¿qué es contiguous()
y por qué lo necesito?
Además, no entiendo por qué se llama al método para la secuencia objetivo y no para la secuencia de entrada, ya que ambas variables están compuestas por los mismos datos.
¿Cómo podría targets
ser no contiguo y inputs
seguir siendo contiguo?
EDITAR:
Traté de omitir las llamadas contiguous()
, pero esto lleva a un mensaje de error al calcular la pérdida.
RuntimeError: invalid argument 1: input is not contiguous at .../src/torch/lib/TH/generic/THTensor.c:231
Entonces, obviamente, contiguous()
es necesario llamar en este ejemplo.
(Para mantener esto legible, evité publicar el código completo aquí, se puede encontrar usando el enlace de GitHub anterior).
¡Gracias por adelantado!
tldr; to the point summary
resumen conciso hasta el punto.Respuestas:
Hay pocas operaciones en Tensor en PyTorch que realmente no cambian el contenido del tensor, sino solo cómo convertir índices en tensor en ubicación de bytes. Estas operaciones incluyen:
Por ejemplo: cuando llamas
transpose()
, PyTorch no genera un nuevo tensor con un nuevo diseño, solo modifica la metainformación en el objeto Tensor para que el desplazamiento y la zancada sean para una nueva forma. ¡El tensor transpuesto y el tensor original comparten la 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 . Lo anterior
x
es contiguo, peroy
no porque su diseño de memoria sea diferente a un tensor de la misma forma hecho desde cero. Tenga en cuenta que la palabra "contiguo" es un poco engañosa porque no es que el contenido del tensor se extienda alrededor de bloques de memoria desconectados. Aquí los bytes todavía se asignan en un bloque de memoria, ¡pero el orden de los elementos es diferente!Cuando llama
contiguous()
, en realidad hace una copia del tensor, por lo que el orden de los elementos sería el mismo que si el tensor de la misma forma se creara desde cero.Normalmente, no necesita preocuparse por esto. Si PyTorch espera un tensor contiguo, pero si no es así, lo obtendrá
RuntimeError: input is not contiguous
y luego simplemente agregará una llamada acontiguous()
.fuente
contiguous()
por sí mismo?permute
, que también puede devolver un tensor no "contiguo".De la [documentación de pytorch] [1]:
Donde
contiguous
aquí significa no solo contiguo 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 aplicarlocontiguous()
cambiará los datos en la memoria para que el mapa de índices a la ubicación de la memoria sea el canónico. [1]: http://pytorch.org/docs/master/tensors.htmlfuente
tensor.contiguous () creará una copia del tensor, y el elemento de la copia se almacenará en la memoria de forma contigua. La función contiguous () generalmente se requiere cuando primero transponemos () un tensor y luego lo reformamos (vemos). Primero, creemos un tensor contiguo:
El retorno de stride () (3,1) significa que: cuando nos movemos 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 mover 1 paso en la memoria. Esto indica que los elementos del tensor se almacenan de forma contigua.
Ahora intentamos aplicar las funciones come al tensor:
Ok, podemos encontrar que transponer (), estrecho () y corte de tensor, y expandir () harán que el tensor generado no sea contiguo. Curiosamente, repeat () y view () no lo hacen descontiguo. Entonces ahora la pregunta es: ¿qué sucede si uso un tensor no contiguo?
La respuesta es que la función view () no se puede aplicar a un tensor no contiguo. Esto probablemente 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:
obtendremos el error:
Para resolver esto, simplemente agregue contiguous () a un tensor no contiguo, para crear una copia contigua y luego aplique view ()
fuente
Como en la respuesta anterior contigous () asigna trozos de memoria contigous , será útil cuando estemos pasando el tensor al código de backend c o c ++ donde los tensores se pasan como punteros
fuente
Las respuestas aceptadas fueron geniales, y traté de engañar al
transpose()
efecto de la función. Creé las dos funciones que pueden verificar elsamestorage()
y elcontiguous
.def samestorage(x,y): if x.storage().data_ptr()==y.storage().data_ptr(): print("same storage") else: print("different storage") def contiguous(y): if True==y.is_contiguous(): print("contiguous") else: print("non contiguous")
Revisé y obtuve este resultado como una tabla:
Puede revisar el código de verificación a continuación, pero demos un ejemplo cuando el tensor no es contiguo . No podemos simplemente llamar
view()
a ese tensor, lo necesitaríamosreshape()
o también podríamos llamar.contiguous().view()
.Además, hay que tener en cuenta que existen métodos que crean tensores contiguos y no contiguos al final. Hay métodos que pueden operar en un mismo almacenamiento , y algunos métodos
flip()
crearán un nuevo almacenamiento (léase: clonar el tensor) antes de regresar.El código del verificador:
fuente
Por lo que entiendo, esta es una respuesta más resumida:
En mi opinión, la palabra contiguo es un término confuso / engañoso ya que en contextos normales significa cuando la memoria no se distribuye en bloques desconectados (es decir, su "contiguo / conectado / continuo").
Algunas operaciones pueden necesitar esta propiedad contigua por alguna razón (probablemente la eficiencia en gpu, etc.).
Tenga en cuenta que
.view
es otra operación que podría causar este problema. Mire el siguiente código que solucioné simplemente llamando a contiguous (en lugar del típico problema de transposición que lo causa, aquí hay un ejemplo que es la causa cuando un RNN no está contento con su entrada):Error que solía obtener:
Fuentes / recurso:
fuente