[python] PyTorch에서 “보기”방법은 어떻게 작동합니까?

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()몇 가지 예를 들어 주요 기능 원리를 설명 할 수 있습니까 ?



답변

뷰 기능은 텐서를 재 형성하기위한 것입니다.

텐서가 있다고 해

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

a1에서 16 (포함)까지 16 개의 요소를 가진 텐서입니다. 당신이 그것을 만들기 위해이 텐서 모양을 변경하려면 4 x 4텐서 당신은 사용할 수 있습니다

a = a.view(4, 4)

이제 텐서 a가 될 것 4 x 4입니다. 모양을 변경 한 후에는 총 요소 수를 동일하게 유지해야합니다. 텐서 a를 텐서 로 바꾸는 3 x 5것은 적절하지 않습니다.

매개 변수 -1의 의미는 무엇입니까?

원하는 행 수를 모르지만 열 수를 확신하는 상황이있는 경우 -1로이를 지정할 수 있습니다. ( 이를 더 많은 치수의 텐서로 확장 할 수 있습니다. 축 값 중 하나만 -1이 될 수 있습니다 ). 이것은 라이브러리에 다음과 같은 방법으로 “열이 많은 텐서 (tensor)를 제공하고이를 수행하는 데 필요한 행 수를 계산합니다.”

이것은 위에서 지정한 신경망 코드에서 볼 수 있습니다. x = self.pool(F.relu(self.conv2(x)))정방향 기능 의 선 뒤에 16 개의 깊이 피처 맵이 있습니다. 완전히 연결된 레이어에 제공하려면 이것을 평평하게해야합니다. 따라서 pytorch에게 얻은 텐서를 재구성하여 특정 수의 열을 가지도록하고 행 수를 결정하도록 지시하십시오.

numpy와 pytorch 사이의 유사성을 그리는 것은 viewnumpy의 변형 기능 과 유사 합니다.


답변

더 간단한 것부터 더 어려운 것까지 몇 가지 예를 들어 봅시다.

  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이것이 매개 변수 중 하나가 아니라고 가정하면 매개 변수를 곱하면 결과는 텐서의 요소 수와 같아야합니다. 그렇게하면 : 16 개의 요소가있는 입력에 모양 (3 x 3)이 유효하지 않기 때문에 a.view(3, 3)증가합니다 RuntimeError. 즉, 3 x 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경우 stride [0]! = stride [1] x size [1] 부터 24 이후 ! = 2 x 3


답변

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은 각 모양 튜플의 장소 중 하나에서를 사용했다는 것 입니다. 를 사용함으로써 -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 조치를 취합니다. (즉에서 이동 06. 하나는 6 단계를 수행하는,)하지만 1의 다음 요소로 하나 개의 요소에서 이동 번째 (예에서 이동하는 차원, 우리는 단지 하나의 단계를 필요 23).

따라서 보폭 정보는 계산을 수행하기 위해 메모리에서 요소에 액세스하는 방법의 핵심입니다.


torch.reshape ()

이 함수는 뷰를 반환하며 torch.Tensor.view()새 모양이 원래 텐서 모양과 호환되는 한 사용하는 것과 정확히 같습니다 . 그렇지 않으면 사본을 반환합니다.

그러나 다음과 같이 torch.reshape()경고합니다.

호환 가능한 보폭을 가진 연속 입력 및 입력은 복사하지 않고 재구성 할 수 있지만 복사 대보기 동작에 의존해서는 안됩니다.


답변

내가 그 그것을 알아 냈 x.view(-1, 16 * 5 * 5)에 해당 x.flatten(1)매개 변수 (1) 당신이 볼 수 있듯이 1 차원합니다 ( ‘샘플’차원을 병합하지 않음)에서 패턴 화 된 프로세스 시작을 나타내는 경우, 후자의 사용은 I 있도록, 사용에 의미보다 명확하고 쉽게 선호합니다 flatten().


답변

매개 변수 -1의 의미는 무엇입니까?

-1동적 매개 변수 수 또는 “anything”으로 읽을 수 있습니다 . 그 때문에 하나 개의 매개 변수가있을 수 있습니다 -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])


답변

weights.reshape(a, b) 데이터를 메모리의 다른 부분에 복사 할 때 크기 (a, b)가있는 가중치와 동일한 데이터를 가진 새 텐서를 반환합니다.

weights.resize_(a, b)다른 모양의 동일한 텐서를 반환합니다. 그러나 새로운 모양으로 인해 원래 텐서보다 적은 수의 요소가 생성되면 일부 요소는 텐서에서 제거되지만 메모리에서는 제거되지 않습니다. 새 모양이 원래 텐서보다 많은 요소를 생성하면 메모리에서 새 요소가 초기화되지 않습니다.

weights.view(a, b) 크기 (a, b)의 가중치와 동일한 데이터를 가진 새로운 텐서를 반환합니다.


답변

@Jadiel de Armas 예제를 정말 좋아했습니다.

.view (…)에 대한 요소의 주문 방법에 대한 작은 통찰력을 추가하고 싶습니다.

  • 모양이 (a, b, c) 인 텐서 의 경우 요소 의 순서 는 번호 매기기 시스템에 의해 결정됩니다. 첫 번째 숫자에는 숫자가 있고
    두 번째 숫자에는 b 숫자가 있고 세 번째 숫자에는 c 숫자가 있습니다.
  • .view (…)에 의해 리턴 된 새로운 Tensor의 요소 맵핑은 원래 Tensor의 순서 를 유지합니다 .