PyTorch - ติดกัน ()


93

ผมจะผ่านตัวอย่างของรูปแบบภาษา LSTM นี้บน GitHub (ลิงค์) สิ่งที่ทำโดยทั่วไปค่อนข้างชัดเจนสำหรับฉัน แต่ฉันยังคงดิ้นรนเพื่อทำความเข้าใจว่าการโทรcontiguous()ทำอะไรซึ่งเกิดขึ้นหลายครั้งในรหัส

ตัวอย่างเช่นในบรรทัด 74/75 ของการป้อนรหัสและลำดับเป้าหมายของ LSTM จะถูกสร้างขึ้น ข้อมูล (เก็บไว้ในids) เป็น 2 มิติโดยที่มิติแรกคือขนาดแบทช์

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

ตัวอย่างง่ายๆเมื่อใช้ขนาดแบทช์ 1 และseq_length10 inputsและtargetsมีลักษณะดังนี้:

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]

โดยทั่วไปคำถามของฉันคืออะไรcontiguous()และทำไมฉันถึงต้องการ?

นอกจากนี้ฉันไม่เข้าใจว่าเหตุใดจึงเรียกวิธีนี้สำหรับลำดับเป้าหมาย แต่ไม่ใช่ลำดับการป้อนข้อมูลเนื่องจากตัวแปรทั้งสองประกอบด้วยข้อมูลเดียวกัน

จะไม่targetsชัดเจนและinputsยังคงติดกันได้อย่างไร?

แก้ไข: ฉันพยายามที่จะไม่โทรออกcontiguous()แต่สิ่งนี้นำไปสู่ข้อความแสดงข้อผิดพลาดเมื่อคำนวณการสูญเสีย

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

เห็นได้ชัดว่าการเรียกcontiguous()ในตัวอย่างนี้เป็นสิ่งจำเป็น

(เพื่อให้สามารถอ่านได้ฉันจึงหลีกเลี่ยงการโพสต์โค้ดแบบเต็มที่นี่สามารถพบได้โดยใช้ลิงก์ GitHub ด้านบน)

ขอบคุณล่วงหน้า!


ชื่อที่สื่อความหมายมากขึ้นจะเป็นประโยชน์ ฉันขอแนะนำให้คุณปรับปรุงชื่อเรื่องหรืออย่างน้อยก็เขียนtldr; to the point summaryโดยมีความกระชับเพื่อสรุปประเด็น
Charlie Parker


คำตอบ:


193

มีการดำเนินการบางอย่างบน Tensor ใน PyTorch ที่ไม่ได้เปลี่ยนเนื้อหาของเทนเซอร์ แต่เป็นเพียงวิธีการแปลงดัชนีในตำแหน่งเทนเซอร์เป็นไบต์ การดำเนินการเหล่านี้ ได้แก่ :

narrow(), view(), expand()และtranspose()

ตัวอย่างเช่นเมื่อคุณเรียกtranspose()PyTorch จะไม่สร้างเทนเซอร์ใหม่พร้อมเลย์เอาต์ใหม่เพียงแค่ปรับเปลี่ยนข้อมูลเมตาในวัตถุ Tensor ดังนั้นออฟเซ็ตและก้าวย่างจึงเป็นรูปร่างใหม่ เทนเซอร์ขนย้ายและเทนเซอร์ดั้งเดิมกำลังแบ่งปันหน่วยความจำ!

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

นี่คือจุดที่แนวคิดของการต่อเนื่องเข้ามาด้านบนxเป็นสิ่งที่ต่อเนื่องกัน แต่yไม่ใช่เพราะเค้าโครงหน่วยความจำแตกต่างจากเทนเซอร์ที่มีรูปร่างเหมือนกันที่สร้างขึ้นตั้งแต่เริ่มต้น โปรดทราบว่าคำว่า"ต่อเนื่องกัน"นั้นทำให้เข้าใจผิดเล็กน้อยเนื่องจากไม่ใช่ว่าเนื้อหาของเทนเซอร์จะกระจายออกไปรอบ ๆ บล็อกหน่วยความจำที่ไม่ได้เชื่อมต่อ ไบต์ที่นี่ยังคงถูกจัดสรรไว้ในหน่วยความจำบล็อกเดียว แต่ลำดับขององค์ประกอบต่างกัน

