web-dev-qa-db-ja.com

NumPyでの4D配列から2D配列への再形成の背後にある直感とアイデア

Kronecker-product を実装している間教育的理由(明白ですぐに利用できるnp.kron()を使用せずに)、私は中間結果として4次元配列を取得しました。最終結果を取得するために形状を変更する必要があります。

しかし、私はまだこれらの高次元配列の再形成に頭を包むことはできません。私はこの4D配列を持っています:

array([[[[ 0,  0],
         [ 0,  0]],

        [[ 5, 10],
         [15, 20]]],


       [[[ 6, 12],
         [18, 24]],

        [[ 7, 14],
         [21, 28]]]])

これは(2, 2, 2, 2)の形状です。(4,4)に再形成したいと思います。これは明らかなことだと思うかもしれません

np.reshape(my4darr, (4,4))

しかし、上記の変形は行いません期待される結果を与えます:

array([[ 0,  5,  0, 10],
       [ 6,  7, 12, 14],
       [ 0, 15,  0, 20],
       [18, 21, 24, 28]])

ご覧のとおり、期待される結果のすべての要素が4D配列に存在しています。 reshapeを必要に応じて正しく行うことのコツがわからないだけです。答えに加えて、そのような高次元の配列に対してreshapeを実行する方法のいくつかの説明は、本当に役に立ちます。ありがとう!

17
kmario23

ndからndへの変換の一般的なアイデア

そのようなndからndへの変換に関するアイデアは、2つのことだけを使用しています-

軸を並べ替える:平坦化されたバージョンが出力の平坦化されたバージョンに対応するような順序を取得します。したがって、どうにかしてそれを2回使用してしまった場合は、再利用しないでください。

Reshape:軸を分割するか、最終出力を目的の形状にします。軸の分割は、ほとんどの場合、最初に必要です。入力が低調で、ブロックに分割する必要がある場合です。繰り返しますが、これは2回以上必要ありません。

したがって、通常は3つのステップがあります。

    [ Reshape ]      --->  [ Permute axes ]   --->  [ Reshape ]

 Create more axes             Bring axes             Merge axes
                          into correct order

バックトラッキング方式

入力と出力が与えられた場合の最も安全な解決方法は、バックトラッキングメソッドとして呼び出すことができるもの、つまり入力の軸を分割する(小さなndから大きなndに移動する場合)または出力の軸を分割することです。大きいndから小さいndに変更する場合)。分割のアイデアは、小さいndの寸法の数を大きいndの寸法と同じにすることです。次に、出力のストライドを調べ、入力と照合して、必要な置換順序を取得します。最後に、最後の軸がndの方が小さい場合、軸をマージするために、最後に形状変更(デフォルトの方法またはC順)が必要になる場合があります。

入力と出力の両方が同じ数のDIMである場合、両方を分割してブロックに分割し、それらのストライドを互いに比較する必要があります。そのような場合、ブロックサイズの追加の入力パラメーターが必要ですが、それはおそらくトピック外です。

これらの戦略を適用する方法を示すために、この特定のケースを使用してみましょう。ここでは、入力は4D、出力は2Dです。したがって、おそらく、分割するために形状を変更する必要はありません。したがって、軸を並べ替えることから始める必要があります。最終出力は4Dではなく、2Dなので、最後に再形成する必要があります。

ここでの入力は次のとおりです。

In [270]: a
Out[270]: 
array([[[[ 0,  0],
         [ 0,  0]],

        [[ 5, 10],
         [15, 20]]],


       [[[ 6, 12],
         [18, 24]],

        [[ 7, 14],
         [21, 28]]]])

予想される出力は次のとおりです。

In [271]: out
    Out[271]: 
    array([[ 0,  5,  0, 10],
           [ 6,  7, 12, 14],
           [ 0, 15,  0, 20],
           [18, 21, 24, 28]])

