PyTorch - contiguous ()


90

Saya telah melalui contoh model bahasa LSTM ini di github (tautan) . Apa yang dilakukannya secara umum cukup jelas bagi saya. Tapi saya masih berjuang untuk memahami apa contiguous()fungsi panggilan , yang terjadi beberapa kali dalam kode.

Misalnya pada baris 74/75 dari input kode dan urutan target LSTM dibuat. Data (disimpan dalam ids) adalah 2 dimensi dimana dimensi pertama adalah ukuran tumpukan.

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

Jadi sebagai contoh sederhana, saat menggunakan ukuran batch 1 dan seq_length10 inputsdan targetsterlihat seperti ini:

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]

Jadi secara umum pertanyaan saya adalah, apa contiguous()dan mengapa saya membutuhkannya?

Lebih lanjut saya tidak mengerti mengapa metode ini dipanggil untuk urutan target dan tetapi bukan urutan input karena kedua variabel terdiri dari data yang sama.

Bagaimana bisa targetstidak bersebelahan dan inputsmasih bersebelahan?

EDIT: Saya mencoba untuk tidak menelepon contiguous(), tetapi ini mengarah ke pesan kesalahan saat menghitung kerugian.

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

Jadi jelas memanggil contiguous()dalam contoh ini diperlukan.

(Agar ini tetap dapat dibaca, saya menghindari memposting kode lengkap di sini, itu dapat ditemukan dengan menggunakan tautan GitHub di atas.)

Terima kasih sebelumnya!


judul yang lebih deskriptif akan berguna. Saya sarankan Anda memperbaiki judul atau setidaknya menulis a tldr; to the point summarydengan ringkasan yang ringkas.
Charlie Parker


Jawaban:


186

Ada beberapa operasi pada Tensor di PyTorch yang tidak benar-benar mengubah konten tensor, tetapi hanya cara mengonversi indeks ke lokasi tensor ke byte. Operasi ini meliputi:

narrow(), view(), expand()Dantranspose()

Misalnya: ketika Anda memanggil transpose(), PyTorch tidak menghasilkan tensor baru dengan tata letak baru, PyTorch hanya mengubah informasi meta di objek Tensor sehingga offset dan langkah untuk bentuk baru. Tensor yang ditransposisikan dan tensor asli memang berbagi memori!

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

Di sinilah konsep contiguous masuk. Di atas xbersebelahan tetapi ybukan karena tata letak memorinya berbeda dari tensor berbentuk sama yang dibuat dari awal. Perhatikan bahwa kata "contiguous" agak menyesatkan karena bukan konten tensor yang tersebar di sekitar blok memori yang terputus. Di sini byte masih dialokasikan dalam satu blok memori tetapi urutan elemennya berbeda!

Saat Anda memanggil contiguous(), itu sebenarnya membuat salinan tensor sehingga urutan elemen akan sama seperti jika tensor dengan bentuk yang sama dibuat dari awal.

Biasanya Anda tidak perlu khawatir tentang ini. Jika PyTorch mengharapkan tensor yang berdekatan tetapi jika tidak, Anda akan mendapatkan RuntimeError: input is not contiguousdan menambahkan panggilan ke contiguous().


Saya baru saja menemukan ini lagi. Penjelasan Anda sangat bagus! Saya hanya ingin tahu: Jika blok dalam memori tidak tersebar luas, apa masalah tata letak memori yang "berbeda dari tensor dengan bentuk yang sama yang dibuat dari awal" ? Mengapa bersebelahan hanya menjadi persyaratan untuk beberapa operasi?
MBT

4
Saya tidak dapat menjawab ini secara pasti tetapi dugaan saya adalah bahwa beberapa kode PyTorch menggunakan implementasi vektorisasi kinerja tinggi dari operasi yang diimplementasikan dalam C ++ dan kode ini tidak dapat menggunakan offset / langkah sewenang-wenang yang ditentukan dalam informasi meta Tensor. Ini hanya tebakan.
Shital Shah

1
Mengapa callee tidak bisa menelepon contiguous()sendiri?
information_interchange

sangat mungkin, karena Anda tidak menginginkannya dalam jarak yang berdekatan, dan selalu menyenangkan memiliki kendali atas apa yang Anda lakukan.
shivam13juna

2
Operasi tensor populer lainnya adalah permute, yang juga dapat mengembalikan tensor non- "bersebelahan".
Oleg

31

Dari [dokumentasi pytorch] [1]:

contiguous () → Tensor

Returns a contiguous tensor containing the same data as self 

tensor. Jika self tensor berdekatan, fungsi ini mengembalikan tensor diri.

Dimana di contiguoussini berarti tidak hanya bersebelahan dalam memori, tetapi juga dalam urutan yang sama dalam memori dengan urutan indeks: misalnya melakukan transposisi tidak mengubah data dalam memori, itu hanya mengubah peta dari indeks ke penunjuk memori, jika Anda kemudian Menerapkannya contiguous()akan mengubah data dalam memori sehingga peta dari indeks ke lokasi memori adalah peta kanonik. [1]: http://pytorch.org/docs/master/tensors.html