เมื่อคุณเรียกcontiguous()มันจะสร้างสำเนาของเทนเซอร์ดังนั้นลำดับขององค์ประกอบจะเหมือนกับว่าเทนเซอร์ของรูปร่างเดียวกันที่สร้างขึ้นตั้งแต่เริ่มต้น

โดยปกติคุณไม่จำเป็นต้องกังวลเกี่ยวกับเรื่องนี้ หาก PyTorch คาดว่าเมตริกซ์ที่ต่อเนื่องกัน แต่ถ้ามันไม่ได้แล้วคุณจะได้รับและจากนั้นคุณเพียงแค่เพิ่มการเรียกร้องให้RuntimeError: input is not contiguouscontiguous()


เพิ่งมาเจอแบบนี้อีก คำอธิบายของคุณดีมาก! ฉันแค่สงสัยว่า: ถ้าบล็อกในหน่วยความจำไม่กระจายอย่างกว้างขวางปัญหาของเค้าโครงหน่วยความจำที่"แตกต่างจากเทนเซอร์ที่มีรูปร่างเหมือนกันจากรอยขีดข่วน"คืออะไร? เหตุใดจึงเป็นเพียงข้อกำหนดที่ต่อเนื่องกันสำหรับการดำเนินการบางอย่าง
MBT

4
ฉันไม่สามารถตอบสิ่งนี้ได้อย่างชัดเจน แต่การคาดเดาของฉันคือโค้ด PyTorch บางตัวใช้การใช้งานเวกเตอร์ที่มีประสิทธิภาพสูงของการดำเนินการที่ดำเนินการใน C ++ และรหัสนี้ไม่สามารถใช้ออฟเซ็ต / ความก้าวหน้าตามอำเภอใจที่ระบุในข้อมูลเมตาของ Tensor นี่เป็นเพียงการคาดเดาเท่านั้น
Shital Shah

1
ทำไม Callee ไม่สามารถเรียกcontiguous()ด้วยตัวเองได้?
information_interchange

อาจเป็นไปได้มากเพราะคุณไม่ต้องการให้มันติดต่อกันและเป็นเรื่องดีเสมอที่คุณจะควบคุมสิ่งที่คุณทำ
shivam13juna

2
การทำงานของเทนเซอร์ที่ได้รับความนิยมอีกอย่างหนึ่งคือpermuteซึ่งอาจส่งคืนเทนเซอร์ที่ไม่ "ต่อเนื่อง"
Oleg

32

จาก [เอกสาร pytorch] [1]:

ติดกัน () → Tensor

Returns a contiguous tensor containing the same data as self 

เทนเซอร์. ถ้าเทนเซอร์ตัวเองติดกันฟังก์ชันนี้จะส่งกลับค่าเทนเซอร์ตัวเอง

ในที่contiguousนี้หมายถึงไม่เพียง แต่อยู่ติดกันในหน่วยความจำ แต่ยังอยู่ในลำดับเดียวกันในหน่วยความจำเช่นเดียวกับลำดับดัชนีตัวอย่างเช่นการย้ายตำแหน่งไม่ได้เปลี่ยนข้อมูลในหน่วยความจำเพียงแค่เปลี่ยนแผนที่จากดัชนีเป็นตัวชี้หน่วยความจำหากคุณเป็นเช่นนั้น ใช้contiguous()มันจะเปลี่ยนข้อมูลในหน่วยความจำเพื่อให้แผนที่จากดัชนีไปยังตำแหน่งหน่วยความจำเป็นข้อมูลที่ยอมรับได้ [1]: http://pytorch.org/docs/master/tensors.html


