PyTorch'ta “view” yöntemi nasıl çalışır?


208

view()Aşağıdaki kod parçacığı yöntemi hakkında kafam karıştı .

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

Karışıklık şu çizgiyle ilgili.

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

Ne mu tensor.view()işlevi mi? Kullanımını birçok yerde gördüm, ancak parametrelerini nasıl yorumladığını anlayamıyorum.

Eğer view()işleve parametre olarak negatif değerler verirsem ne olur ? Örneğin, ararsam ne olur tensor_variable.view(1, 1, -1)?

Herkes ana view()işlev prensibini bazı örneklerle açıklayabilir mi?

Yanıtlar:


288

Görüntüleme fonksiyonu, tensörü yeniden şekillendirmek içindir.

Bir tensörünüz olduğunu söyle

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

a1 ila 16 (dahil) arasında 16 elemente sahip bir tensördür. Bu tensörü bir 4 x 4tensör haline getirmek için yeniden şekillendirmek istiyorsanız

a = a.view(4, 4)

Şimdi abir 4 x 4tensör olacak . Yeniden şekillendirmeden sonra toplam eleman sayısının aynı kalması gerektiğini unutmayın. Tensörünü yeniden şekillendirme aa 3 x 5tensör uygun olmaz.

Parametre -1'in anlamı nedir?

Kaç satır istediğinizi bilmediğiniz, ancak sütun sayısından emin olduğunuz herhangi bir durum varsa, bunu -1 ile belirleyebilirsiniz. ( Bunu daha fazla boyutlu tansörlere genişletebileceğinizi unutmayın. Eksen değerinden yalnızca biri -1 olabilir ). Bu, kütüphaneye söylemenin bir yoludur: "bana bu kadar sütun içeren bir tensör verin ve bunun gerçekleşmesi için gerekli sayıda uygun satırı hesaplayın".

Bu, yukarıda verdiğiniz sinir ağı kodunda görülebilir. x = self.pool(F.relu(self.conv2(x)))İleri fonksiyondaki çizgiden sonra 16 derinlik özellik haritanız olacak. Tamamen bağlı katmana vermek için bunu düzleştirmeniz gerekir. Böylece, Pytorch'a, belirli sayıda sütuna sahip olduğunuz tensörü yeniden şekillendirmesini ve satır sayısına kendi başına karar vermesini söylersiniz.

Numpy ve pytorch arasında bir benzerlik çizmek, numpy'nin yeniden şekillendirme işlevine viewbenzer .


93
"görünümreshape numpy'nin yeniden şekillenmesine benziyor" - neden sadece PyTorch'ta çağırmadılar ?!
MaxB

54
@MaxB Yeniden şekillendirmenin aksine, "görünüm" tarafından döndürülen yeni tensör, temel verileri orijinal tensörle paylaşır, bu yüzden yepyeni bir tensör oluşturmak yerine eski tensöre bir görünümdür.
qihqi

39
@blckbird "yeniden şekillendirme her zaman belleği kopyalar. görünüm asla belleği kopyalamaz." github.com/torch/cutorch/issues/98
devinbost

3
@devinbost Torch yeniden şekillendirmesi her zaman belleği kopyalar. NumPy yeniden şekillendirme yapmaz.
Tavian Barnes

32

