PyTorch-contiguous()


92

私はgithub (リンク)でLSTM言語モデルのこの例を調べていました。それが一般的に何をするかは私にはかなり明白です。しかしcontiguous()、コード内で数回発生する呼び出しが何をするのかを理解するのにまだ苦労しています。

たとえば、コードの74/75行目に、LSTMの入力シーケンスとターゲットシーケンスが作成されます。データ(に格納されているids)は2次元であり、最初の次元はバッチサイズです。

for i in range(0, ids.size(1) - seq_length, seq_length):
    # Get batch inputs and targets
    inputs = Variable(ids[:, i:i+seq_length])
    targets = Variable(ids[:, (i+1):(i+1)+seq_length].contiguous())

したがって、簡単な例として、バッチサイズ1およびseq_length10を使用するinputsと、targets次のようになります。

inputs Variable containing:
0     1     2     3     4     5     6     7     8     9
[torch.LongTensor of size 1x10]

targets Variable containing:
1     2     3     4     5     6     7     8     9    10
[torch.LongTensor of size 1x10]

それで、一般的に私の質問は、何がcontiguous()、そしてなぜそれが必要なのかということです。

さらに、両方の変数が同じデータで構成されているため、メソッドがターゲットシーケンスに対して呼び出され、入力シーケンスに対して呼び出されない理由がわかりません。

どうして連続していないのにtargetsinputsそれでも連続しているのでしょうか?

編集: 私は呼び出しを省略しようとしましたcontiguous()が、これは損失を計算するときにエラーメッセージにつながります。

RuntimeError: invalid argument 1: input is not contiguous at .../src/torch/lib/TH/generic/THTensor.c:231

したがって、明らかcontiguous()にこの例で呼び出す必要があります。

(これを読みやすくするために、ここに完全なコードを投稿することは避けました。上記のGitHubリンクを使用して見つけることができます。)

前もって感謝します!


より説明的なタイトルが役立つでしょう。タイトルを改善するか、少なくともtldr; to the point summaryポイントの要約を簡潔に書くことをお勧めします。
チャーリーパーカー


回答:


192

PyTorchのTensorには、テンソルの内容を実際には変更しない操作がいくつかありますが、インデックスをテンソルからバイト位置に変換する方法のみが変更されます。これらの操作には次のものが含まれます。

narrow()view()expand()およびtranspose()

例:を呼び出すとtranspose()、PyTorchは新しいレイアウトで新しいテンソルを生成せず、Tensorオブジェクトのメタ情報を変更するだけなので、オフセットとストライドは新しい形状になります。転置テンソルと元のテンソルは確かに記憶を共有しています!

x = torch.randn(3,2)
y = torch.transpose(x, 0, 1)
x[0, 0] = 42
print(y[0,0])
# prints 42

ここで、連続の概念が登場ます。上記xは連続ですがy、そのメモリレイアウトが、最初から作成された同じ形状のテンソルと異なるためではありません。「連続」という言葉は、テンソルの内容が接続されていないメモリブロックの周囲に分散しているわけではないため、少し誤解を招く可能性があることに注意してください。ここでは、バイトはメモリの1つのブロックに割り当てられますが、要素の順序は異なります。

を呼び出すとcontiguous()、実際にはテンソルのコピーが作成されるため、要素の順序は、同じ形状のテンソルを最初から作成した場合と同じになります。

通常、これについて心配する必要はありません。PyTorchが連続テンソルを期待しているが、そうでない場合は、を取得RuntimeError: input is not contiguousし、に呼び出しを追加するだけcontiguous()です。


私はちょうどこれに再び出くわしました。あなたの説明はとても良いです!私はただ疑問に思います:メモリ内のブロックが広く広がっていない場合、「ゼロから作られた同じ形状のテンソルとは異なる」メモリレイアウトの問題は何ですか?一部の操作で連続している必要があるのはなぜですか?
MBT

4
これに明確に答えることはできませんが、一部のPyTorchコードは、C ++で実装された操作の高性能なベクトル化された実装を使用し、このコードはTensorのメタ情報で指定された任意のオフセット/ストライドを使用できないと思います。ただし、これは単なる推測です。
ShitalShah18年

1
なぜ、呼び出し先は単にcontiguous()自分で呼び出すことができなかったのですか?
information_interchange