1
ขอบคุณสำหรับคำตอบ! คุณบอกฉันได้ไหมว่าทำไม / เมื่อฉันต้องการให้ข้อมูลติดกัน เป็นเพียงประสิทธิภาพหรือเหตุผลอื่น ๆ ? PyTorch ต้องการข้อมูลที่ต่อเนื่องกันสำหรับการดำเนินการบางอย่างหรือไม่? เหตุใดเป้าหมายจึงต้องอยู่ติดกันและปัจจัยการผลิตไม่
MBT

มันเป็นเพียงเพื่อประสิทธิภาพ ฉันไม่รู้ว่าทำไมโค้ดถึงใช้กับเป้าหมาย แต่ไม่ใช่สำหรับอินพุต
patapouf_ai

2
เห็นได้ชัดว่า pytorch ต้องการให้เป้าหมายในการสูญเสียมีความจำเป็นในหน่วยความจำ แต่อินพุตของ neuralnet ไม่จำเป็นต้องเป็นไปตามข้อกำหนดนี้
patapouf_ai

2
ขอบคุณมาก! ฉันคิดว่านี่สมเหตุสมผลสำหรับฉันฉันสังเกตเห็นว่า () ที่ต่อเนื่องกันนั้นถูกนำไปใช้กับข้อมูลเอาต์พุต (ซึ่งแน่นอนว่าเดิมคืออินพุต) ในฟังก์ชันไปข้างหน้าดังนั้นทั้งเอาต์พุตและเป้าหมายจึงติดกันเมื่อคำนวณการสูญเสีย ขอบคุณมาก!
MBT

1
@patapouf_ai ไม่คำอธิบายของคุณไม่ถูกต้อง ตามที่ระบุไว้ในคำตอบที่ถูกต้องไม่เกี่ยวกับบล็อกหน่วยความจำที่ต่อเนื่องกันเลย
Akaisteph7

14

tensor.contiguous () จะสร้างสำเนาของเทนเซอร์และองค์ประกอบในสำเนาจะถูกเก็บไว้ในหน่วยความจำในลักษณะที่ต่อเนื่องกัน โดยปกติจะต้องใช้ฟังก์ชันต่อเนื่อง () เมื่อเราทรานสโพส () เทนเซอร์ () เทนเซอร์ก่อนแล้วจึงปรับรูปร่างใหม่ (ดู) ก่อนอื่นมาสร้างเทนเซอร์ที่ต่อเนื่องกัน:

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

stride () return (3,1) หมายความว่า: เมื่อเคลื่อนที่ไปตามมิติแรกทีละขั้นตอน (ทีละแถว) เราต้องย้าย 3 ขั้นในหน่วยความจำ เมื่อย้ายไปตามมิติที่สอง (คอลัมน์ทีละคอลัมน์) เราต้องย้าย 1 ขั้นตอนในหน่วยความจำ สิ่งนี้บ่งชี้ว่าองค์ประกอบในเทนเซอร์ถูกจัดเก็บอย่างต่อเนื่องกัน

ตอนนี้เราลองใช้ฟังก์ชัน come กับเทนเซอร์:

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

โอเคเราจะพบว่าการแบ่งส่วนทรานสโพส () แคบ () และเทนเซอร์และขยาย ()จะทำให้เทนเซอร์ที่สร้างขึ้นไม่ติดกัน สิ่งที่น่าสนใจคือการทำซ้ำ () และ view () ไม่ได้ทำให้ไม่สอดคล้องกัน ตอนนี้คำถามคือ: จะเกิดอะไรขึ้นถ้าฉันใช้เทนเซอร์ที่ไม่ต่อเนื่องกัน?

คำตอบคือฟังก์ชัน view () ไม่สามารถใช้กับเทนเซอร์ที่ไม่ติดกันได้ อาจเป็นเพราะ view () ต้องการให้มีการจัดเก็บเทนเซอร์ที่ต่อเนื่องกันเพื่อให้สามารถปรับรูปร่างใหม่ได้อย่างรวดเร็วในหน่วยความจำ เช่น:

