PyTorch - contiguous ()


90

Tôi đã xem qua ví dụ này về mô hình ngôn ngữ LSTM trên github (liên kết) . Những gì nó làm nói chung là khá rõ ràng đối với tôi. Nhưng tôi vẫn đang đấu tranh để hiểu những gì gọi contiguous()thực hiện, điều này xảy ra nhiều lần trong mã.

Ví dụ trong dòng 74/75 của đầu vào mã và trình tự đích của LSTM được tạo. Dữ liệu (được lưu trữ trong ids) là 2 chiều trong đó kích thước đầu tiên là kích thước lô.

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

Vì vậy, như một ví dụ đơn giản, khi sử dụng kích thước lô 1 và seq_length10 inputstargetstrông giống như sau:

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]

Vì vậy, nói chung câu hỏi của tôi là, những gì có contiguous()và tại sao tôi cần nó?

Hơn nữa, tôi không hiểu tại sao phương thức được gọi cho chuỗi đích chứ không phải chuỗi đầu vào vì cả hai biến đều bao gồm cùng một dữ liệu.

Làm thế nào có thể targetsđược uncontiguous và inputsvẫn được tiếp giáp?

CHỈNH SỬA: Tôi đã cố gắng bỏ qua cuộc gọi contiguous(), nhưng điều này dẫn đến thông báo lỗi khi tính toán mất mát.

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

Vì vậy, rõ ràng việc gọi contiguous()trong ví dụ này là cần thiết.

(Để giữ cho mã này dễ đọc, tôi đã tránh đăng toàn bộ mã ở đây, có thể tìm thấy mã này bằng cách sử dụng liên kết GitHub ở trên.)

Cảm ơn trước!


một tiêu đề mô tả hơn sẽ hữu ích. Tôi đề nghị bạn cải thiện tiêu đề hoặc ít nhất là viết một tldr; to the point summarycâu ngắn gọn cho phần tóm tắt điểm.
Charlie Parker

được đăng chéo: quora.com/unanswered/…
Charlie Parker

Câu trả lời từ diễn đàn: thảo luận.pytorch.org/t/contigious
Charlie Parker

Câu trả lời:


186

Có một vài thao tác trên Tensor trong PyTorch không thực sự thay đổi nội dung của tensor, mà chỉ là cách chuyển đổi các chỉ số trong vị trí tensor sang byte. Các hoạt động này bao gồm:

narrow(), view(), expand()transpose()

Ví dụ: khi bạn gọi transpose(), PyTorch không tạo ra tensor mới với bố cục mới, nó chỉ sửa đổi thông tin meta trong đối tượng Tensor để bù đắp và sải chân cho hình dạng mới. Tensor chuyển vị và tensor gốc thực sự đang chia sẻ bộ nhớ!

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

Đây là nơi xuất hiện khái niệm tiếp giáp . Ở trên xlà tiếp giáp nhưng ykhông phải vì bố cục bộ nhớ của nó khác với một tenxơ có cùng hình dạng được làm từ đầu. Lưu ý rằng từ "liền kề" hơi gây hiểu nhầm vì nó không phải là nội dung của tensor được trải ra xung quanh các khối bộ nhớ bị ngắt kết nối. Ở đây các byte vẫn được cấp phát trong một khối bộ nhớ nhưng thứ tự của các phần tử là khác nhau!

Khi bạn gọi contiguous(), nó thực sự tạo ra một bản sao của tensor để thứ tự của các phần tử sẽ giống như khi tensor có cùng hình dạng được tạo ra từ đầu.

Bình thường bạn không cần phải lo lắng về điều này. Nếu PyTorch mong đợi tensor liền kề nhưng nếu nó không phải thì bạn sẽ nhận được RuntimeError: input is not contiguousvà sau đó bạn chỉ cần thêm một cuộc gọi đến contiguous().


