Я просматривал этот пример языковой модели 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
все же непрерывным?
РЕДАКТИРОВАТЬ:
я попытался не использовать вызов 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
краткое резюме.Ответы:
В PyTorch есть несколько операций с Tensor, которые на самом деле не изменяют содержимое тензора, а только то, как преобразовать индексы в тензор в расположение байтов. Эти операции включают:
Например: когда вы вызываете
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()
.источник
contiguous()
сам по себе?permute
, которая также может возвращать не «непрерывный» тензор.Из [документации pytorch] [1]:
Где
contiguous
здесь означает не только непрерывность в памяти, но и в том же порядке в памяти, что и порядок индексов: например, выполнение транспонирования не меняет данные в памяти, оно просто меняет карту с индексов на указатели памяти, если вы затем применить,contiguous()
он изменит данные в памяти, так что карта из индексов в ячейку памяти будет канонической. [1]: http://pytorch.org/docs/master/tensors.htmlисточник
tensor.contiguous () создаст копию тензора, и элемент в копии будет сохранен в памяти непрерывным образом. Функция contiguous () обычно требуется, когда мы сначала транспонируем () тензор, а затем меняем его форму (просматриваем). Сначала создадим непрерывный тензор:
Функция stride () return (3,1) означает, что: при перемещении по первому измерению на каждом шаге (строка за строкой) нам нужно переместить 3 шага в памяти. При перемещении по второму измерению (столбец за столбцом) нам нужно переместить 1 шаг в памяти. Это указывает на то, что элементы в тензоре хранятся непрерывно.
Теперь попробуем применить к тензору функции come:
Хорошо, мы можем обнаружить, что транспонирование (), сужение () и тензорное сечение и расширение () сделают сгенерированный тензор несмежным. Интересно, что repeat () и view () не делают его разрозненным. Итак, теперь вопрос: что произойдет, если я использую несмежный тензор?
Ответ заключается в том, что функция view () не может применяться к несмежному тензору. Вероятно, это связано с тем, что view () требует, чтобы тензор сохранялся непрерывно, чтобы он мог быстро изменять форму в памяти. например:
мы получим ошибку:
Чтобы решить эту проблему, просто добавьте contiguous () к несмежному тензору, чтобы создать непрерывную копию, а затем примените view ()
источник
Как и в предыдущем ответе, contigous () выделяет непрерывные фрагменты памяти , это будет полезно, когда мы передаем тензор в бэкэнд-код c или c ++, где тензоры передаются как указатели.
источник
Принятые ответы были настолько хороши, что я попытался обмануть
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()
.Также следует отметить, что существуют методы, которые в конце создают непрерывные и несмежные тензоры. Существуют методы, которые могут работать с одним и тем же хранилищем , а некоторые из них перед возвратом
flip()
создают новое хранилище (читай: клонируйте тензор).Код проверки:
источник
Насколько я понимаю, это более обобщенный ответ:
На мой взгляд, слово «смежный» - термин, сбивающий с толку / вводящий в заблуждение, поскольку в нормальном контексте оно означает, что память не распределяется по разрозненным блокам (т.е. ее «непрерывная / связная / непрерывная»).
Некоторым операциям может потребоваться это смежное свойство по какой-то причине (скорее всего, эффективность в gpu и т. Д.).
Обратите внимание, что
.view
это еще одна операция, которая может вызвать эту проблему. Посмотрите на следующий код, который я исправил, просто вызвав смежный (вместо типичной проблемы транспонирования, вызывающей это, вот пример, который является причиной, когда RNN не доволен своим вводом):Ошибка, которую я получал:
Источники / Ресурс:
источник