PyTorch - bitişik ()


94

Github (bağlantı) üzerinde bir LSTM dil modeli örneğini inceliyordum . Genel olarak ne yaptığı benim için oldukça açık. Ama hala contiguous()kodda birkaç kez gerçekleşen aramanın ne yaptığını anlamakta zorlanıyorum .

Örneğin kod girişinin 74/75 satırında ve LSTM'nin hedef dizileri yaratılır. Veri (depolanan ids) 2 boyutludur ve birinci boyut, parti boyutudur.

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

Basit bir örnek olarak, parti boyutu 1 ve seq_length10 kullanıldığında inputsve targetsşuna benzer:

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]

Yani genel olarak sorum şu, ne contiguous()işe yarıyor ve neden buna ihtiyacım var?

Ayrıca, yöntemin neden hedef dizi için çağrıldığını ve her iki değişken de aynı verilerden oluştuğundan girdi dizisinin neden çağrıldığını anlamıyorum.

Nasıl bitişik targetsolmayan ve inputshala bitişik olabilir?

DÜZENLEME: Aramayı dışarıda bırakmaya çalıştım contiguous(), ancak bu, kaybı hesaplarken bir hata mesajına yol açıyor.

RuntimeError: invalid argument 1: input is not contiguous at .../src/torch/lib/TH/generic/THTensor.c:231

contiguous()Bu örnekte açıkça aramak gerekli.

(Bunu okunabilir kılmak için kodun tamamını buraya göndermekten kaçındım, yukarıdaki GitHub bağlantısını kullanarak bulunabilir.)

Şimdiden teşekkürler!


daha açıklayıcı bir başlık yararlı olacaktır. Başlığı geliştirmenizi veya en azından tldr; to the point summaryözetle özetle bir yazmanızı öneririm .
Charlie Parker


Yanıtlar:


193

PyTorch'ta Tensor'da tensörün içeriğini gerçekten değiştirmeyen, sadece indekslerin tensöre ve bayt konumuna nasıl dönüştürüleceğine dair birkaç işlem vardır. Bu işlemler şunları içerir:

narrow(), view(), expand()Vetranspose()

Örneğin: aradığınızda transpose(), PyTorch yeni düzen ile yeni tensör oluşturmaz, sadece Tensor nesnesindeki meta bilgileri değiştirir, böylece ofset ve adım yeni şekil içindir. Transpoze tensör ve orijinal tensör gerçekten de hafızayı paylaşıyor!

x = torch.randn(3,2)
y = torch.transpose(x, 0, 1)
x[0, 0] = 42
print(y[0,0])
# prints 42

Bitişik kavramı burada devreye giriyor. Yukarıdakiler xbitişiktir, ancak yhafıza düzeninin sıfırdan yapılmış aynı şekle sahip bir tensörden farklı olması değildir. "Bitişik" kelimesinin biraz yanıltıcı olduğuna dikkat edin, çünkü tensör içeriğinin bağlantısız bellek blokları etrafına yayılması değildir. Burada baytlar hala bir bellek bloğuna tahsis edilmektedir, ancak elemanların sırası farklıdır!

Aradığınızda contiguous(), aslında tensörün bir kopyasını oluşturur, böylece elemanların sırası, aynı şekle sahip tensörün sıfırdan yaratılmasıyla aynı olur.

Normalde bunun için endişelenmenize gerek yoktur. PyTorch bitişik tensör beklerse, ancak değilse, o zaman alırsınız RuntimeError: input is not contiguousve sonra bir çağrı eklersiniz contiguous().


Bununla tekrar karşılaştım. Açıklamanız çok güzel! Sadece merak ediyorum: Hafızadaki bloklar geniş bir alana yayılmıyorsa, "sıfırdan yapılmış aynı şekle sahip bir tensörden farklı" bir hafıza düzeniyle ilgili sorun nedir? Neden bitişik olmak sadece bazı işlemler için zorunludur?
MBT

4
Buna kesin olarak cevap veremem ama tahminimce PyTorch kodunun bir kısmı C ++ 'da uygulanan işlemlerin yüksek performanslı vektörleştirilmiş uygulamasını kullanıyor ve bu kod Tensor'un meta bilgisinde belirtilen keyfi ofset / adımlarını kullanamıyor. Bu sadece bir tahmin.
Shital Şah

