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/unansarded/…
Charlie Parker
Ответ с форума: Discussion.pytorch.org/t/contigious-vs-non-contigious-tensor/30107/…
Чарли Паркер

Ответы:

186

В PyTorch есть несколько операций с Tensor, которые на самом деле не изменяют содержимое тензора, а только то, как преобразовать индексы в тензор в расположение байтов. Эти операции включают:

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 contiguousа затем вы просто добавите вызов contiguous().

Шитал Шах
источник
Я снова столкнулся с этим. Ваше объяснение очень хорошее! Мне просто интересно: если блоки в памяти не так широко распространены, в чем проблема с разметкой памяти, которая «отличается от тензора той же формы, сделанного с нуля» ? Почему непрерывность требуется только для некоторых операций?
MBT
4
Я не могу однозначно ответить на этот вопрос, но предполагаю, что часть кода PyTorch использует высокопроизводительную векторизованную реализацию операций, реализованных на C ++, и этот код не может использовать произвольные смещения / шаги, указанные в метаинформации Tensor. Это всего лишь предположение.
Шитал Шах
1
Почему вызываемый не может просто позвонить contiguous()сам по себе?
information_interchange
вполне возможно, потому что вы не хотите, чтобы это происходило непрерывно, и всегда приятно иметь контроль над тем, что вы делаете.
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

patapouf_ai
источник
1
Спасибо за ваш ответ! Вы можете сказать мне, почему / когда мне нужно, чтобы данные были непрерывными? Это просто производительность или какая-то другая причина? Требуется ли PyTorch для некоторых операций непрерывные данные? Почему цели должны быть смежными, а входные - нет?
MBT
Это просто для производительности. Я не знаю, почему коды делают это для целей, а не для входов.
patapouf_ai
2
Таким образом, очевидно, что pytorch требует, чтобы цели в потере были непрерывными в памяти, но входные данные нейронной сети не должны удовлетворять этому требованию.
patapouf_ai
2
Отлично, спасибо! Я думаю, что это имеет для меня смысл, я заметил, что contiguous () также применяется к выходным данным (которые, конечно, ранее были входными) в функции forward, поэтому и выходы, и цели являются смежными при вычислении потерь. Большое спасибо!
MBT
1
@patapouf_ai Нет. Ваше объяснение неверно. Как указано в правильном ответе, речь вовсе не идет о непрерывных блоках памяти.
Akaisteph7,
14

tensor.contiguous () создаст копию тензора, и элемент в копии будет сохранен в памяти непрерывным образом. Функция 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

Хорошо, мы можем обнаружить, что транспонирование (), сужение () и тензорное сечение и расширение () сделают сгенерированный тензор несмежным. Интересно, что repeat () и 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


Источники / Ресурс:

Чарли Паркер
источник