Kopieren Sie das 2D-Array N-mal in die 3. Dimension (Python)

105

Ich möchte ein numpy 2D-Array in eine dritte Dimension kopieren. Beispiel: (2D) numpy array:

import numpy as np
arr = np.array([[1,2],[1,2]])
# arr.shape = (2, 2)

konvertiere es in eine 3D-Matrix mit N solchen Kopien in einer neuen Dimension. Acting auf arrmit N = 3, sollte der Ausgang sein:

new_arr = np.array([[[1,2],[1,2]],[[1,2],[1,2]],[[1,2],[1,2]]])
# new_arr.shape = (3, 2, 2)
anon01
quelle

Antworten:

146

Der wahrscheinlich sauberste Weg ist np.repeat:

a = np.array([[1, 2], [1, 2]])
print(a.shape)
# (2,  2)

# indexing with np.newaxis inserts a new 3rd dimension, which we then repeat the
# array along, (you can achieve the same effect by indexing with None, see below)
b = np.repeat(a[:, :, np.newaxis], 3, axis=2)

print(b.shape)
# (2, 2, 3)

print(b[:, :, 0])
# [[1 2]
#  [1 2]]

print(b[:, :, 1])
# [[1 2]
#  [1 2]]

print(b[:, :, 2])
# [[1 2]
#  [1 2]]

Trotzdem können Sie häufig vermeiden, Ihre Arrays mithilfe von Broadcasting vollständig zu wiederholen . Angenommen, ich wollte einen (3,)Vektor hinzufügen :

c = np.array([1, 2, 3])

zu a. Ich konnte den Inhalt von a3 Mal in der dritten Dimension kopieren und dann den Inhalt von czweimal sowohl in der ersten als auch in der zweiten Dimension kopieren , so dass meine beiden Arrays waren (2, 2, 3), und dann ihre Summe berechnen. Dies ist jedoch viel einfacher und schneller:

d = a[..., None] + c[None, None, :]

Hier a[..., None]hat Form (2, 2, 1)und c[None, None, :]hat Form (1, 1, 3)*. Wenn ich die Summe berechne, wird das Ergebnis entlang der Abmessungen der Größe 1 "ausgestrahlt", was mir ein Ergebnis der Form gibt (2, 2, 3):

print(d.shape)
# (2,  2, 3)

print(d[..., 0])    # a + c[0]
# [[2 3]
#  [2 3]]

print(d[..., 1])    # a + c[1]
# [[3 4]
#  [3 4]]

print(d[..., 2])    # a + c[2]
# [[4 5]
#  [4 5]]

Broadcasting ist eine sehr leistungsfähige Technik, da der zusätzliche Aufwand für das Erstellen wiederholter Kopien Ihrer Eingabearrays im Speicher vermieden wird.


* Obwohl ich sie aus Gründen der Übersichtlichkeit eingefügt habe, sind die NoneIndizes in cnicht wirklich erforderlich - Sie können dies auch tun a[..., None] + c, dh ein (2, 2, 1)Array gegen ein (3,)Array senden . Dies liegt daran, dass, wenn eines der Arrays weniger Dimensionen als das andere hat, nur die nachfolgenden Dimensionen der beiden Arrays kompatibel sein müssen. Um ein komplizierteres Beispiel zu geben:

a = np.ones((6, 1, 4, 3, 1))  # 6 x 1 x 4 x 3 x 1
b = np.ones((5, 1, 3, 2))     #     5 x 1 x 3 x 2
result = a + b                # 6 x 5 x 4 x 3 x 2
ali_m
quelle
Um sicherzustellen , dass dies in der Tat das richtige Ergebnis gibt, können Sie auch ausdrucken b[:,:,0], b[:,:,1]und b[:,:,2]. Jedes Slice der dritten Dimension ist eine Kopie des ursprünglichen 2D-Arrays. Das ist nicht so offensichtlich, wenn man es nur ansieht print(b).
Ely
Was ist der Unterschied zwischen None und np.newaxis? Als ich es getestet habe, gab es das gleiche Ergebnis.
Monolith
1
@wedran Sie sind genau gleich - np.newaxisist nur ein Alias ​​vonNone
ali_m
27

Ein anderer Weg ist zu verwenden numpy.dstack. Angenommen, Sie möchten die Matrixzeiten wiederholen a num_repeats:

import numpy as np
b = np.dstack([a]*num_repeats)