Daha basit olandan daha zor olana bazı örnekler yapalım.

  1. viewYöntem ile aynı veri ile bir tensörünün döner selftensör (döndürülen tensör elemanların aynı sayıda olduğunu vasıtasıyla), ancak farklı bir şekle sahiptir. Örneğin:

    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. -1Parametrelerden biri olmadığını varsayarsak, bunları bir araya getirdiğinizde, sonuç tensördeki eleman sayısına eşit olmalıdır. Bunu yaparsanız a.view(3, 3), RuntimeError16 elemanlı giriş için şekil (3 x 3) geçersiz olduğu için bir neden ortaya çıkar. Başka bir deyişle: 3 x 3 16 değil 9'a eşittir.

  3. İşleve ilettiğiniz -1parametrelerden biri olarak kullanabilirsiniz , ancak yalnızca bir kez. Tüm bunlar, yöntemin bu boyutu nasıl dolduracağınız konusunda sizin için matematik yapmasıdır. Örneğin a.view(2, -1, 4), eşdeğerdir a.view(2, 2, 4). [16 / (2 x 4) = 2]

  4. Döndürülen tensörün aynı verileri paylaştığına dikkat edin . "Görünüm" de bir değişiklik yaparsanız orijinal tensörün verilerini değiştirirsiniz:

    b = a.view(4, 4)
    b[0, 2] = 2
    a[2] == 3.0
    False
  5. Şimdi, daha karmaşık bir kullanım için. Belgeler, her yeni görünüm boyutunun orijinal bir boyutun alt alanı olması veya yalnızca tüm i = 0 için aşağıdaki bitişiklik benzeri koşulu karşılayan d, d + 1, ..., d + k açıklığı olması gerektiğini söylüyor . .., k - 1, adım [i] = adım [i + 1] x boyut [i + 1] . Aksi takdirde, contiguous()tensörün görüntülenebilmesi için çağrılması gerekir. Örneğin:

    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)

    Bildirim için bu a_t, adım [0]! = Adım [1] x-boyutu [1] itibaren 24! = 2 x 3


8

torch.Tensor.view()

Basitçe söylemek gerekirse, torch.Tensor.view()esinlenerek numpy.ndarray.reshape()veya esinlenerek , yeni şekil orijinal tensörün şekli ile uyumlu olduğu sürece, tensörün yeni bir görünümününumpy.reshape() oluşturur .

Somut bir örnek kullanarak bunu ayrıntılı olarak anlayalım.

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

Bu tşekil tensörü ile , yalnızca aşağıdaki şekiller için (18,)yeni görünümler oluşturulabilir:

(1, 18)ya da eşdeğer (1, -1)ya da eşdeğer ya da eşdeğer ya da eşdeğer ya da eşdeğer ya da eşdeğer ya da(-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)

Yukarıdaki şekil tüplerinden zaten gözlemleyebileceğimiz gibi, şekil demetinin elemanlarının (örn. 2*9, 3*6Vb.) Çoğaltılması her zaman orijinal tensördeki toplam 18örneğe eşit olmalıdır ( örneğimizde).

Gözlemlenecek bir başka şey -1de, şekil tupleslerinin her birinde bir yer kullanmış olmamızdır . A kullanarak -1, hesaplamayı kendimiz yapmakta tembel davranıyoruz ve yeni görünümü oluştururken şekil için bu değerin hesaplanmasını yapmak için görevi PyTorch'a devrediyoruz . Unutulmaması gereken önemli bir nokta, şekil demetinde sadece bir tane kullanabileceğimizdir -1. Kalan değerler bizim tarafımızdan açıkça belirtilmelidir. Else PyTorch atarak şikayet edecek RuntimeError:

RuntimeError: yalnızca bir boyut çıkarılabilir

Bu nedenle, yukarıda belirtilen şekillerin hepsiyle, PyTorch her zaman orijinal tensörün yeni bir görünümünü döndürecektir t. Bu temel olarak, istenen yeni görünümlerin her biri için sadece tensörün adım bilgilerini değiştirdiği anlamına gelir.

Aşağıda, her yeni görünümde tansörlerin adımlarının nasıl değiştiğini gösteren bazı örnekler verilmiştir .

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

Şimdi, yeni görünümlerin adımlarını göreceğiz :

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

İşte bu view()fonksiyonun büyüsü . Yeni görünümün şekli orijinal şekliyle uyumlu olduğu sürece, yalnızca yeni görünümlerin her biri için (orijinal) tensörün adımlarını değiştirir .

