В numpy мы используем ndarray.reshape () для изменения формы массива.

Я заметил, что в pytorch люди используют torch.view (...) для той же цели, но в то же время есть torch.reshape (... ) существующие.

Итак, мне интересно, в чем разница между ними и когда я должен использовать любой из них?

Lifu Huang

Ответов: 5

Ответы (5)

torch.view существует давно. Он вернет тензор с новой формой. Возвращенный тензор будет разделять подчиненные данные с исходным тензором. См. Документацию здесь.

С другой стороны, кажется, что torch.reshape был недавно представлен в версии 0.4. Согласно документу, этот метод будет

Возвращает тензор с теми же данными и количеством элементов, что и входные, но с указанной формой. Когда возможно, возвращаемый тензор будет видом ввода. В противном случае это будет копия. Смежные входы и входы с совместимыми шагами можно изменять без копирования, но вы не должны зависеть от поведения копирования и просмотра.

Это означает, что torch.reshape может возвращать копию или представление исходного тензора. Вы не можете рассчитывать на то, чтобы вернуть представление или копию. Со слов разработчика:

если вам нужна копия, используйте clone (), если вам нужно такое же хранилище, используйте view (). Семантика reshape () состоит в том, что он может или не может совместно использовать хранилище, и вы не знаете заранее.

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

view () попытается изменить форму тензора, сохраняя при этом то же самое распределение базовых данных, таким образом, данные будут совместно использоваться двумя тензорами. reshape () при необходимости создаст новое выделение базовой памяти.

Создадим тензор:

a = torch.arange(8).reshape(2, 4)

initial 2D tensor

Память распределяется, как показано ниже (это C непрерывно, т.е. строки хранятся рядом друг с другом):

initial 2D tensor's memory allocation

stride () дает количество байтов, необходимое для перехода к следующему элементу в каждом измерении:

a.stride()
(4, 1)

Мы хотим, чтобы его форма стала (4, 2), мы можем использовать view:

a.view(4,2)

after view to switch the dimensions

Базовое распределение данных не изменилось, тензор по-прежнему C смежный:

memory allocation after switch

a.view(4, 2).stride()
(2, 1)

Попробуем с a.t (). Transpose () не изменяет выделение основной памяти, и поэтому a.t () не является непрерывным.

a.t().is_contiguous()
False

after transpose

memory allocation after transpose

Хотя он не является непрерывным, информации о шаге достаточно для перебора тензора

a.t().stride()
(1, 4)

view () больше не работает:

a.t().view(2, 4)
Traceback (most recent call last):
  File "", line 1, in 
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.

Ниже приведена форма, которую мы хотели получить с помощью представления (2, 4):

after transpose and reshape

Как бы выглядело распределение памяти?

memory allocation without reshape

Шаг будет примерно таким, как (4, 2), но нам придется вернуться к началу тензора после того, как мы дойдем до конца. Не работает.

В этом случае reshape () создаст новый тензор с другим распределением памяти, чтобы сделать транспонирование непрерывным:

memory allocation with reshape or contiguous

Обратите внимание, что мы можем использовать вид для разделения первого измерения транспонирования. В отличие от того, что сказано в принятых и других ответах, view () может работать с несмежными тензорами!

a.t().view(2, 2, 2)

after transpose and view 2, 2, 2

memory allocation after transpose

a.t().view(2, 2, 2).stride()
(2, 1, 4)

По документации:

Для просмотра тензора новый размер представления должен быть совместим с исходный размер и шаг, т. е. каждое новое измерение вида должно либо быть подпространством исходного измерения, либо охватывать только исходные размеры d, d + 1,…, d + k, удовлетворяющие следующим условиям условие, подобное примыканию, что ∀i = d,…, d + k − 1,
шаг [i] = шаг [i + 1] × размер [i + 1]

Это потому, что первые два измерения после применения вида (2, 2, 2) являются подпространствами первого измерения транспонирования.

Я бы сказал, что ответы здесь технически правильные, но есть еще одна причина существования reshape.pytorch обычно считается более удобным, чем другие фреймворки, потому что он ближе к python и numpy. Интересно, что в вопросе фигурирует numpy.

Давайте посмотрим на size и shape в pytorch.size - это функция, поэтому вы вызываете ее как x.size ().форма в pytorch не является функцией. В numpy у вас есть shape, и это не функция - вы используете его x.shape. Так что удобно получить их обоих в pytorch. Если вы пришли из numpy, было бы неплохо использовать те же функции.

Tensor.reshape () более надежен. Он будет работать с любым тензором, а Tensor.view () работает только с тензором t, где t.is_contiguous () == True.

Объяснение несмежных и смежных - это отдельная история, но вы всегда можете сделать тензор t смежным, если вы вызовете t.contiguous (), а затем вы можете вызвать view () без ошибки.

Хотя оба torch.view и torch.reshape используются для изменения формы тензоров, вот различия между ними.

  1. Как следует из названия, torch.view просто создает view исходного тензора. Новый тензор всегда будет делиться своими данными с исходным тензором. Это означает, что если вы измените исходный тензор, измененный тензор изменится, и наоборот.
>>> z = torch.zeros (3, 2)
>>> x = z.view (2, 3)
>>> z.fill_ (1)
>>> х
тензор ([[1., 1., 1.],
        [1., 1., 1.]])
  1. Чтобы новый тензор всегда делился своими данными с исходным, torch.view накладывает некоторые ограничения смежности на формы двух тензоров [docs]. Чаще всего это не вызывает беспокойства, но иногда torch.view выдает ошибку, даже если формы двух тензоров совместимы. Вот известный контрпример.
>>> z = torch.zeros (3, 2)
>>> y = z.t ()
>>> y.size ()
torch.Size ([2, 3])
>>> y.view (6)
Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
RuntimeError: недопустимый аргумент 2: размер представления несовместим с входным тензором
размер и шаг (по крайней мере, одно измерение охватывает два смежных подпространства).
Вызовите .contiguous () перед .view ().
  1. torch.reshape не накладывает никаких ограничений на смежность, но также не гарантирует совместное использование данных. Новый тензор может быть представлением исходного тензора или может быть совершенно новым тензором.
>>> z = torch.zeros (3, 2)
>>> y = z.reshape (6)
>>> x = z.t (). изменить форму (6)
>>> z.fill_ (1)
тензор ([[1., 1.],
        [1., 1.],
        [1., 1.]])
>>> у
тензор ([1., 1., 1., 1., 1., 1.])
>>> х
тензор ([0., 0., 0., 0., 0., 0.])

TL; DR:
Если вы просто хотите изменить форму тензоров, используйте torch.reshape. Если вас также беспокоит использование памяти и вы хотите, чтобы два тензора использовали одни и те же данные, используйте torch.view.

2022 WebDevInsider