おそらく、連続した方法でそれを望まないので、あなたが何をするかを制御できることは常に素晴らしいことです。
shivam13juna

2
もう1つの一般的なテンソル演算はですpermute。これも「連続していない」テンソルを返す場合があります。
オレグ

32

[pytorchドキュメント] [1]から:

contiguous()→テンソル

Returns a contiguous tensor containing the same data as self 

テンソル。自己テンソルが隣接している場合、この関数は自己テンソルを返します。

ここcontiguousで、メモリ内で連続しているだけでなく、メモリ内でインデックスの順序と同じ順序であることを意味します。たとえば、転置を行ってもメモリ内のデータは変更されません。その場合、マップはインデックスからメモリポインタに変更されます。適用contiguous()すると、メモリ内のデータが変更され、インデックスからメモリ位置へのマップが正規のものになります。[1]:http//pytorch.org/docs/master/tensors.html


1
ご回答ありがとうございます!データを連続させる必要がある理由/時期を教えてください。それは単なるパフォーマンスですか、それとも他の理由ですか?PyTorchは、一部の操作に連続したデータを必要としますか?ターゲットが連続している必要があり、入力が隣接していない必要があるのはなぜですか?
MBT

パフォーマンスのためだけです。コードがターゲットに対してそれを行うのに、入力に対しては行わない理由はわかりません。
patapouf_ai 2018

2
したがって、明らかにpytorchでは、損失のターゲットがメモリ内で連続している必要がありますが、neuralnetの入力はこの要件を満たす必要はありません。
patapouf_ai 2018

2
よかった。ありがとう!これは私には理にかなっていると思います。contiguous()はforward関数の出力データ(もちろん以前は入力でした)にも適用されるため、損失を計算するときに出力とターゲットの両方が連続していることに気付きました。どうもありがとう!
MBT

1
@patapouf_aiいいえ。あなたの説明は正しくありません。正解で指摘されているように、それはメモリの連続したブロックに関するものではありません。
Akaisteph7

14

tensor.contiguous()はテンソルのコピーを作成し、コピー内の要素は連続した方法でメモリに格納されます。contiguous()関数は通常、最初にテンソルを転置()し、次にそれを再形成(表示)するときに必要です。まず、隣接するテンソルを作成しましょう。

aaa = torch.Tensor( [[1,2,3],[4,5,6]] )
print(aaa.stride())
print(aaa.is_contiguous())
#(3,1)
#True

stride()return(3,1)は、次のことを意味します。各ステップ(行ごと)で最初の次元に沿って移動する場合、メモリ内で3ステップ移動する必要があります。2番目の次元に沿って(列ごとに)移動する場合、メモリ内を1ステップ移動する必要があります。これは、テンソルの要素が連続して格納されていることを示しています。

次に、テンソルにcome関数を適用してみます。

bbb = aaa.transpose(0,1)
print(bbb.stride())
print(bbb.is_contiguous())

#(1, 3)
#False


ccc = aaa.narrow(1,1,2)   ## equivalent to matrix slicing aaa[:,1:3]
print(ccc.stride())
print(ccc.is_contiguous())

#(3, 1)
#False


ddd = aaa.repeat(2,1)   # The first dimension repeat once, the second dimension repeat twice
print(ddd.stride())
print(ddd.is_contiguous())

#(3, 1)
#True


## expand is different from repeat.
## if a tensor has a shape [d1,d2,1], it can only be expanded using "expand(d1,d2,d3)", which
## means the singleton dimension is repeated d3 times
eee = aaa.unsqueeze(2).expand(2,3,3)
print(eee.stride())
print(eee.is_contiguous())

#(3, 1, 0)
#False


fff = aaa.unsqueeze(2).repeat(1,1,8).view(2,-1,2)
print(fff.stride())
print(fff.is_contiguous())

#(24, 2, 1)
#True

わかりました。transpose()、narrow()、テンソルスライス、expand()を使用すると、生成されたテンソルが隣接しなくなります。興味深いことに、repeat()とview()はそれを不連続にしません。だから今問題は、不連続テンソルを使用するとどうなるかということです。

答えは、view()関数を不連続なテンソルに適用できないということです。これはおそらく、view()がテンソルを連続して格納して、メモリ内で高速に再形成できるようにする必要があるためです。例えば:

bbb.view(-1,3)

エラーが発生します:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-63-eec5319b0ac5> in <module>()
----> 1 bbb.view(-1,3)