Der Trick besteht darin, die Matrix ain eine Liste eines einzelnen Elements zu verpacken und dann mit dem *Operator die Elemente in dieser Liste zu duplizieren num_repeats.

Zum Beispiel, wenn:

a = np.array([[1, 2], [1, 2]])
num_repeats = 5

Dies wiederholt das Array von [1 2; 1 2]5 Mal in der dritten Dimension. So überprüfen Sie (in IPython):

In [110]: import numpy as np

In [111]: num_repeats = 5

In [112]: a = np.array([[1, 2], [1, 2]])

In [113]: b = np.dstack([a]*num_repeats)

In [114]: b[:,:,0]
Out[114]: 
array([[1, 2],
       [1, 2]])

In [115]: b[:,:,1]
Out[115]: 
array([[1, 2],
       [1, 2]])

In [116]: b[:,:,2]
Out[116]: 
array([[1, 2],
       [1, 2]])

In [117]: b[:,:,3]
Out[117]: 
array([[1, 2],
       [1, 2]])

In [118]: b[:,:,4]
Out[118]: 
array([[1, 2],
       [1, 2]])

In [119]: b.shape
Out[119]: (2, 2, 5)

Am Ende können wir sehen, dass die Form der Matrix 2 x 2mit 5 Schichten in der dritten Dimension ist.

Rayryeng
quelle
Wie ist das zu vergleichen reshape? Schneller? gibt die gleiche Struktur? Es ist definitiv ordentlicher.
Ander Biguri
@AnderBiguri Ich habe noch nie ein Benchmarking durchgeführt ... Ich habe dies hier hauptsächlich der Vollständigkeit halber eingefügt. Es wird interessant sein, die Unterschiede zu sehen.
Rayryeng
1
Ich habe gerade img = np.dstack ([arr] * 3) gemacht und gut funktioniert! Vielen Dank
thanos.a
1
Ich dachte, ich könnte aus Effizienzgründen eine angesehene Ausgabe vorschlagen. Als alter Posten haben die Leute das vielleicht verpasst. Es wurde eine Lösung für diese Fragen und Antworten hinzugefügt.
Divakar
1
IMHO am besten lesbare Lösung, aber es wäre großartig, sie mit anderen Vergleichsmethoden zu vergleichen.
Mrgloom
15

Verwenden Sie eine Ansicht und erhalten Sie freie Laufzeit! Erweitern Sie generische n-dimArrays aufn+1-dim

In NumPy eingeführt1.10.0 , können wir nutzen, numpy.broadcast_toum einfach eine 3DAnsicht in das 2DEingabearray zu generieren . Der Vorteil wäre kein zusätzlicher Speicheraufwand und praktisch freie Laufzeit. Dies ist in Fällen wichtig, in denen die Arrays groß sind und wir mit Ansichten arbeiten können. Dies würde auch bei generischen n-dimFällen funktionieren .

Ich würde das Wort stackanstelle von verwenden copy, da die Leser es möglicherweise mit dem Kopieren von Arrays verwechseln, die Speicherkopien erstellen.

Stapel entlang der ersten Achse

Wenn wir Eingaben arrentlang der ersten Achse stapeln möchten , wäre die Lösung np.broadcast_tozum Erstellen einer 3DAnsicht:

np.broadcast_to(arr,(3,)+arr.shape) # N = 3 here

Stapel entlang der dritten / letzten Achse

Um Eingaben arrentlang der dritten Achse zu stapeln , wäre die Lösung zum Erstellen einer 3DAnsicht:

np.broadcast_to(arr[...,None],arr.shape+(3,))

Wenn wir tatsächlich eine Speicherkopie benötigen, können wir diese jederzeit anhängen .copy(). Daher wären die Lösungen -

np.broadcast_to(arr,(3,)+arr.shape).copy()
np.broadcast_to(arr[...,None],arr.shape+(3,)).copy()

So funktioniert das Stapeln für die beiden Fälle, die mit ihren Forminformationen für einen Beispielfall angezeigt werden:

# Create a sample input array of shape (4,5)
In [55]: arr = np.random.rand(4,5)

# Stack along first axis
In [56]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[56]: (3, 4, 5)

# Stack along third axis
In [57]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[57]: (4, 5, 3)

Dieselben Lösungen würden funktionieren, um eine n-dimEingabe zu erweitern und die n+1-dimAusgabe entlang der ersten und letzten Achse anzuzeigen. Lassen Sie uns einige Fälle mit höherer Dunkelheit untersuchen -

