Wie baue ich ein Numpy-Array aus einem Generator?

166

Wie kann ich aus einem Generatorobjekt ein Numpy-Array erstellen?

Lassen Sie mich das Problem veranschaulichen:

>>> import numpy
>>> def gimme():
...   for x in xrange(10):
...     yield x
...
>>> gimme()
<generator object at 0x28a1758>
>>> list(gimme())
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> numpy.array(xrange(10))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> numpy.array(gimme())
array(<generator object at 0x28a1758>, dtype=object)
>>> numpy.array(list(gimme()))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In diesem Fall gimme()handelt es sich um den Generator, dessen Ausgabe ich in ein Array verwandeln möchte. Der Array-Konstruktor iteriert jedoch nicht über den Generator, sondern speichert einfach den Generator selbst. Das Verhalten, das ich mir wünsche, ist das von numpy.array(list(gimme())), aber ich möchte nicht den Speicheraufwand dafür bezahlen, dass die Zwischenliste und das endgültige Array gleichzeitig im Speicher sind. Gibt es einen platzsparenderen Weg?

saffsd
quelle
6
Dies ist ein interessantes Thema. Ich bin darauf gestoßen from numpy import *; print any(False for i in range(1))- was das Eingebaute beschattet any()und das gegenteilige Ergebnis erzeugt (wie ich jetzt weiß).
Moooeeeep
4
@moooeeeep das ist schrecklich. Wenn numpyGeneratoren nicht wie Python behandelt werden können (oder wollen), sollte zumindest eine Ausnahme ausgelöst werden, wenn ein Generator als Argument empfangen wird.
Max
1
@max Ich trat genau auf meine. Anscheinend wurde dies in der NumPy-Liste (und früher ) erwähnt, was zu dem Schluss führte, dass dies nicht geändert wird, um eine Ausnahme auszulösen, und man sollte immer Namespaces verwenden.
Alexei

Antworten:

128

Bei Numpy-Arrays muss ihre Länge im Gegensatz zu Python-Listen zum Zeitpunkt der Erstellung explizit festgelegt werden. Dies ist erforderlich, damit Speicherplatz für jedes Element nacheinander im Speicher zugewiesen werden kann. Die fortlaufende Zuweisung ist das Hauptmerkmal von Numpy-Arrays: In Kombination mit der Implementierung von nativem Code können Operationen an ihnen viel schneller ausgeführt werden als reguläre Listen.

Vor diesem Hintergrund ist es technisch unmöglich, ein Generatorobjekt in ein Array umzuwandeln, es sei denn, Sie:

  1. kann vorhersagen, wie viele Elemente es beim Ausführen ergeben wird:

    my_array = numpy.empty(predict_length())
    for i, el in enumerate(gimme()): my_array[i] = el
    
  2. sind bereit, ihre Elemente in einer Zwischenliste zu speichern:

    my_array = numpy.array(list(gimme()))
  3. Sie können zwei identische Generatoren erstellen, den ersten durchlaufen, um die Gesamtlänge zu ermitteln, das Array initialisieren und dann den Generator erneut durchlaufen, um jedes Element zu finden:

    length = sum(1 for el in gimme())
    my_array = numpy.empty(length)
    for i, el in enumerate(gimme()): my_array[i] = el
    

1 ist wahrscheinlich das, wonach Sie suchen. 2 ist platzsparend und 3 zeitlich ineffizient (Sie müssen den Generator zweimal durchlaufen).

shsmurfy
quelle
11
Das eingebaute array.arrayist eine zusammenhängende nicht verknüpfte Liste, und Sie können einfach array.array('f', generator). Zu sagen, dass es unmöglich ist, ist irreführend. Es ist nur eine dynamische Zuordnung.
Cuadue
1
Warum numpy.array die Speicherzuweisung nicht auf die gleiche Weise wie das eingebaute array.array ausführt, wie Cuadue sagt. Was ist der Handel mit? Ich frage, weil in beiden Beispielen zusammenhängend Speicher zugewiesen ist. Oder nicht?
jgomo3
3
numpy geht davon aus, dass sich die Arraygrößen nicht ändern. Es basiert stark auf unterschiedlichen Ansichten desselben Speicherblocks. Wenn also Arrays erweitert und neu zugewiesen werden sollen, ist beispielsweise eine zusätzliche Indirektionsebene erforderlich, um Ansichten zu ermöglichen.
Joeln
2
Die Verwendung von leer ist etwas schneller. Da Sie die Werte auf irgendeine Weise initialisieren, müssen Sie dies nicht zweimal tun.
Kaushik Ghose
Siehe auch @ dhills Antwort unten, die schneller als 1 ist.
Bill
206