1
Aranan uç neden contiguous()kendi kendine arayamadı ?
information_interchange

büyük olasılıkla, çünkü onu bitişik bir şekilde istemiyorsunuz ve yaptığınız şey üzerinde kontrol sahibi olmak her zaman güzeldir.
shivam13juna

2
Diğer bir popüler tensör işlemi permutede "bitişik olmayan" tensörü döndürebilen işlemdir .
Oleg

32

[Pytorch belgelerinden] [1]:

bitişik () → Tensör

Returns a contiguous tensor containing the same data as self 

tensör. Self tensor bitişik ise, bu fonksiyon self tensörü döndürür.

Burada contiguousyalnızca bellekte bitişik değil, aynı zamanda endeks sırası ile aynı sırayla bellekte olduğu anlamına gelir: örneğin, bir aktarım yapmak bellekteki verileri değiştirmez, yalnızca haritayı endekslerden bellek işaretçilerine değiştirir, eğer öyleyse Uygula contiguous(), bellekteki verileri değiştirecek, böylece endekslerden bellek konumuna giden harita kurallı olanı olacaktır. [1]: http://pytorch.org/docs/master/tensors.html


1
Cevabınız için teşekkür ederim! Verilerin bitişik olmasına neden / ne zaman ihtiyacım olduğunu bana söyleyebilir misiniz? Bu sadece performans mı yoksa başka bir sebep mi? PyTorch bazı işlemler için bitişik verilere ihtiyaç duyar mı? Neden hedeflerin bitişik olması gerekirken girdilerin olmaması gerekir?
MBT

Bu sadece performans içindir. Kodların neden hedefler için yaptığını ama girdiler için olmadığını bilmiyorum.
patapouf_ai

2
Öyleyse görünüşe göre pytorch, kayıptaki hedeflerin bellekte bitişik olmasını gerektiriyor, ancak neuralnet'in girdilerinin bu gereksinimi karşılaması gerekmiyor.
patapouf_ai

2
Harika teşekkür ederim! Bunun benim için mantıklı olduğunu düşünüyorum, contiguous () işlevinin ileri işlevdeki çıktı verilerine de (tabii ki daha önce girdiydi) uygulandığını fark ettim, bu nedenle kaybı hesaplarken hem çıktılar hem de hedefler bitişiktir. Çok teşekkürler!
MBT

1
@patapouf_ai Hayır. Sizin açıklamanız yanlış. Doğru cevapta da belirtildiği gibi, bu hiç de bitişik bellek blokları ile ilgili değildir.
Akaisteph7

14

tensor.contiguous (), tensörün bir kopyasını oluşturacak ve kopyadaki öğe, bitişik bir şekilde bellekte saklanacaktır. Contiguous () işlevi genellikle bir tensörü transpoze ettiğimizde () ve sonra yeniden şekillendirdiğimizde (görüntülediğimizde) gereklidir. İlk önce bitişik bir tensör oluşturalım:

aaa = torch.Tensor( [[1,2,3],[4,5,6]] )
print(aaa.stride())
print(aaa.is_contiguous())
#(3,1)
#True

Stride () return (3,1) şu anlama gelir: her adımda (satır satır) ilk boyut boyunca hareket ederken, bellekte 3 adım hareket etmemiz gerekir. İkinci boyut boyunca (sütun sütun) ilerlerken bellekte 1 adım ilerlememiz gerekir. Bu, tensördeki öğelerin bitişik olarak depolandığını gösterir.

Şimdi gel işlevlerini tensöre uygulamaya çalışıyoruz:

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

Tamam, transpose (), dar () ve tensor dilimlemenin ve expand () ' nin üretilen tensörü bitişik yapmayacağını bulabiliriz. İlginç bir şekilde, tekrar () ve görünüm () onu bitişik yapmaz. Şimdi soru şu: bitişik olmayan bir tensör kullanırsam ne olur?

Cevap, view () işlevinin bitişik olmayan bir tensöre uygulanamamasıdır. Bunun nedeni muhtemelen view () tensörün bellekte hızlı bir şekilde yeniden şekillendirilebilmesi için bitişik olarak depolanmasını gerektirmesidir. Örneğin:

bbb.view(-1,3)

hatayı alacağız:

---------------------------------------------------------------------------
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:203

