Как работает метод «просмотра» в PyTorch?

208

Я запутался в методе view()в следующем фрагменте кода.

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool  = nn.MaxPool2d(2,2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1   = nn.Linear(16*5*5, 120)
        self.fc2   = nn.Linear(120, 84)
        self.fc3   = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16*5*5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

Моя путаница касается следующей строки.

x = x.view(-1, 16*5*5)

Что делает tensor.view()функция? Я видел его использование во многих местах, но я не могу понять, как он интерпретирует свои параметры.

Что произойдет, если я передам функции отрицательные значения view()? Например, что произойдет , если я называю tensor_variable.view(1, 1, -1)?

Кто-нибудь может объяснить основной принцип view()функционирования на некоторых примерах?

Васи ахмад
источник

Ответы:

288

Функция view предназначена для изменения тензора.

Скажем, у вас есть тензор

import torch
a = torch.range(1, 16)

aэто тензор, который имеет 16 элементов от 1 до 16 (включены). Если вы хотите изменить этот тензор, чтобы сделать его 4 x 4тензорным, то вы можете использовать

a = a.view(4, 4)

Теперь aбудет 4 x 4тензор. Обратите внимание, что после изменения формы общее количество элементов должно оставаться неизменным. Изменение формы тензора aв 3 x 5тензор не будет целесообразным.

Что означает параметр -1?

Если есть ситуация, когда вы не знаете, сколько строк вы хотите, но уверены в количестве столбцов, вы можете указать это с -1. ( Обратите внимание, что вы можете расширить это на тензоры с большим количеством измерений. Только одно из значений оси может быть -1 ). Это способ сказать библиотеке: «дайте мне тензор с таким количеством столбцов, и вы вычислите соответствующее количество строк, необходимое для этого».

Это можно увидеть в коде нейронной сети, который вы дали выше. После линии x = self.pool(F.relu(self.conv2(x)))в прямой функции у вас будет 16 карт глубины. Вы должны сгладить это, чтобы дать это полностью связанному слою. Таким образом, вы приказываете pytorch изменить форму полученного вами тензора, чтобы он имел определенное количество столбцов, и предлагаете ему самостоятельно определять количество строк.

Рисование сходства между Numpy и Pytorch, viewпохоже на функцию изменения формы Numpy .

Kashyap
источник
93
«вид похож на форму numpy» - почему они просто не назвали это reshapeв PyTorch ?!
MaxB
54
@MaxB В отличие от reshape, новый тензор, возвращаемый функцией «view», делит базовые данные с оригинальным тензором, так что это действительно просмотр старого тензора, а не создание нового.
qihqi
40
@blckbird "reshape всегда копирует память. представление никогда не копирует память." github.com/torch/cutorch/issues/98
devinbost
3
@devinbost Изменение факела всегда копирует память. NumPy изменить не делает.
Тавиан Барнс
32

Давайте сделаем несколько примеров, от простого к сложному.

  1. viewМетод возвращает тензор с теми же данными, что и selfтензор (что означает , что возвращаемый тензор имеет такое же число элементов), но с различной формой. Например:

    a = torch.arange(1, 17)  # a's shape is (16,)
    
    a.view(4, 4) # output below
      1   2   3   4
      5   6   7   8
      9  10  11  12
     13  14  15  16
    [torch.FloatTensor of size 4x4]
    
    a.view(2, 2, 4) # output below
    (0 ,.,.) = 
    1   2   3   4
    5   6   7   8
    
    (1 ,.,.) = 
     9  10  11  12
    13  14  15  16
    [torch.FloatTensor of size 2x2x4]
  2. Предполагая, что -1это не один из параметров, при умножении их вместе результат должен быть равен количеству элементов в тензоре. Если вы сделаете:, a.view(3, 3)это вызовет, RuntimeErrorпотому что форма (3 x 3) недопустима для ввода с 16 элементами. Другими словами: 3 х 3 не равно 16, но 9.

  3. Вы можете использовать в -1качестве одного из параметров, которые вы передаете в функцию, но только один раз. Все, что происходит, - это то, что метод сделает для вас математику о том, как заполнить это измерение. Например a.view(2, -1, 4)эквивалентно a.view(2, 2, 4). [16 / (2 x 4) = 2]

  4. Обратите внимание, что возвращенный тензор имеет те же данные . Если вы вносите изменения в «представление», вы изменяете исходные данные тензора:

    b = a.view(4, 4)
    b[0, 2] = 2
    a[2] == 3.0
    False
  5. Теперь для более сложного варианта использования. В документации говорится, что каждое новое измерение представления должно быть либо подпространством исходного измерения, либо охватывать только d, d + 1, ..., d + k, которые удовлетворяют следующему условию смежности, которое для всех i = 0,. .., k - 1, шаг [i] = шаг [i + 1] x размер [i + 1] . В противном случае contiguous()необходимо вызвать, прежде чем можно будет просмотреть тензор. Например:

    a = torch.rand(5, 4, 3, 2) # size (5, 4, 3, 2)
    a_t = a.permute(0, 2, 3, 1) # size (5, 3, 2, 4)
    
    # The commented line below will raise a RuntimeError, because one dimension
    # spans across two contiguous subspaces
    # a_t.view(-1, 4)
    
    # instead do:
    a_t.contiguous().view(-1, 4)
    
    # To see why the first one does not work and the second does,
    # compare a.stride() and a_t.stride()
    a.stride() # (24, 6, 2, 1)
    a_t.stride() # (24, 2, 1, 6)

    Обратите внимание , что для a_t, походки [0]! = Шаг [1] х размера [1] , так как 24! = 2 х 3

Джадьель де Армас
источник
8

torch.Tensor.view()

Проще говоря, torch.Tensor.view()что вдохновлено numpy.ndarray.reshape()или numpy.reshape()создает новый взгляд на тензор, если новая форма совместима с формой исходного тензора.

Давайте разберемся в этом подробно на конкретном примере.

In [43]: t = torch.arange(18) 

In [44]: t 
Out[44]: 
tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17])