1
Terima kasih atas jawaban Anda! Dapatkah Anda memberi tahu saya mengapa / kapan saya membutuhkan data yang berdekatan? Apakah itu hanya kinerja, atau alasan lain? Apakah PyTorch memerlukan data yang berdekatan untuk beberapa operasi? Mengapa target harus bersebelahan dan masukan tidak?
MBT

Itu hanya untuk kinerja. Saya tidak tahu mengapa kode melakukannya untuk target tetapi tidak untuk input.
patapouf_ai

2
Jadi tampaknya pytorch membutuhkan target di loss agar berdekatan dalam memori, tetapi input neuralnet tidak perlu memenuhi persyaratan ini.
patapouf_ai

2
Terima kasih banyak! Saya pikir ini masuk akal bagi saya, saya perhatikan bahwa contiguous () juga diterapkan pada data output (yang tentu saja sebelumnya adalah input) dalam fungsi maju, jadi output dan target bersebelahan saat menghitung kerugian. Terima kasih banyak!
MBT

1
@patapouf_ai Tidak. Penjelasan Anda tentang hal itu salah. Seperti yang ditunjukkan dalam jawaban yang benar, ini sama sekali bukan tentang blok memori yang berdekatan.
Akaisteph7

14

tensor.contiguous () akan membuat salinan tensor, dan elemen dalam salinan akan disimpan dalam memori dengan cara yang berdekatan. Fungsi contiguous () biasanya diperlukan ketika kita pertama kali mentransposisi () tensor dan kemudian membentuk kembali (view). Pertama, mari buat tensor bersebelahan:

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

The stride () return (3,1) berarti bahwa: saat bergerak di sepanjang dimensi pertama dengan setiap langkah (baris demi baris), kita perlu memindahkan 3 langkah dalam memori. Saat bergerak sepanjang dimensi kedua (kolom demi kolom), kita perlu memindahkan 1 langkah dalam memori. Ini menunjukkan bahwa elemen dalam tensor disimpan secara berdekatan.

Sekarang kami mencoba menerapkan fungsi come ke 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)
#True

Ok, kita bisa menemukan bahwa transpose (), narrow () dan tensor slicing, serta expand () akan membuat tensor yang dihasilkan tidak bersebelahan. Menariknya, repeat () dan view () tidak membuatnya rancu. Jadi sekarang pertanyaannya adalah: apa yang terjadi jika saya menggunakan tensor yang tidak bersebelahan?

Jawabannya adalah fungsi view () tidak dapat diterapkan ke tensor yang tidak bersebelahan. Ini mungkin karena view () mengharuskan tensor disimpan secara berdekatan sehingga dapat melakukan pembentukan ulang cepat dalam memori. misalnya:

bbb.view(-1,3)

kami akan mendapatkan kesalahan:

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

Untuk mengatasi ini, cukup tambahkan contiguous () ke tensor tidak bersebelahan, untuk membuat salinan berdekatan lalu terapkan view ()

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

10

Seperti dalam jawaban sebelumnya contigous () mengalokasikan potongan memori contigous , akan sangat membantu ketika kita meneruskan tensor ke kode backend c atau c ++ di mana tensor diteruskan sebagai pointer


3

Jawaban yang diterima sangat bagus, dan saya mencoba menipu transpose()efek fungsi. Saya membuat dua fungsi yang dapat memeriksa samestorage()dan 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")

Saya memeriksa dan mendapatkan hasil ini sebagai tabel:

fungsi

Anda dapat meninjau kode pemeriksa di bawah, tetapi mari kita berikan satu contoh ketika tensor tidak bersebelahan . Kita tidak bisa begitu saja memanggil view()tensor itu, kita akan membutuhkannya reshape()atau kita juga bisa memanggilnya .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)

Selanjutnya yang perlu diperhatikan ada metode yang membuat tensor bersebelahan dan tidak bersebelahan pada akhirnya. Ada metode yang dapat beroperasi pada penyimpanan yang sama , dan beberapa metode seperti flip()itu akan membuat penyimpanan baru (baca: kloning tensor) sebelum dikembalikan.

Kode pemeriksa:

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

Dari apa yang saya pahami, ini jawaban yang lebih ringkas:

Bersebelahan adalah istilah yang digunakan untuk menunjukkan bahwa tata letak memori tensor tidak sejajar dengan informasi meta-data atau bentuknya yang diiklankan.

Menurut pendapat saya, kata bersebelahan adalah istilah yang membingungkan / menyesatkan karena dalam konteks normal itu berarti ketika memori tidak tersebar di blok yang terputus (yaitu "bersebelahan / terhubung / kontinu").

Beberapa operasi mungkin memerlukan properti bersebelahan ini karena beberapa alasan (kemungkinan besar efisiensi dalam GPU, dll).

Perhatikan bahwa .viewoperasi lain yang mungkin menyebabkan masalah ini. Lihatlah kode berikut yang saya perbaiki hanya dengan memanggil bersebelahan (bukan masalah transpose khas yang menyebabkannya di sini adalah contoh yang menyebabkan ketika RNN tidak senang dengan inputnya):

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

Kesalahan yang biasa saya dapatkan:

RuntimeError: rnn: hx is not contiguous


Sumber / Sumber:

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.