Estaba revisando este ejemplo de un modelo de lenguaje LSTM en github (enlace) . Lo que hace en general es bastante claro para mí. Pero todavía estoy luchando por entender qué hace llamar a contiguous() , 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 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, las inputs y targets se ven 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é hace contiguous() y por qué lo necesito?
Además, no entiendo por qué se llama al método para la secuencia de destino y no para la secuencia de entrada, ya que ambas variables se componen de los mismos datos.
¿Cómo podrían targets no ser contiguos y las inputs seguir siendo contiguas?
EDITAR:
Traté de dejar de llamar a contiguous() , pero esto genera 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, es necesario llamar a contiguous() en este ejemplo.
Hay algunas operaciones en 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 a 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 el paso 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 el concepto de contiguo . En el ejemplo anterior, x es contiguo pero y no 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 es que el contenido del tensor se extienda alrededor de bloques de memoria desconectados. Aquí todavía se asignan bytes en un bloque de memoria, ¡pero el orden de los elementos es diferente!
Cuando llamas a 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 necesita preocuparse por esto. En general, puede asumir con seguridad que todo funcionará y esperar hasta que obtenga un RuntimeError: input is not contiguous donde PyTorch espera que un tensor contiguo agregue una llamada a contiguous() .
De la documentación de pytorch :
contiguous() → Tensor
Devuelve un tensor contiguo que contiene los mismos datos que el tensor propio. Si el tensor propio es contiguo, esta función devuelve el tensor propio.
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 aplique contiguous() cambiará los datos en la memoria para que el mapa de los índices a la ubicación de la memoria sea el canónico.
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 lo remodelamos (lo vemos). Primero, creemos un tensor contiguo:
aaa = torch.Tensor( [[1,2,3],[4,5,6]] ) print(aaa.stride()) print(aaa.is_contiguous()) #(3,1) #TrueEl retorno de stride() (3,1) significa que: cuando nos movemos a lo largo de la primera dimensión por cada paso (fila por fila), necesitamos movernos 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 tratamos de 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) #TrueOk, podemos encontrar que transpose(), estrecho() y tensor slicing, y expand() harán que el tensor generado no sea contiguo. Curiosamente, repeat() y view() no lo hacen discontinuo. 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. Esto probablemente se deba a que view() requiere que el tensor se almacene de forma contigua para que pueda remodelarse rápidamente 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:203Para resolver esto, simplemente agregue contiguous() 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.]])