Jak działa metoda „przeglądania” w PyTorch?


205

Nie jestem pewien co do tej metody view()w poniższym fragmencie kodu.

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool  = nn.MaxPool2d(2,2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1   = nn.Linear(16*5*5, 120)
        self.fc2   = nn.Linear(120, 84)
        self.fc3   = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16*5*5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

Moje zamieszanie dotyczy następującej linii.

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

Do czego tensor.view()służy funkcja? Widziałem jego użycie w wielu miejscach, ale nie rozumiem, w jaki sposób interpretuje jego parametry.

Co się stanie, jeśli podam funkcji wartości ujemne jako parametry view()? Na przykład, co się stanie, jeśli zadzwonię tensor_variable.view(1, 1, -1)?

Czy ktoś może wyjaśnić główną zasadę view()działania na kilku przykładach?

Odpowiedzi:


283

Funkcja widoku ma na celu przekształcenie tensora.

Powiedz, że masz tensora

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

ajest tensorem, który ma 16 elementów od 1 do 16 (w zestawie). Jeśli chcesz przekształcić ten tensor, aby był 4 x 4tensorem, możesz użyć

a = a.view(4, 4)

Teraz abędzie 4 x 4tensor. Pamiętaj, że po zmianie kształtu całkowita liczba elementów musi pozostać taka sama. Przekształcenie tensora aw 3 x 5tensor nie byłoby właściwe.

Jakie jest znaczenie parametru -1?

Jeśli istnieje jakakolwiek sytuacja, w której nie wiesz, ile wierszy chcesz, ale jesteś pewien liczby kolumn, możesz to określić za pomocą -1. ( Zauważ, że możesz to rozszerzyć na tensory o większej liczbie wymiarów. Tylko jedna wartość osi może wynosić -1 ). Oto sposób powiedzenia bibliotece: „daj mi tensor, który ma tyle kolumn, a obliczysz odpowiednią liczbę wierszy niezbędną do tego, aby tak się stało”.

Można to zobaczyć w powyższym kodzie sieci neuronowej. Po linii x = self.pool(F.relu(self.conv2(x)))w funkcji przewijania pojawi się mapa cech 16 głębokości. Musisz go spłaszczyć, aby uzyskać w pełni połączoną warstwę. Mówisz pytorchowi, aby przekształcił uzyskany tensor tak, aby miał określoną liczbę kolumn, i sam decyduje o liczbie wierszy.

Rysowanie podobieństwa między numpy i pytorch viewjest podobne do funkcji przekształcania numpy .


93
„widok jest podobny do przekształcania numpy” - dlaczego nie nazwali go po prostu reshapew PyTorch ?!
MaxB

54
@MaxB W przeciwieństwie do przekształcenia, nowy tensor zwrócony przez „widok” dzieli podstawowe dane z oryginalnym tensorem, więc tak naprawdę jest to widok starego tensora zamiast tworzenia zupełnie nowego.
qihqi

37
@blckbird „zmiana kształtu zawsze kopiuje pamięć. widok nigdy nie kopiuje pamięci”. github.com/torch/cutorch/issues/98
devinbost

3
@devinbost Zmiana pochodni zawsze kopiuje pamięć. Przekształcenie NumPy nie.
Tavian Barnes

32

Zróbmy kilka przykładów, od prostszych do trudniejszych.

  1. viewSposób powraca tensora o tych samych danych, co selftensora (co oznacza, że zwrócona napinacz ma taką samą liczbę elementów), ale o innym kształcie. Na przykład:

    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. Zakładając, że -1nie jest to jeden z parametrów, po pomnożeniu ich razem wynik musi być równy liczbie elementów w tensorze. Jeśli to zrobisz: a.view(3, 3)podniesie RuntimeErrorkształt ponieważ (3 x 3) jest nieprawidłowy dla danych wejściowych z 16 elementami. Innymi słowy: 3 x 3 nie jest równe 16, ale 9.

  3. Możesz użyć -1jako jednego z parametrów przekazywanych do funkcji, ale tylko raz. Wszystko, co się dzieje, polega na tym, że metoda zrobi dla ciebie matematykę, jak wypełnić ten wymiar. Na przykład a.view(2, -1, 4)jest równoważne z a.view(2, 2, 4). [16 / (2 x 4) = 2]

  4. Zauważ, że zwrócony tensor ma te same dane . Jeśli wprowadzisz zmianę w „widoku”, zmienisz dane pierwotnego tensora:

    b = a.view(4, 4)
    b[0, 2] = 2
    a[2] == 3.0
    False
  5. Teraz dla bardziej złożonego przypadku użycia. Dokumentacja mówi, że każdy nowy wymiar widoku musi być podprzestrzenią oryginalnego wymiaru lub tylko rozpiętością d, d + 1, ..., d + k, które spełniają następujący warunek podobny do ciągłości, który dla wszystkich i = 0 ,. .., k - 1, krok [i] = krok [i + 1] x rozmiar [i + 1] . W przeciwnym razie contiguous()należy wywołać, zanim będzie można zobaczyć tensor. Na przykład:

    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)

    Zauważ, że dla a_t, stride [0]! = Stride [1] x size [1] od 24! = 2 x 3


6

torch.Tensor.view()

Mówiąc prosto, torch.Tensor.view()który jest inspirowany przez numpy.ndarray.reshape()lub numpy.reshape(), tworzy nowy widok tensora, o ile nowy kształt jest zgodny z kształtem oryginalnego tensora.

Rozumiemy to szczegółowo na konkretnym przykładzie.

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

Za pomocą tego tensora tkształtu można tworzyć (18,)nowe widoki tylko dla następujących kształtów:

