PyTorch - смежный()

Я просматривал этот пример языковой модели 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_length 10 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 оставаться непрерывным?

EDIT: Я пытался пропустить вызов contiguous(), но это приводит к сообщению об ошибке при вычислении потери.

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

Итак, очевидно, что вызов contiguous() в этом примере необходим.

(Для сохранения читабельности я не стал публиковать здесь полный код, его можно найти по ссылке выше на GitHub.)

Заранее спасибо!

Ответ 1

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

Ответ 2

Из документации по Pytorch:

contiguous() → Tensor

Returns a contiguous tensor containing the same data as self 

тензор. Если собственный тензор является непрерывным, эта функция возвращает self тензор.

Где contiguous здесь означает смежный в памяти. Таким образом, функция contiguous вообще не влияет на целевой тензор, она просто гарантирует, что она хранится в непрерывном фрагменте памяти.

Ответ 3

Как и в предыдущем ответе contigous() выделяет непрерывные блоки памяти, будет полезно, когда мы передаем тензор в код c или c++, где тензоры передается как указатели

Ответ 4

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

ccc = aaa.narrow(1,1,2)   ## equivalent to matrix slicing aaa[:,1:3]
print(ccc.stride())
print(ccc.is_contiguous())


ddd = aaa.repeat(2,1 )   # The first dimension repeat once, the second dimension repeat twice
print(ddd.stride())
print(ddd.is_contiguous())

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

fff = aaa.unsqueeze(2).repeat(1,1,8).view(2,-1,2)
print(fff.stride())
print(fff.is_contiguous())

#(1, 3)
#False
#(3, 1)
#False
#(3, 1)
#True
#(3, 1, 0)
#False
#(24, 2, 1)
#True

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