RuntimeError: invalid argument 2: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Call .contiguous() before .view(). at /pytorch/aten/src/TH/generic/THTensor.cpp:203

これを解決するには、連続したテンソルにcontiguous()を追加し、連続したコピーを作成してから、view()を適用します。

bbb.contiguous().view(-1,3)
#tensor([[1., 4., 2.],
        [5., 3., 6.]])

10

前の回答のように、contigous()は連続したメモリチャンクを割り当てます。テンソルがポインタとして渡されるcまたはc ++バックエンドコードにテンソルを渡すときに役立ちます。


3

受け入れられた答えはとても素晴らしかったので、私はtranspose()機能効果を複製しようとしました。私がチェックすることができる2つの関数を作成samestorage()してcontiguous

def samestorage(x,y):
    if x.storage().data_ptr()==y.storage().data_ptr():
        print("same storage")
    else:
        print("different storage")
def contiguous(y):
    if True==y.is_contiguous():
        print("contiguous")
    else:
        print("non contiguous")

この結果を確認して、表として取得しました。

関数

以下のチェッカーコードを確認できますが、テンソルが連続していない場合の例を1つ挙げましょう。view()そのテンソルを単純に呼び出すことはできません。必要にreshape()なるか、を呼び出すこともできます.contiguous().view()

x = torch.randn(3,2)
y = x.transpose(0, 1)
y.view(6) # 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.
  
x = torch.randn(3,2)
y = x.transpose(0, 1)
y.reshape(6)

x = torch.randn(3,2)
y = x.transpose(0, 1)
y.contiguous().view(6)

さらに、最終的に連続テンソルと非連続テンソルを作成する方法があることに注意してください。同じストレージで操作できるメソッドと、戻る前に新しいストレージを作成するメソッド(読み取り:テンソルのクローンを作成するメソッド)があります。flip()

チェッカーコード:

import torch
x = torch.randn(3,2)
y = x.transpose(0, 1) # flips two axes
print("\ntranspose")
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nnarrow")
x = torch.randn(3,2)
y = x.narrow(0, 1, 2) #dim, start, len  
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\npermute")
x = torch.randn(3,2)
y = x.permute(1, 0) # sets the axis order
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nview")
x = torch.randn(3,2)
y=x.view(2,3)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nreshape")
x = torch.randn(3,2)
y = x.reshape(6,1)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nflip")
x = torch.randn(3,2)
y = x.flip(0)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nexpand")
x = torch.randn(3,2)
y = x.expand(2,-1,-1)
print(x)
print(y)
contiguous(y)
samestorage(x,y) 

0

私がこれを理解していることから、より要約された答え:

連続とは、テンソルのメモリレイアウトが、アドバタイズされたメタデータまたは形状情報と一致しないことを示すために使用される用語です。

私の意見では、連続という言葉は紛らわしい/誤解を招く用語です。通常のコンテキストでは、メモリが切断されたブロック(つまり、「連続/接続/連続」)に分散されていないことを意味するためです。

一部の操作では、何らかの理由でこの連続したプロパティが必要になる場合があります(GPUでの効率など)。

.viewこれは、この問題を引き起こす可能性のある別の操作であることに注意してください。連続を呼び出すだけで修正した次のコードを見てください(ここで発生する典型的な転置の問題の代わりに、RNNが入力に満足していない場合に発生する例です)。

        # normal lstm([loss, grad_prep, train_err]) = lstm(xn)
        n_learner_params = xn_lstm.size(1)
        (lstmh, lstmc) = hs[0] # previous hx from first (standard) lstm i.e. lstm_hx = (lstmh, lstmc) = hs[0]
        if lstmh.size(1) != xn_lstm.size(1): # only true when prev lstm_hx is equal to decoder/controllers hx
            # make sure that h, c from decoder/controller has the right size to go into the meta-optimizer
            expand_size = torch.Size([1,n_learner_params,self.lstm.hidden_size])
            lstmh, lstmc = lstmh.squeeze(0).expand(expand_size).contiguous(), lstmc.squeeze(0).expand(expand_size).contiguous()
        lstm_out, (lstmh, lstmc) = self.lstm(input=xn_lstm, hx=(lstmh, lstmc))

以前に取得したエラー:

RuntimeError: rnn: hx is not contiguous


ソース/リソース:

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