また、これはより大きなndから小さなndへの変換であるため、バックトラッキング方法では、出力を分割してその strides を調べ、入力内の対応する値と照合します。

                    axis = 3
                   ---      -->          

                    axis = 1                    
                   ------>           
axis=2|  axis=0|   [ 0,  5,  0, 10],        

               |   [ 6,  7, 12, 14],
               v  
      |            [ 0, 15,  0, 20],
      v
                   [18, 21, 24, 28]])

したがって、必要な置換された順序は(2,0,3,1)です。

In [275]: a.transpose((2, 0, 3, 1))
Out[275]: 
array([[[[ 0,  5],
         [ 0, 10]],

        [[ 6,  7],
         [12, 14]]],


       [[[ 0, 15],
         [ 0, 20]],

        [[18, 21],
         [24, 28]]]])

次に、単純に予想される形状に再形成します。

In [276]: a.transpose((2, 0, 3, 1)).reshape(4,4)
Out[276]: 
array([[ 0,  5,  0, 10],
       [ 6,  7, 12, 14],
       [ 0, 15,  0, 20],
       [18, 21, 24, 28]])

その他の例

履歴を調べたところ、ndからndへの変換に基づくQ&Asはほとんど見つかりませんでした。これらは、ほとんど説明がありませんが、他の事例として使用できます。前述のように、最大​​2つのreshapesと最大1つのswapaxes/transposeがあらゆる場所で仕事をしました。それらは次のとおりです。

29
Divakar

transpose に続いて reshape を探しているようです。

x.transpose((2, 0, 3, 1)).reshape(np.prod(x.shape[:2]), -1)

array([[ 0,  5,  0, 10],
       [ 6,  7, 12, 14],
       [ 0, 15,  0, 20],
       [18, 21, 24, 28]])

転置が必要な理由を理解するのに役立つように、誤った形状の出力(単一のreshape呼び出しによって取得された)を分析して、なぜそれがunderstandであるかを理解します。

この結果の単純な2D変形バージョン(転置なし)は次のようになります-

x.reshape(4, 4)

array([[ 0,  0,  0,  0],
       [ 5, 10, 15, 20],
       [ 6, 12, 18, 24],
       [ 7, 14, 21, 28]])

次に、予想される出力に関してこの出力を検討します-

array([[ 0,  5,  0, 10],
       [ 6,  7, 12, 14],
       [ 0, 15,  0, 20],
       [18, 21, 24, 28]])

実際の結果は、誤って形作られた出力をZのようにたどることによって得られることに気づくでしょう-

start
    | /|     /| /|
    |/ |    / |/ |
      /    /    / 
     /    /    /
    | /| /    | /|
    |/ |/     |/ |
                 end

これは、actual結果を得るために、さまざまなストライドで配列を移動する必要があることを意味します。結論として、単純な形状変更では不十分です。元の配列をtransposeして、これらのZのような要素が互いに隣接するようにして、後続の再形成呼び出しができるようにする必要があります。必要な出力を提供します。

正しく転置する方法を理解するには、入力に沿って要素をトレースし、出力の各軸に到達するためにジャンプする必要がある軸を把握する必要があります。それに応じて転置が続きます。 Divakarの答え は、これを説明するスターリングジョブを実行します。

11
cs95

Divarkarの答えは素晴らしい ですが、transposereshapeがカバーするすべての可能なケースをチェックする方が簡単な場合もあります。

たとえば、次のコード

n, m = 4, 2
arr = np.arange(n*n*m*m).reshape(n,n,m,m)
for permut in itertools.permutations(range(4)):
    arr2 = (arr.transpose(permut)).reshape(n*m, n*m)
    print(permut, arr2[0])

transpose + reshapeを使用して4次元配列から取得できるすべてのものが得られます。出力がどのように表示されるかはわかっているので、正しい答えを示した順列を選択するだけです。希望どおりの結果が得られなかった場合、transpose + reshapeは私のケースをカバーするには一般的ではなく、もっと複雑なことをする必要があります。

0
cheyp