Adımlar dizilerini gözlemlemek olabilir bir başka ilginç olan bir 0 elemanın değeri olmasıdır inci pozisyon 1 'de elemanının değerine eşittir st şekil başlığın konumu.

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

Bunun nedeni ise:

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

adım (6, 1)olduğunu 0 boyunca sonraki öğeye bir öğeden gitmek diyor inci Zorunda, boyutun atlamak ya da 6 adımları atın. (yani gitmek için 0için 6. bir 6 adım atması gerekir,) Ama 1'de sonraki öğeye bir öğeden gitmek st (örn dan gitmek için boyutun, sadece tek bir adım gerekir 2için 3).

Bu nedenle, adım bilgileri, hesaplamayı gerçekleştirmek için öğelere bellekten nasıl erişildiğinin merkezindedir.


torch.reshape ()

Bu işlev bir görünüm döndürür torch.Tensor.view()ve yeni şekil orijinal tensörün şekliyle uyumlu olduğu sürece kullanmakla tamamen aynıdır . Aksi takdirde bir kopyasını döndürür.

Ancak, notlar şunları torch.reshape()uyarıyor:

bitişik girişler ve uyumlu adımlara sahip girişler kopyalamadan yeniden şekillendirilebilir, ancak kopyalama ve görüntüleme davranışına bağlı olmamalıdır.


1

Parametre 1'in düzleştirme işleminin 1. boyuttan başladığını gösterdiğine ('örnek' boyutunu düzleştirmeden değil) x.view(-1, 16 * 5 * 5)eşdeğer olduğunu anladım. x.flatten(1)Gördüğünüz gibi, ikinci kullanım anlamsal olarak daha net ve kullanımı daha kolay, bu yüzden tercih edin flatten().


1

Parametre -1'in anlamı nedir?

-1Dinamik sayıda parametre veya "herhangi bir şey" olarak okuyabilirsiniz . Çünkü o sadece bir parametre olabilir -1de view().

Bunu sorarsanız , içindeki eleman sayısına bağlı olarak x.view(-1,1)tensör şekli çıkacaktır . Örneğin:[anything, 1]x

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)

Çıktı olacak:

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) boyutu (a, b) olan ağırlıklarla aynı verilere sahip yeni bir tensörü, verileri belleğin başka bir bölümüne kopyalar.

weights.resize_(a, b)farklı bir şekle sahip aynı tensörü döndürür. Ancak, yeni şekil orijinal tensörden daha az elemanla sonuçlanırsa, bazı elemanlar tensörden çıkarılır (ancak hafızadan çıkarılmaz). Yeni şekil orijinal tensörden daha fazla öğeye yol açarsa, yeni öğeler bellekte başlatılmaz.

weights.view(a, b) boyutu (a, b) olan ağırlıklarla aynı verilere sahip yeni bir tensör döndürür


1

Aşağıdaki örneklerle görünümü anlamaya çalışalım:

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

Bir argüman değeri olarak -1, 3d durumunda y, z veya başka bir yolun değerini bildiğimiz sürece, x ifadesinin değerini hesaplamanın kolay bir yoludur ve 2d için yine x ifadesinin değerini hesaplamanın kolay bir yoludur. y ya da tersi değerlerini bilir ..


0

@Jadiel de Armas örneklerini gerçekten çok beğendim.

Öğelerin .view (...) için nasıl sipariş edildiğine ilişkin küçük bir fikir eklemek istiyorum

  • Şekilli bir Tensör için (a, b, c) , elemanlarının sırası bir numaralandırma sistemi tarafından belirlenir: ilk basamağın bir sayısı varsa, ikinci basamağın b sayıları ve üçüncü basamağın c sayıları vardır.
  • .View (...) tarafından döndürülen yeni Tensördeki öğelerin eşlenmesi , orijinal Tensörün bu sırasını korur .
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.