3D-Eingabefall:

In [58]: arr = np.random.rand(4,5,6)

# Stack along first axis
In [59]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[59]: (3, 4, 5, 6)

# Stack along last axis
In [60]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[60]: (4, 5, 6, 3)

4D Eingangsfall:

In [61]: arr = np.random.rand(4,5,6,7)

# Stack along first axis
In [62]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[62]: (3, 4, 5, 6, 7)

# Stack along last axis
In [63]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[63]: (4, 5, 6, 7, 3)

und so weiter.

Timings

Lassen Sie uns einen großen Beispielfall verwenden 2Dund die Timings abrufen und überprüfen, ob die Ausgabe a ist view.

# Sample input array
In [19]: arr = np.random.rand(1000,1000)

Lassen Sie uns beweisen, dass die vorgeschlagene Lösung tatsächlich eine Ansicht ist. Wir werden das Stapeln entlang der ersten Achse verwenden (die Ergebnisse für das Stapeln entlang der dritten Achse wären sehr ähnlich) -

In [22]: np.shares_memory(arr, np.broadcast_to(arr,(3,)+arr.shape))
Out[22]: True

Lassen Sie uns das Timing zeigen, dass es praktisch kostenlos ist -

In [20]: %timeit np.broadcast_to(arr,(3,)+arr.shape)
100000 loops, best of 3: 3.56 µs per loop

In [21]: %timeit np.broadcast_to(arr,(3000,)+arr.shape)
100000 loops, best of 3: 3.51 µs per loop

Eine Ansicht zu sein, Nvon 3zu 3000ändern, hat nichts an Timings geändert und beide sind an Timing-Einheiten vernachlässigbar. Daher effizient sowohl im Speicher als auch in der Leistung!

Divakar
quelle
3
A=np.array([[1,2],[3,4]])
B=np.asarray([A]*N)

Bearbeiten Sie @ Mr.F, um die Dimensionsreihenfolge beizubehalten:

B=B.T
yevgeniy
quelle
Dies führt für mich zu einem N x 2 x 2-Array, z. B. B.shapeDrucken (N, 2, 2)für einen beliebigen Wert von N. Wenn Sie transponieren Bmit B.Tdann passt es die erwartete Ausgabe.
Ely
@ Mr.F - Du hast recht. Dies wird entlang der ersten Dimension gesendet, und B[0], B[1],...auf diese Weise erhalten Sie das richtige Slice, das ich argumentieren und sagen werde, dass es einfacher zu tippen ist als zu verwenden B[:,:,0], B[:,:,1]usw.
Rayryeng
Die Eingabe ist möglicherweise einfacher, aber wenn Sie dies beispielsweise mit Bilddaten tun, ist dies weitgehend falsch, da fast alle Algorithmen erwarten, dass die Konventionen der linearen Algebra für die 2D-Schichten von Pixelkanälen verwendet werden. Es ist schwer vorstellbar, dass Anwendungen mit einem 2D-Array beginnen, Zeilen und Spalten mit einer bestimmten Konvention behandeln und dann mehrere Kopien derselben Sache in eine neue Achse hineinragen sollen, aber plötzlich soll die erste Achse ihre Bedeutung ändern sei die neue Achse ...
ely
@ Mr.F - Oh sicher. Ich kann nicht erraten, für welche Anwendungen Sie die 3D-Matrix in Zukunft verwenden möchten. Davon abgesehen hängt alles von der Anwendung ab. FWIW, ich bevorzuge das B[:,:,i]und das ist es, woran ich gewöhnt bin.
Rayryeng
2

Hier ist ein Sendebeispiel, das genau das tut, was angefordert wurde.

a = np.array([[1, 2], [1, 2]])
a=a[:,:,None]
b=np.array([1]*5)[None,None,:]

Dann b*aist das gewünschte Ergebnis und (b*a)[:,:,0]erzeugt array([[1, 2],[1, 2]]), welches das Original ist a, wie auch (b*a)[:,:,1]usw.

Mike O'Connor
quelle
1

Dies kann jetzt auch mit np.tile wie folgt erreicht werden:

import numpy as np

a = np.array([[1,2],[1,2]])
b = np.tile(a,(3, 1,1))

b.shape
(3,2,2)

b
array([[[1, 2],
        [1, 2]],

       [[1, 2],
        [1, 2]],

       [[1, 2],
        [1, 2]]])
FBruzzesi
quelle