bbb.view(-1,3)

เราจะได้รับข้อผิดพลาด:

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

ในการแก้ปัญหานี้เพียงแค่เพิ่ม contiguous () ไปยังเทนเซอร์ที่ไม่ติดกันเพื่อสร้างสำเนาที่อยู่ติดกันจากนั้นใช้มุมมอง ()

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

10

เช่นเดียวกับในคำตอบก่อนหน้า contigous () จัดสรรหน่วยความจำที่ต่อเนื่องกันมันจะมีประโยชน์เมื่อเราส่งรหัสแบ็กเอนด์เทนเซอร์ไปยัง c หรือ c ++ซึ่งเทนเซอร์จะถูกส่งผ่านเป็นพอยน์เตอร์


3

คำตอบที่ยอมรับนั้นยอดเยี่ยมมากและฉันพยายามหลอกล่อเอtranspose()ฟเฟกต์ฟังก์ชัน ฉันสร้างสองฟังก์ชันที่สามารถตรวจสอบsamestorage()และ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")

ฉันตรวจสอบและได้ผลลัพธ์นี้เป็นตาราง:

ฟังก์ชั่น

คุณสามารถตรวจสอบรหัสการตรวจสอบการลงด้านล่าง แต่ขอให้เป็นตัวอย่างหนึ่งเมื่อเมตริกซ์เป็นที่ไม่ต่อเนื่องกัน เราไม่สามารถเรียกview()ใช้เทนเซอร์นั้นได้ง่ายๆเราจำเป็นต้องใช้reshape()มันหรือเราสามารถเรียก.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)

นอกจากนี้ยังมีวิธีการที่สร้างเทนเซอร์ที่ต่อเนื่องกันและไม่ติดกันในตอนท้าย มีวิธีการที่สามารถทำงานบนที่เก็บข้อมูลเดียวกันและบางวิธีflip()ที่จะสร้างที่เก็บข้อมูลใหม่ (อ่าน: โคลนเทนเซอร์) ก่อนส่งคืน

รหัสตัวตรวจสอบ:

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

จากสิ่งที่ฉันเข้าใจคำตอบที่สรุปเพิ่มเติม:

คำที่ติดกันเป็นคำที่ใช้เพื่อระบุว่าเค้าโครงหน่วยความจำของเทนเซอร์ไม่สอดคล้องกับข้อมูลเมตาดาต้าที่โฆษณาหรือข้อมูลรูปร่าง

ในความคิดของฉันคำว่าติดกันเป็นคำที่สับสน / ทำให้เข้าใจผิดเนื่องจากในบริบทปกติหมายถึงเมื่อหน่วยความจำไม่กระจายไปรอบ ๆ ในบล็อกที่ขาดการเชื่อมต่อ (เช่นคำว่า "ต่อเนื่อง / เชื่อมต่อ / ต่อเนื่อง")

การดำเนินการบางอย่างอาจต้องการคุณสมบัติที่ต่อเนื่องกันนี้ด้วยเหตุผลบางประการ (น่าจะมีประสิทธิภาพมากที่สุดใน gpu เป็นต้น)

โปรดทราบว่า.viewเป็นการดำเนินการอื่นที่อาจทำให้เกิดปัญหานี้ ดูรหัสต่อไปนี้ที่ฉันแก้ไขโดยเพียงแค่เรียกติดกัน (แทนที่จะเป็นปัญหาการเปลี่ยนความถี่ทั่วไปที่ทำให้เกิดนี่คือตัวอย่างที่เป็นสาเหตุเมื่อ RNN ไม่พอใจกับอินพุต):

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

ข้อผิดพลาดที่ฉันเคยได้รับ:

RuntimeError: rnn: hx is not contiguous


แหล่งที่มา / ทรัพยากร:

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.