PyTorch - суміжний ()


90

Я переглядав цей приклад мовної моделі LSTM на github (посилання) . Що це взагалі робить, мені цілком зрозуміло. Але я все ще намагаюся зрозуміти, що contiguous()робить виклик , який кілька разів трапляється в коді.

Наприклад, у рядку 74/75 коду створюються вхідні та цільові послідовності LSTM. Дані (що зберігаються ids) є двовимірними, де перший вимір - це розмір партії.

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короткий зміст.
Чарлі Паркер,

перехресне розміщення: quora.com/unanswered/…
Чарлі Паркер,

Відповіді:


186

Існує небагато операцій з Tensor у PyTorch, які насправді не змінюють вміст тензора, а лише те, як перетворити індекси в тензор у байтове розташування. Ці операції включають:

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

Наприклад: коли ви телефонуєте transpose(), PyTorch не генерує новий тензор з новим макетом, він просто модифікує метаінформацію в об'єкті Тензор, тому зміщення та крок призначені для нової форми. Транспонований тензор та оригінальний тензор справді діляться пам’яттю!

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 contiguousа потім просто додасте дзвінок contiguous().


Я просто знову натрапив на це. Ваше пояснення дуже добре! Мені просто цікаво: якщо блоки в пам'яті не широко розповсюджені, в чому проблема макета пам'яті, який "відрізняється від тензора однакової форми, зробленого з нуля" ? Чому суміжність є лише вимогою для деяких операцій?
MBT

4
Я не можу остаточно відповісти на це, але я припускаю, що частина коду PyTorch використовує високоефективну векторизовану реалізацію операцій, реалізованих в C ++, і цей код не може використовувати довільні зміщення / кроки, зазначені в метаінформації Tensor. Це лише припущення.
Shital Shah

1
Чому виклик не міг просто подзвонити contiguous()сам?
інформацією

цілком можливо, тому що ви не хочете цього суміжно, і завжди приємно мати контроль над тим, що ви робите.
shivam13juna

2
Ще однією популярною тензорною операцією є permute, яка також може повернути несуміжний тензор.
Олег

31

З [документації до pytorch] [1]:

суміжні () → Тензор

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 вимагає, щоб цілі в втратах були неперервними в пам’яті, але входи нейронної мережі не повинні задовольняти цій вимозі.
patapouf_ai

2
Велике спасибі! Думаю, це для мене має сенс, я помітив, що contiguous () також застосовується до вихідних даних (які, звичайно, раніше були вхідними) у функції forward, тому і результати, і цілі є суміжними при обчисленні збитків. Дуже дякую!
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 () (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 () у суміжний тензор, щоб створити суміжну копію, а потім застосувати view ()

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


Джерела / Ресурс:

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.