Як працює метод «перегляду» в 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

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

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

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 в RESHAPE функції.


93
"вид схожий на перетворення форми numpy" - чому вони просто не назвали його reshapeв PyTorch ?!
MaxB

54
@MaxB На відміну від переформатування, новий тензор, повернутий "view", ділиться базовими даними з оригінальним тензором, так що це справді перегляд старого тензора, а не створення абсолютно нового.
qihqi

39
@blckbird "Переформатування завжди копіює пам'ять. Перегляд ніколи не копіює пам'ять." github.com/torch/cutorch/isissue/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)він підніме a, RuntimeErrorтому що форма (3 x 3) недійсна для введення з 16 елементами. Іншими словами: 3 х 3 не дорівнює 16, а 9.

  3. Ви можете використовувати -1як один із параметрів, які передаєте функції, але лише один раз. Все, що трапляється, полягає в тому, що метод зробить математику для вас, як заповнити цей вимір. Наприклад a.view(2, -1, 4), еквівалентно a.view(2, 2, 4). [16 / (2 х 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, stride [i] = stride [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, stride [0]! = Stride [1] x size [1] since 24! = 2 x 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в кортежі формі. Решта значень повинні бути явно надані нами. Else 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.Tensor.view(), якщо нова форма сумісна з формою оригінального тензора. В іншому випадку він поверне копію.

Однак нотатки torch.reshape()попереджають:

суміжні входи та входи із сумісними кроками можна переробити без копіювання, але це не повинно залежати від поведінки копіювання та перегляду.


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 знову простий спосіб обчислити значення say x за умови, що ми знати значення у або навпаки ..


0

Мені дуже сподобалися приклади @Jadiel de Armas.

Я хотів би додати невелике розуміння того, як замовляють елементи для .view (...)

  • Для тензора з формою (а, б, в) , то порядок з його елементів визначається система нумерації: де перша цифра має номер, друга цифра має б числа і третя цифра має гр номер.
  • Зображення елементів у новому тензорі, повернене .view (...), зберігає цей порядок початкового тензора.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.