Tôi chỉ gặp lại điều này một lần nữa. Giải thích của bạn là rất tốt! Tôi chỉ tự hỏi: Nếu các khối trong bộ nhớ không được phổ biến rộng rãi, thì có vấn đề gì với một bố cục bộ nhớ "khác với một khối cùng hình dạng được làm từ đầu" ? Tại sao liên tục chỉ là yêu cầu cho một số hoạt động?
MBT

4
Tôi không thể trả lời dứt khoát điều này nhưng tôi đoán là một số mã PyTorch sử dụng vectơ triển khai hiệu suất cao của các hoạt động được triển khai trong C ++ và mã này không thể sử dụng độ lệch / bước tùy ý được chỉ định trong thông tin meta của Tensor. Đây chỉ là một phỏng đoán mặc dù.
Shital Shah

1
Tại sao callee không thể gọi một contiguous()mình?
information_interchange

hoàn toàn có thể xảy ra, bởi vì bạn không muốn nó theo một cách liền kề, và việc kiểm soát những gì bạn làm là điều rất tốt.
shivam13juna

2
Một hoạt động tensor phổ biến khác là permute, cũng có thể trả về tensor không "liền kề".
Oleg

31

Từ [tài liệu pytorch] [1]:

contiguous () → Tensor

Returns a contiguous tensor containing the same data as self 

tenxơ. Nếu tensor tự liền kề, hàm này trả về tensor tự.

contiguousđây không chỉ có nghĩa là liền kề trong bộ nhớ mà còn theo cùng một thứ tự trong bộ nhớ với thứ tự chỉ mục: ví dụ: thực hiện chuyển vị không thay đổi dữ liệu trong bộ nhớ, nó chỉ đơn giản là thay đổi bản đồ từ chỉ số thành con trỏ bộ nhớ, nếu bạn sau đó áp dụng contiguous()nó sẽ thay đổi dữ liệu trong bộ nhớ để bản đồ từ chỉ số đến vị trí bộ nhớ là bản đồ chuẩn. [1]: http://pytorch.org/docs/master/tensors.html


1
Cảm ơn về câu trả lời của bạn! Bạn có thể cho tôi biết tại sao / khi nào tôi cần dữ liệu liền nhau không? Nó chỉ là hiệu suất, hay một số lý do khác? PyTorch có yêu cầu dữ liệu liền kề cho một số hoạt động không? Tại sao các mục tiêu cần phải liền nhau và đầu vào thì không?
MBT

Nó chỉ dành cho hiệu suất. Tôi không biết tại sao các mã làm điều đó cho mục tiêu nhưng không cho đầu vào.
patapouf_ai

2
Vì vậy, rõ ràng pytorch yêu cầu các mục tiêu bị mất phải dự phòng trong bộ nhớ, nhưng các đầu vào của mạng thần kinh không cần phải đáp ứng yêu cầu này.
patapouf_ai

2
Tuyệt vời cảm ơn bạn! Tôi nghĩ điều này có ý nghĩa với tôi, tôi đã nhận thấy rằng contiguous () cũng được áp dụng cho dữ liệu đầu ra (tất nhiên trước đây là đầu vào) trong hàm chuyển tiếp, vì vậy cả đầu ra và mục tiêu đều liền kề khi tính toán tổn thất. Cảm ơn rất nhiều!
MBT

1
@patapouf_ai Không. Lời giải thích của bạn không chính xác. Như đã chỉ ra trong câu trả lời đúng, nó không phải là về các khối bộ nhớ liền kề nhau.
Akaisteph7

14

tensor.contiguous () sẽ tạo một bản sao của tensor và phần tử trong bản sao sẽ được lưu trữ trong bộ nhớ theo cách liền kề. Hàm contiguous () thường được yêu cầu khi chúng ta lần đầu tiên chuyển vị () một tensor và sau đó định hình lại (xem) nó. Đầu tiên, hãy tạo một tensor liền kề:

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

