wake-up-neo.net

Intuition und Idee hinter der Umgestaltung eines 4D-Arrays in ein 2D-Array in NumPy

Bei der Implementierung eines Kronecker-product aus pädagogischen Gründen (ohne das offensichtliche und leicht verfügbare np.kron() zu verwenden), erhielt ich ein 4-dimensionales Array als Zwischenergebnis, das ich neu formieren muss, um das Endergebnis zu erhalten .

Aber ich kann meinen Kopf immer noch nicht darum bitten, diese hochdimensionalen Arrays umzugestalten. Ich habe dieses 4D-Array:

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

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


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

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

Dies ist von Form (2, 2, 2, 2) und ich möchte es in (4,4) umformen. Man könnte meinen, dass dies offensichtlich ist 

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

Die oben genannte Umformung nicht gibt mir das erwartete Ergebnis, das heißt:

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

Wie Sie sehen können, sind alle Elemente im erwarteten Ergebnis im 4D-Array vorhanden. Ich kann es einfach nicht verstehen, das umformen richtig zu machen, je nach Bedarf. Neben der Antwort wäre eine Erklärung, wie die Variable reshape für solche hochdimensionalen Arrays gemacht wird, wirklich hilfreich. Vielen Dank!

14
kmario23

Allgemeine Idee für die Transformation von nd nach nd

Die Idee mit einer solchen Transformation von nd zu nd besteht aus nur zwei Dingen:

Achsen permutieren: Die Reihenfolge so festlegen, dass die abgeflachte Version der abgeflachten Ausgabeversion entspricht. Wenn Sie es also zweimal verwenden, schauen Sie noch einmal, weil Sie es nicht sollten.

Umformen: Um die Achsen zu teilen oder die endgültige Ausgabe auf die gewünschte Form zu bringen. Das Teilen von Achsen wird meistens am Anfang benötigt, wenn die Eingabe niedriger ist und wir in Blöcke aufteilen müssen. Auch dies sollte nicht mehr als zweimal benötigt werden.

Daher hätten wir im Allgemeinen drei Schritte:

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

 Create more axes             Bring axes             Merge axes
                          into correct order

Rückverfolgungsmethode

Der sicherste Weg, um die Eingabe und Ausgabe zu lösen, ist das, was man als Rückverfolgungsmethode bezeichnen könnte, dh die Achsen der Eingabe zu teilen (beim Übergang von kleinerem nd zu größerem nd) oder teilen Sie die Achsen der Ausgabe (wenn Sie von größerem nd zu kleinerem nd wechseln). Die Idee bei der Aufteilung ist es, die Anzahl der Dimits des kleineren nd mit der des größeren nd zu vergleichen. Untersuchen Sie dann die Schritte der Ausgabe und vergleichen Sie sie mit der Eingabe, um die erforderliche Permut-Reihenfolge zu erhalten. Schließlich kann am Ende eine Umformung (Standardmethode oder C-Reihenfolge) erforderlich sein, um die Achsen zusammenzuführen, wenn die letzte kleiner als nd ist.

Wenn sowohl Input als auch Output die gleiche Anzahl von Dimits haben, müssten wir beide aufteilen und in Blöcke aufteilen und ihre Schritte gegeneinander untersuchen. In solchen Fällen sollten wir den zusätzlichen Eingabeparameter der Blockgrößen haben, aber das ist wahrscheinlich nicht thematisch.

Beispiel

Verwenden wir diesen speziellen Fall, um zu demonstrieren, wie diese Strategien angewendet werden. Hier ist die Eingabe 4D, Während die Ausgabe 2D Ist. Wir brauchen also höchstwahrscheinlich keine Umformung, um uns zu trennen. Wir müssen also mit dem Vertauschen der Achsen beginnen. Da die endgültige Ausgabe nicht 4D, Sondern eine 2D Ist, benötigen wir am Ende eine Umgestaltung.

Die Eingabe hier ist nun:

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

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


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

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

Die erwartete Ausgabe ist:

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

Dies ist auch eine größere nd zu kleinere nd Transformation, so dass die Rückverfolgungsmethode beinhalten würde, die Ausgabe aufzuteilen und ihre Schritte zu studieren und gegen die abzugleichen entsprechende eingabewerte:

                    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]])

Daher ist die permutierte Reihenfolge (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]]]])

Dann einfach auf die erwartete Form umformen:

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]])

Weitere Beispiele

Ich habe meine Geschichte ausgegraben und einige Q&As Gefunden, die auf nd bis nd Transformationen basieren. Diese könnten als andere Beispielfälle dienen, wenn auch (meistens) mit geringerer Erklärung. Wie bereits erwähnt, haben höchstens zwei reshapes und höchstens ein swapaxes/transpose die Arbeit überall erledigt. Sie sind unten aufgeführt:

27
Divakar

Es scheint, als würden Sie nach einer transpose suchen, gefolgt von einer 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]])

Um zu verstehen, warum eine Transposition erforderlich ist, analysieren wir Ihre falsch geformte Ausgabe (die durch einen einzelnen Aufruf der Variable reshape erhalten wird), um Verstehen zu erfahren, warum sie falsch ist.

Eine einfache 2D-Version dieses Ergebnisses (ohne Transposition) sieht folgendermaßen aus: 

x.reshape(4, 4)

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

Betrachten Sie nun diese Ausgabe in Bezug auf Ihre erwartete Ausgabe - 

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

Sie werden feststellen, dass Ihr tatsächliches Ergebnis durch eine Z-artige Durchquerung Ihrer falsch geformten Ausgabe erzielt wird. 

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

Dies bedeutet, dass Sie sich in unterschiedlichen Schritten über das Array bewegen müssen, um das Ergebnis actual zu erhalten. Abschließend reicht eine einfache Umformung nicht aus. Sie müssen das ursprüngliche Array transponieren so, dass diese Z-artigen Elemente als zusammenhängende Elemente dargestellt werden, sodass ein nachfolgender Aufruf zur Umformung die Ausgabe liefert, die Sie benötigen.

Um zu verstehen, wie man korrekt transponiert, sollten Sie die Elemente entlang der Eingabe verfolgen und herausfinden, welche Achsen Sie springen müssen, um zu den einzelnen Achsen in der Ausgabe zu gelangen. Die Umsetzung erfolgt entsprechend. Divakars Antwort erklärt das klasse.

11
coldspeed

Die Antwort von Divarkar ist großartig , obwohl es für mich manchmal einfacher ist, alle möglichen Fälle zu prüfen, die transpose und reshape abdecken.

Zum Beispiel den folgenden Code

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])

gibt mir alles, was man von einem 4-dimensionalen Array mit transpose + reshape bekommen kann. Da ich weiß, wie die Ausgabe aussehen soll, wähle ich einfach die Permutation aus, die mir die richtige Antwort zeigte. Wenn ich nicht bekam, was ich wollte, ist transpose + reshape nicht allgemein genug, um meinen Fall zu behandeln, und ich muss etwas komplizierter machen.

0
cheyp