Bunu çözmek için, bitişik bir kopya oluşturmak için bitişik olmayan tensöre contiguous () ekleyin ve ardından view ()

bbb.contiguous().view(-1,3)
#tensor([[1., 4., 2.],
        [5., 3., 6.]])

10

Önceki cevapta olduğu gibi contigous () bitişik bellek parçalarını ayırır , tensörü tensörlerin olduğu yerde c veya c ++ arka uç koduna geçirdiğimizde işaretçiler iletildiği


3

Kabul edilen cevaplar çok harikaydı ve transpose()fonksiyon etkisini kopyalamaya çalıştım . Ben kontrol edebilirsiniz iki işlev yarattı samestorage()ve contiguous.

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

Kontrol ettim ve bu sonucu bir tablo olarak aldım:

fonksiyonlar

Aşağıdaki pulu kodunu inceleyebilirsiniz, ancak tensör bitişik olmadığında bir örnek verelim . view()O tensöre basitçe çağrı yapamayız, ona ihtiyacımız reshape()olur ya da arayabiliriz .contiguous().view().

x = torch.randn(3,2)
y = x.transpose(0, 1)
y.view(6) # RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.
  
x = torch.randn(3,2)
y = x.transpose(0, 1)
y.reshape(6)

x = torch.randn(3,2)
y = x.transpose(0, 1)
y.contiguous().view(6)

Ayrıca , sonunda bitişik ve bitişik olmayan tensörler yaratan yöntemler vardır . Aynı depolamada çalışabilen yöntemler ve yeni bir depolama alanıflip() oluşturacak bazı yöntemler vardır. geri dönmeden önce (okuma: tensörü klonlama).

Kontrol kodu:

import torch
x = torch.randn(3,2)
y = x.transpose(0, 1) # flips two axes
print("\ntranspose")
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nnarrow")
x = torch.randn(3,2)
y = x.narrow(0, 1, 2) #dim, start, len  
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\npermute")
x = torch.randn(3,2)
y = x.permute(1, 0) # sets the axis order
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nview")
x = torch.randn(3,2)
y=x.view(2,3)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nreshape")
x = torch.randn(3,2)
y = x.reshape(6,1)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nflip")
x = torch.randn(3,2)
y = x.flip(0)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nexpand")
x = torch.randn(3,2)
y = x.expand(2,-1,-1)
print(x)
print(y)
contiguous(y)
samestorage(x,y) 

0

Anladığım kadarıyla bu daha özetlenmiş bir cevap:

Bitişik, bir tensörün bellek düzeninin, reklamı yapılan meta-veriler veya şekil bilgileriyle hizalanmadığını belirtmek için kullanılan terimdir.

Kanımca bitişik kelimesi kafa karıştırıcı / yanıltıcı bir terimdir çünkü normal bağlamlarda belleğin bağlantısız bloklarda yayılmadığı (yani "bitişik / bağlantılı / sürekli") anlamına gelir.

Bazı işlemler bazı nedenlerle bu bitişik özelliğe ihtiyaç duyabilir (büyük olasılıkla gpu'da verimlilik vb.).

.viewBu soruna neden olabilecek başka bir işlem olduğunu unutmayın . Sadece bitişik arayarak düzelttiğim aşağıdaki koda bakın (burada buna neden olan tipik devrik sorunu yerine, bir RNN'nin girdisinden memnun olmadığı bir örnektir):

        # normal lstm([loss, grad_prep, train_err]) = lstm(xn)
        n_learner_params = xn_lstm.size(1)
        (lstmh, lstmc) = hs[0] # previous hx from first (standard) lstm i.e. lstm_hx = (lstmh, lstmc) = hs[0]
        if lstmh.size(1) != xn_lstm.size(1): # only true when prev lstm_hx is equal to decoder/controllers hx
            # make sure that h, c from decoder/controller has the right size to go into the meta-optimizer
            expand_size = torch.Size([1,n_learner_params,self.lstm.hidden_size])
            lstmh, lstmc = lstmh.squeeze(0).expand(expand_size).contiguous(), lstmc.squeeze(0).expand(expand_size).contiguous()
        lstm_out, (lstmh, lstmc) = self.lstm(input=xn_lstm, hx=(lstmh, lstmc))

Aldığım hata:

RuntimeError: rnn: hx is not contiguous


Kaynaklar / Kaynak:

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.