(1, 18)lub równoważnie (1, -1)lub lub równoważnie lub lub równoważnie lub lub równoważnie lub lub równoważnie lub lub równoważnie lub(-1, 18)
(2, 9)(2, -1)(-1, 9)
(3, 6)(3, -1)(-1, 6)
(6, 3)(6, -1)(-1, 3)
(9, 2)(9, -1)(-1, 2)
(18, 1)(18, -1)(-1, 1)

Jak możemy już obserwować z powyższych krotki kształtu, mnożenie elementów krotki kształt (np 2*9, 3*6etc.) zawsze musi być równa całkowitej liczbie elementów w oryginalnym tensora ( 18w naszym przykładzie).

Kolejną rzeczą do zaobserwowania jest to, że użyliśmy a -1w jednym z miejsc w każdym krotce kształtu. Korzystając z a -1, jesteśmy leniwi w samodzielnym wykonywaniu obliczeń i raczej przekazujemy zadanie PyTorchowi, aby wykonał obliczenia tej wartości kształtu podczas tworzenia nowego widoku . Jedną ważną rzeczą do zapamiętania jest to, że możemy użyć tylko jednego -1krotki w kształcie. Pozostałe wartości powinny być przez nas wyraźnie podane. W przeciwnym razie PyTorch będzie narzekał, rzucając RuntimeError:

RuntimeError: można wywnioskować tylko jeden wymiar

Tak więc, przy wszystkich wyżej wymienionych kształtach, PyTorch zawsze zwraca nowy widok oryginalnego tensora t. Zasadniczo oznacza to, że po prostu zmienia informacje o kroku napinacza dla każdego żądanego nowego widoku.

Poniżej znajduje się kilka przykładów ilustrujących, w jaki sposób postępy tensorów są zmieniane przy każdym nowym widoku .

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

Teraz zobaczymy postępy dla nowych widoków :

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

To jest magia tej view()funkcji. Po prostu zmienia kroki (pierwotnego) tensora dla każdego z nowych widoków , o ile kształt nowego widoku jest zgodny z oryginalnym kształtem.

Inną ciekawą rzeczą może obserwować jeden z krokami krotek jest to, że wartość elementu w 0 th pozycji jest równa wartości elementu w 1 st położenia kształtu krotki.

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

To dlatego, że:

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

krok (6, 1)mówi, że aby przejść od jednego elementu do następnego elementu wzdłuż zerowego wymiaru, musimy przeskoczyć lub zrobić 6 kroków. (tzn. aby przejść od 0do 6, trzeba zrobić 6 kroków.) Ale aby przejść od jednego elementu do następnego elementu w pierwszym wymiarze, wystarczy tylko jeden krok (na przykład, aby przejść od 2do 3).

Tak więc informacja o krokach ma kluczowe znaczenie dla dostępu do elementów z pamięci w celu wykonania obliczeń.


torch.reshape ()

Ta funkcja zwróci widok i jest dokładnie taka sama, jak użycie, torch.Tensor.view()o ile nowy kształt jest zgodny z kształtem oryginalnego tensora. W przeciwnym razie zwróci kopię.

Jednak notatki torch.reshape()ostrzegają, że:

ciągłe dane wejściowe i dane wejściowe ze zgodnymi krokami można przekształcać bez kopiowania, ale nie należy polegać na kopiowaniu a oglądaniu.


1

Doszedłem do wniosku, że x.view(-1, 16 * 5 * 5)jest to równoważne z tym x.flatten(1), że parametr 1 wskazuje, że proces spłaszczania rozpoczyna się od pierwszego wymiaru (nie spłaszczając wymiaru „próbki”) Jak widać, to drugie użycie jest semantycznie bardziej przejrzyste i łatwiejsze w użyciu, więc I wolę flatten().


1

Jakie jest znaczenie parametru -1?

Możesz odczytać -1jako dynamiczną liczbę parametrów lub „cokolwiek”. Z tego powodu nie może być tylko jeden parametr -1w view().

Jeśli poprosisz x.view(-1,1), wyświetli kształt tensora w [anything, 1]zależności od liczby elementów w x. Na przykład:

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)

Wyjdzie:

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

1

weights.reshape(a, b) zwróci nowy tensor z tymi samymi danymi co wagi o rozmiarze (a, b), ponieważ w nim kopiuje dane do innej części pamięci.

weights.resize_(a, b)zwraca ten sam tensor o innym kształcie. Jeśli jednak nowy kształt spowoduje mniej elementów niż pierwotny tensor, niektóre elementy zostaną usunięte z tensora (ale nie z pamięci). Jeśli nowy kształt spowoduje powstanie większej liczby elementów niż oryginalny tensor, nowe elementy nie zostaną zainicjowane w pamięci.

weights.view(a, b) zwróci nowy tensor z tymi samymi danymi co wagi o rozmiarze (a, b)


0

Bardzo podobały mi się przykłady @Jadiel de Armas.

Chciałbym dodać mały wgląd w sposób zamawiania elementów dla .view (...)

  • Dla tensora w kształcie (a, b, c) The celu na jego elementy są określane przez system numeracji: gdy pierwsze cyfry, a cyfry druga cyfra jest b numery i trzecia cyfra jest c liczby.
  • Odwzorowanie elementów w nowym Tensorze zwrócone przez .view (...) zachowuje tę kolejność oryginalnego Tensora.

0

Spróbujmy zrozumieć widok za pomocą następujących przykładów:

    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 jako wartość argumentu jest łatwym sposobem obliczenia wartości powiedz x, pod warunkiem, że znamy wartości y, z lub odwrotnie w przypadku 3d, a dla 2d ponownie łatwym sposobem obliczenia wartości powiedz x znać wartości y lub vice versa ..

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.