Trả về stride () (3,1) có nghĩa là: khi di chuyển dọc theo chiều thứ nhất theo từng bước (từng hàng), chúng ta cần di chuyển 3 bước trong bộ nhớ. Khi di chuyển dọc theo chiều thứ hai (từng cột), chúng ta cần di chuyển 1 bước trong bộ nhớ. Điều này chỉ ra rằng các phần tử trong tensor được lưu trữ liền kề.

Bây giờ chúng ta thử áp dụng các hàm come cho 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

Được rồi, chúng ta có thể tìm thấy phép cắt lát chuyển tiếp (), thu hẹp () và tensor và mở rộng () sẽ làm cho tensor được tạo ra không tiếp giáp. Điều thú vị là repeat () và view () không làm cho nó không liên tục. Vì vậy, bây giờ câu hỏi là: điều gì sẽ xảy ra nếu tôi sử dụng một tensor không liên tục?

Câu trả lời là hàm view () không thể được áp dụng cho một tensor không liên tục. Điều này có thể là do view () yêu cầu tensor phải được lưu trữ liên tục để nó có thể định hình lại nhanh chóng trong bộ nhớ. ví dụ:

bbb.view(-1,3)

chúng tôi sẽ nhận được lỗi:

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

Để giải quyết vấn đề này, chỉ cần thêm contiguous () vào tensor không liền kề, để tạo bản sao liền kề và sau đó áp dụng view ()

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

10

Như trong câu trả lời trước contigous () phân bổ các khối bộ nhớ liên tục , sẽ rất hữu ích khi chúng ta chuyển tensor sang mã phụ trợ c hoặc c ++ trong đó tensor được chuyển dưới dạng con trỏ


3

Các câu trả lời được chấp nhận quá tuyệt vời, và tôi đã cố gắng đánh lừa transpose()hiệu ứng hàm. Tôi đã tạo hai hàm có thể kiểm tra 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")

Tôi đã kiểm tra và nhận được kết quả này dưới dạng bảng:

chức năng

Bạn có thể xem lại mã bộ kiểm tra bên dưới, nhưng hãy đưa ra một ví dụ khi tensor không liền kề . Chúng ta không thể gọi đơn giản view()trên tensor đó, chúng ta cần reshape()nó hoặc chúng ta cũng có thể gọi .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)

Ngoài ra, cần lưu ý rằng cuối cùng có các phương pháp tạo ra các tenxơ liền kềkhông liền kề . Có những phương thức có thể hoạt động trên cùng một bộ nhớ và một số phương thức flip()sẽ tạo ra bộ nhớ mới (đọc: sao chép tensor) trước khi trả về.

Mã người kiểm tra:

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

Từ những gì tôi hiểu, một câu trả lời tóm tắt hơn:

Liền kề là thuật ngữ được sử dụng để chỉ ra rằng bố trí bộ nhớ của tensor không phù hợp với thông tin hình dạng hoặc siêu dữ liệu được quảng cáo của nó.

Theo tôi từ liên tục là một thuật ngữ khó hiểu / gây hiểu lầm vì trong ngữ cảnh thông thường, nó có nghĩa là khi bộ nhớ không được lan truyền xung quanh trong các khối ngắt kết nối (tức là "tiếp giáp / kết nối / liên tục").

Một số hoạt động có thể cần thuộc tính liền kề này vì một số lý do (rất có thể là hiệu quả trong gpu, v.v.).

Lưu ý rằng đó .viewlà một hoạt động khác có thể gây ra sự cố này. Hãy xem đoạn mã sau mà tôi đã sửa bằng cách gọi tiếp giáp (thay vì vấn đề chuyển vị điển hình gây ra nó ở đây là một ví dụ gây ra khi RNN không hài lòng với đầu vào của nó):

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

Lỗi tôi từng mắc phải:

RuntimeError: rnn: hx is not contiguous


Nguồn / Nguồn:

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.