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.]])