Ein Google hinter diesem Stackoverflow-Ergebnis fand ich, dass es eine gibt numpy.fromiter(data, dtype, count). Die Standardeinstellung count=-1übernimmt alle Elemente aus der Iterable. Es erfordert eine dtypeexplizite Einstellung. In meinem Fall hat das funktioniert:

numpy.fromiter(something.generate(from_this_input), float)

Dhill
quelle
Wie würden Sie dies auf die Frage anwenden? numpy.fromiter(gimme(), float, count=-1)funktioniert nicht. Wofür steht something?
Matthias 009
1
@ Matthias009 numpy.fromiter(gimme(), float, count=-1)arbeitet für mich.
Moooeeeep
14
Ein Thread, der erklärt, warum fromiternur 1D-Arrays funktionieren : mail.scipy.org/pipermail/numpy-discussion/2007-August/… .
Max
2
fwiw count=-1muss nicht angegeben werden, da dies die Standardeinstellung ist.
Askewchan
5
Wenn Sie die Länge der iterierbaren Datei im Voraus kennen, geben Sie die countan, um die Leistung zu verbessern. Auf diese Weise wird der Speicher zugewiesen, bevor er mit Werten gefüllt wird, anstatt bei Bedarf die Größe zu ändern (siehe Dokumentation von numpy.fromiter)
Eddy
15

Während Sie ein 1D-Array aus einem Generator mit numpy.fromiter()erstellen können, können Sie ein ND-Array aus einem Generator erstellen mit numpy.stack:

>>> mygen = (np.ones((5, 3)) for _ in range(10))
>>> x = numpy.stack(mygen)
>>> x.shape
(10, 5, 3)

Es funktioniert auch für 1D-Arrays:

>>> numpy.stack(2*i for i in range(10))
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

Beachten Sie, dass numpy.stackder Generator intern verbraucht und eine Zwischenliste mit erstellt wird arrays = [asanyarray(arr) for arr in arrays]. Die Implementierung finden Sie hier .

mdeff
quelle
1
Dies ist eine gute Lösung, danke für den Hinweis. Aber es scheint (in meiner Anwendung) etwas langsamer zu sein als die Verwendung np.array(tuple(mygen)). Hier sind die Testergebnisse: im %timeit np.stack(permutations(range(10), 7)) 1 loop, best of 3: 1.9 s per loopVergleich zu%timeit np.array(tuple(permutations(range(10), 7))) 1 loop, best of 3: 427 ms per loop
Bill
13
Das scheint großartig und funktioniert für mich. Aber mit Numpy 1.16.1 bekomme ich folgende Warnung:FutureWarning: arrays to stack must be passed as a "sequence" type such as list or tuple. Support for non-sequence iterables such as generators is deprecated as of NumPy 1.16 and will raise an error in the future.
Joseph Sheedy
6

Etwas tangential, aber wenn Ihr Generator ein Listenverständnis ist, können Sie es verwenden numpy.where, um Ihr Ergebnis effektiver zu erhalten (ich habe dies in meinem eigenen Code entdeckt, nachdem ich diesen Beitrag gesehen habe).

Benjamin Horstman
quelle
0

Die Funktionen vstack , hstack und dstack können als Eingabegeneratoren verwendet werden, die mehrdimensionale Arrays ergeben.

Mike R.
quelle
3
Können Sie ein Beispiel geben, falls sich die Links ändern oder so? :)
Ari Cooper-Davis
Diese Funktionen können einen Generator von Arrays verwenden, keinen Generator von Werten
retnikt