С этим тензором tформы (18,)новые виды могут быть созданы только для следующих фигур:

(1, 18)или , что эквивалентно (1, -1)или или , что эквивалентно или или , что эквивалентно или или , что эквивалентно или или , что эквивалентно или или , что эквивалентно или(-1, 18)
(2, 9)(2, -1)(-1, 9)
(3, 6)(3, -1)(-1, 6)
(6, 3)(6, -1)(-1, 3)
(9, 2)(9, -1)(-1, 2)
(18, 1)(18, -1)(-1, 1)

Как мы уже можем наблюдать из приведенных выше кортежей формы, умножение элементов кортежа формы (например 2*9, 3*6и т. Д.) Всегда должно быть равно общему количеству элементов в исходном тензоре ( 18в нашем примере).

Еще одна вещь, которую стоит заметить, это то, что мы использовали -1в одном из мест в каждом из кортежей формы. Используя a -1, мы ленимся в выполнении вычислений сами, и скорее делегируем задачу PyTorch, чтобы выполнить вычисление этого значения для фигуры, когда она создает новое представление . Важно отметить, что мы можем использовать только один -1кортеж формы. Остальные значения должны быть явно предоставлены нами. Остальное PyTorch будет жаловаться, бросая RuntimeError:

RuntimeError: может быть выведено только одно измерение

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

Ниже приведены некоторые примеры, иллюстрирующие, как шаги тензоров изменяются с каждым новым представлением .

# stride of our original tensor `t`
In [53]: t.stride() 
Out[53]: (1,)

Теперь мы увидим шаги для новых взглядов :

# shape (1, 18)
In [54]: t1 = t.view(1, -1)
# stride tensor `t1` with shape (1, 18)
In [55]: t1.stride() 
Out[55]: (18, 1)

# shape (2, 9)
In [56]: t2 = t.view(2, -1)
# stride of tensor `t2` with shape (2, 9)
In [57]: t2.stride()       
Out[57]: (9, 1)

# shape (3, 6)
In [59]: t3 = t.view(3, -1) 
# stride of tensor `t3` with shape (3, 6)
In [60]: t3.stride() 
Out[60]: (6, 1)

# shape (6, 3)
In [62]: t4 = t.view(6,-1)
# stride of tensor `t4` with shape (6, 3)
In [63]: t4.stride() 
Out[63]: (3, 1)

# shape (9, 2)
In [65]: t5 = t.view(9, -1) 
# stride of tensor `t5` with shape (9, 2)
In [66]: t5.stride()
Out[66]: (2, 1)

# shape (18, 1)
In [68]: t6 = t.view(18, -1)
# stride of tensor `t6` with shape (18, 1)
In [69]: t6.stride()
Out[69]: (1, 1)

Так что это магия view()функции. Это просто изменяет шаги (оригинального) тензора для каждого из новых представлений , пока форма нового представления совместима с исходной формой.

Другая интересная вещь, которую можно наблюдать из кортежей шагов, состоит в том, что значение элемента в 0- й позиции равно значению элемента в 1- й позиции кортежа формы.

In [74]: t3.shape 
Out[74]: torch.Size([3, 6])
                        |
In [75]: t3.stride()    |
Out[75]: (6, 1)         |
          |_____________|

Это потому что:

In [76]: t3 
Out[76]: 
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11],
        [12, 13, 14, 15, 16, 17]])

шаг (6, 1)говорит, что для перехода от одного элемента к следующему элементу по 0- му измерению мы должны прыгнуть или сделать 6 шагов. (т.е. чтобы перейти от 0к 6, нужно сделать 6 шагов.) Но чтобы перейти от одного элемента к следующему элементу в 1- м измерении, нам нужен только один шаг (например, чтобы перейти от 2к3 ).

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


torch.reshape ()

Эта функция будет возвращать представление и будет точно такой же, как при использовании, torch.Tensor.view()если новая форма совместима с формой исходного тензора. В противном случае он вернет копию.

Однако заметки torch.reshape()предупреждают, что:

непрерывные входы и входы с совместимыми шагами могут быть изменены без копирования, но это не должно зависеть от поведения копирования и просмотра.

kmario23
источник
1

Я выяснил, что x.view(-1, 16 * 5 * 5)это эквивалентно тому x.flatten(1), где параметр 1 указывает, что процесс сглаживания начинается с 1-го измерения (не сглаживания «выборочного» измерения). Как вы можете видеть, последнее использование семантически более понятно и проще в использовании, поэтому я предпочитают flatten().

ФЭНЬШЖЭНЬ
источник
1

Что означает параметр -1?

Вы можете прочитать -1как динамическое количество параметров или «что-нибудь». Из-за этого может быть только один параметр -1в view().

Если вы спросите, x.view(-1,1)это выведет тензорную форму в [anything, 1]зависимости от количества элементов в x. Например:

import torch
x = torch.tensor([1, 2, 3, 4])
print(x,x.shape)
print("...")
print(x.view(-1,1), x.view(-1,1).shape)
print(x.view(1,-1), x.view(1,-1).shape)

Будет выводить:

tensor([1, 2, 3, 4]) torch.Size([4])
...
tensor([[1],
        [2],
        [3],
        [4]]) torch.Size([4, 1])
tensor([[1, 2, 3, 4]]) torch.Size([1, 4])
прости
источник
1

weights.reshape(a, b) вернет новый тензор с теми же данными, что и веса с размером (a, b), так как он копирует данные в другую часть памяти.

weights.resize_(a, b)возвращает тот же тензор с другой формой. Однако, если новая форма приводит к меньшему количеству элементов, чем исходный тензор, некоторые элементы будут удалены из тензора (но не из памяти). Если новая форма дает больше элементов, чем исходный тензор, новые элементы будут неинициализированы в памяти.

weights.view(a, b) вернет новый тензор с теми же данными, что и веса с размером (a, b)

Джибин Мэтью
источник
1

Попробуем разобраться в представлении на следующих примерах:

    a=torch.range(1,16)

print(a)

    tensor([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12., 13., 14.,
            15., 16.])

print(a.view(-1,2))

    tensor([[ 1.,  2.],
            [ 3.,  4.],
            [ 5.,  6.],
            [ 7.,  8.],
            [ 9., 10.],
            [11., 12.],
            [13., 14.],
            [15., 16.]])

print(a.view(2,-1,4))   #3d tensor

    tensor([[[ 1.,  2.,  3.,  4.],
             [ 5.,  6.,  7.,  8.]],

            [[ 9., 10., 11., 12.],
             [13., 14., 15., 16.]]])
print(a.view(2,-1,2))

    tensor([[[ 1.,  2.],
             [ 3.,  4.],
             [ 5.,  6.],
             [ 7.,  8.]],

            [[ 9., 10.],
             [11., 12.],
             [13., 14.],
             [15., 16.]]])

print(a.view(4,-1,2))

    tensor([[[ 1.,  2.],
             [ 3.,  4.]],

            [[ 5.,  6.],
             [ 7.,  8.]],

            [[ 9., 10.],
             [11., 12.]],

            [[13., 14.],
             [15., 16.]]])

-1 в качестве значения аргумента - это простой способ вычислить значение, скажем, x, при условии, что мы знаем значения y, z или наоборот, в случае 3d, и для 2d снова простой способ вычислить значение, скажем, x при условии, что мы знать значения у или наоборот ..

Лия Алекс
источник
0

Мне очень понравились примеры @Jadiel de Armas.

Я хотел бы добавить небольшое понимание того, как элементы упорядочены для .view (...)

  • Для тензора с формой (а, б, в) , то порядок из его элементов определяются система нумерации: где первая цифра имеет номер, вторая цифра имеет б числа и третья цифра имеет гр номер.
  • Отображение элементов в новом Tensor, возвращаемое .view (...), сохраняет этот порядок исходного Tensor.
ychnh
источник