Przeglądałem ten przykład modelu języka LSTM na github (link) . Ogólnie rzecz biorąc, jest dla mnie całkiem jasne. Ale wciąż staram się zrozumieć, co contiguous()
robi wywołanie , co występuje kilka razy w kodzie.
Na przykład w linii 74/75 kodu wejściowego i sekwencji docelowej LSTM są tworzone. Dane (przechowywane w ids
) są dwuwymiarowe, gdzie pierwszy wymiar to rozmiar partii.
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())
A więc jako prosty przykład, gdy używasz rozmiaru partii 1 i seq_length
10 inputs
i targets
wygląda to tak:
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]
Więc ogólnie moje pytanie brzmi: co contiguous()
i dlaczego tego potrzebuję?
Ponadto nie rozumiem, dlaczego metoda jest wywoływana dla sekwencji docelowej, a nie dla sekwencji wejściowej, ponieważ obie zmienne zawierają te same dane.
Jak mogłoby targets
być nieciągłe i inputs
nadal przylegać do siebie?
EDYCJA:
Próbowałem pominąć wywołanie contiguous()
, ale prowadzi to do komunikatu o błędzie podczas obliczania straty.
RuntimeError: invalid argument 1: input is not contiguous at .../src/torch/lib/TH/generic/THTensor.c:231
Więc oczywiście wezwanie contiguous()
w tym przykładzie jest konieczne.
(Aby zachować czytelność, unikałem publikowania tutaj pełnego kodu, można go znaleźć, korzystając z powyższego linku GitHub).
Z góry dziękuję!
tldr; to the point summary
zwięzłe do punktacji podsumowanie.Odpowiedzi:
Jest kilka operacji na Tensor w PyTorch, które tak naprawdę nie zmieniają zawartości tensora, ale tylko przekształcają indeksy w tensor na lokalizację bajtów. Te operacje obejmują:
Na przykład: kiedy wywołujesz
transpose()
, PyTorch nie generuje nowego tensora z nowym układem, po prostu modyfikuje metainformacje w obiekcie Tensor, więc offset i stride są dla nowego kształtu. Transponowany tensor i oryginalny tensor rzeczywiście współużytkują pamięć!x = torch.randn(3,2) y = torch.transpose(x, 0, 1) x[0, 0] = 42 print(y[0,0]) # prints 42
Tutaj pojawia się pojęcie ciągłości . Powyższe
x
jest ciągłe, aley
nie jest, ponieważ jego układ pamięci jest inny niż tensor o tym samym kształcie, wykonany od podstaw. Zauważ, że słowo „ciągły” jest nieco mylące, ponieważ nie chodzi o to, że zawartość tensora jest rozłożona wokół odłączonych bloków pamięci. Tutaj bajty są nadal przydzielane w jednym bloku pamięci, ale kolejność elementów jest inna!Kiedy wywołujesz
contiguous()
, faktycznie tworzy kopię tensora, więc kolejność elementów byłaby taka sama, jak gdyby tensor o tym samym kształcie został utworzony od podstaw.Zwykle nie musisz się tym martwić. Jeśli PyTorch oczekuje ciągłego tensora, ale jeśli nie, otrzymasz,
RuntimeError: input is not contiguous
a następnie po prostu dodaj wywołanie docontiguous()
.źródło
contiguous()
sam?permute
, która również może zwrócić tensor nie „ciągły”.Z [dokumentacji ogrodniczej] [1]:
Gdzie
contiguous
tutaj oznacza nie tylko ciągłe w pamięci, ale także w tej samej kolejności w pamięci co kolejność indeksów: na przykład wykonanie transpozycji nie zmienia danych w pamięci, po prostu zmienia mapę z indeksów na wskaźniki pamięci, jeśli wtedy jego zastosowaniecontiguous()
spowoduje zmianę danych w pamięci, tak aby mapa z indeksów do lokalizacji pamięci była kanoniczna. [1]: http://pytorch.org/docs/master/tensors.htmlźródło
tensor.contiguous () utworzy kopię tensora, a element w kopii zostanie zapisany w pamięci w sposób ciągły. Funkcja contiguous () jest zwykle wymagana, gdy najpierw transponujemy () tensor, a następnie zmieniamy jego kształt (wyświetlamy). Najpierw utwórzmy ciągły tensor:
Stride () return (3,1) oznacza, że: poruszając się po pierwszym wymiarze o każdy krok (rząd po rzędzie), musimy przejść 3 kroki w pamięci. Poruszając się po drugim wymiarze (kolumna po kolumnie), musimy przejść o 1 krok w pamięci. Oznacza to, że elementy w tensorze są przechowywane w sposób ciągły.
Teraz spróbujemy zastosować funkcje come do tensora:
Ok, możemy stwierdzić, że transpose (), narrow () i tensor slicing oraz expand () sprawią, że wygenerowany tensor nie będzie ciągły. Co ciekawe, powtarzanie () i widok () nie powodują, że jest on niejednoznaczny. Więc teraz pytanie brzmi: co się stanie, jeśli użyję nieciągłego tensora?
Odpowiedź brzmi: funkcja view () nie może być zastosowana do nieciągłego tensora. Dzieje się tak prawdopodobnie dlatego, że metoda view () wymaga, aby tensor był przechowywany w sposób ciągły, aby mógł szybko zmienić kształt w pamięci. na przykład:
otrzymamy błąd:
Aby rozwiązać ten problem, po prostu dodaj ciągłe () do nieciągłego tensora, aby utworzyć ciągłą kopię, a następnie zastosuj widok ()
źródło
Podobnie jak w poprzedniej odpowiedzi, contigous () przydziela ciągłe fragmenty pamięci , przyda się, gdy przekazujemy tensor do kodu zaplecza c lub c ++, w którym tensory są przekazywane jako wskaźniki
źródło
Przyjęte odpowiedzi były tak świetne, że starałem się oszukać
transpose()
efekt funkcji. Stworzyłem dwie funkcje, które mogą sprawdzaćsamestorage()
icontiguous
.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")
Sprawdziłem i otrzymałem ten wynik w postaci tabeli:
Możesz przejrzeć kod kontrolera poniżej, ale podajmy jeden przykład, kiedy tensor nie jest ciągły . Nie możemy po prostu wywołać
view()
tego tensora, potrzebowalibyśmyreshape()
go lub moglibyśmy również wywołać.contiguous().view()
.Co więcej, istnieją metody, które na końcu tworzą ciągłe i nieciągłe tensory. Istnieją metody, które mogą działać na tym samym magazynie , a niektóre z
flip()
nich utworzą nowy magazyn (czytaj: sklonuj tensor) przed powrotem.Kod kontrolny:
źródło
Z tego, co rozumiem, bardziej podsumowana odpowiedź:
Moim zdaniem słowo ciągły jest terminem mylącym / wprowadzającym w błąd, ponieważ w normalnych kontekstach oznacza to, że pamięć nie jest rozłożona w rozłączonych blokach (tj. Jest „ciągła / połączona / ciągła”).
Niektóre operacje mogą potrzebować tej ciągłej właściwości z jakiegoś powodu (najprawdopodobniej wydajność w GPU itp.).
Zauważ, że
.view
jest to kolejna operacja, która może powodować ten problem. Spójrz na następujący kod, który naprawiłem, po prostu wywołując ciągły (zamiast typowego problemu z transpozycją powodującego go tutaj jest przykład, który jest przyczyną, gdy RNN nie jest zadowolony ze swoich danych wejściowych):Błąd, który otrzymałem:
Źródła / zasoby:
źródło