「ビュー」メソッドはPyTorchでどのように機能しますか?


205

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()がいくつかの例で機能の主な原理を説明できますか?

回答:


283

ビュー関数は、テンソルを再形成することを目的としています。

テンソルがあるとしましょう

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

a1から16(含まれる)までの16の要素を持つテンソルです。このテンソルを変形してテンソルにしたい4 x 4場合は、

a = a.view(4, 4)

a4 x 4テンソルになります。形状変更後、要素の総数は同じままである必要があることに注意してください。テンソルaをテンソルに変形する3 x 5ことは適切ではありません。

パラメータ-1の意味は何ですか?

必要な行数はわからないが、列数はわかっている場合は、-1を指定できます。(これをより多くの次元のテンソルに拡張できることに注意してください。軸の値の1つだけが-1)。これはライブラリに伝える方法です:「これらの多くの列を持つテンソルを与え、これを実現するために必要な行の適切な数を計算してください」。

これは、上記のニューラルネットワークコードで確認できます。x = self.pool(F.relu(self.conv2(x)))forward関数の行の後に、16深度フィーチャーマップがあります。これを平坦化して、完全に接続されたレイヤーに適用する必要があります。したがって、pytorchに、取得したテンソルを特定の数の列を持つように再形成し、それだけで行数を決定するように指示します。

numpyのとpytorch間の類似性を描く、viewnumpyののに似ている形状変更機能。


93
「ビューはnumpyのリシェイプに似ています」 -なぜreshapePyTorchでそれを呼び出さなかったのですか?
MaxB 2017

54
@MaxB reshapeとは異なり、「view」によって返される新しいテンソルは元のテンソルと基になるデータを共有するため、新しいテンソルを作成するのではなく、実際には古いテンソルのビューになります。
qihqi

37
@blckbird "reshapeは常にメモリをコピーします。viewはメモリをコピーしません。" github.com/torch/cutorch/issues/98
devinbost 2017

3
@devinbost Torch reshapeは常にメモリをコピーします。 NumPyの形状変更には対応していません。
Tavian Barnes

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がパラメーターの1つではないと想定すると、それらを一緒に乗算すると、結果はテンソルの要素数と等しくなければなりません。次のようにすると、16要素の入力に対して形状(3 x 3)が無効a.view(3, 3)になるため、それによってa RuntimeErrorが発生します。つまり、3 x 3は16ではなく9です。

  3. -1関数に渡すパラメーターの1つとして使用できますが、使用できるのは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、stride [i] = stride [i + 1] x size [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


7

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*93*6等)が常になければならない(元のテンソルの要素の総数と等しくなります18私たちの例では)。

もう1つ注意すべき点は-1、各形状タプルの1つの場所でを使用したことです。を使用する-1ことで、計算を自分で行うのが面倒になり、タスクをPyTorchに委任して、新しいビューを作成するときに形状のその値の計算を実行します。注意すべき重要な点の1つは、形状タプルでは1つしか使用できないこと-1です。残りの値は、明示的に提供する必要があります。エルスPyTorchは投げることで文句を言うだろうRuntimeError

RuntimeError:推論できる次元は1つだけです

したがって、上記のすべての形状で、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()関数の魔法です。それはちょうど新しいのそれぞれについて(オリジナル)テンソルのストライド変化するビューを限り新規の形状として、ビューは元の形状に対応しています。

stridesタプルから観察できるもう1つの興味深いことは、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 番目の次元に沿って1つの要素から次の要素に移動するには、ジャンプするか6つのステップを実行する必要があることを示しています。(つまり、からに移動する0には6、6つのステップを実行する必要があります。)1次元で1つの要素から次の要素に移動するには、ステップが1つだけ必要です(たとえば、からに移動2します3)。

したがって、ストライド情報は、計算を実行するためにメモリから要素にアクセスする方法の中心にあります。


torch.reshape()

この関数はビューを返しtorch.Tensor.view()、新しい形状が元のテンソルの形状と互換性がある限り、を使用する場合とまったく同じです。それ以外の場合は、コピーを返します。

ただし、のメモは次のtorch.reshape()ように警告しています。

隣接する入力と互換性のあるストライドを持つ入力は、コピーせずに再形成できますが、コピーと表示の動作に依存するべきではありません。


1

x.view(-1, 16 * 5 * 5)同等x.flatten(1)であることがわかりました。ここで、パラメーター1は、平坦化プロセスが1番目の次元から開始することを示します(「サンプル」次元を平坦化しない)。好むflatten()


1

パラメータ-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)の重みと同じデータを持つ新しいテンソルを返します


0

@Jadiel de Armasの例が本当に気に入りました。

要素が.view(...)

  • 形状テンソルのために(a、b、c)は注文それの要素を番号付けシステムによって決定される:最初の桁が持っている 番号を、第二桁が有するB番号と3桁目を有するCを番号。
  • .view(...)によって返される新しいTensorの要素のマッピングは、元のTensorのこの順序を保持します。

0

次の例でビューを理解してみましょう。

    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は、y、z、または3dの場合はその逆の値がわかっている場合、say xの値を計算する簡単な方法であり、2dの場合、say xの値を計算する簡単な方法です。 yの値を知っている、またはその逆

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.