Come faccio a creare un array intorpidito da un generatore?


166

Come posso costruire una matrice numpy da un oggetto generatore?

Vorrei illustrare il problema:

>>> 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 questo caso, gimme()è il generatore il cui output mi piacerebbe trasformare in un array. Tuttavia, il costruttore dell'array non esegue l'iterazione sul generatore, ma memorizza semplicemente il generatore stesso. Il comportamento che desidero è quello di numpy.array(list(gimme())), ma non voglio pagare l'overhead di memoria di avere contemporaneamente l'elenco intermedio e l'array finale. Esiste un modo più efficiente in termini di spazio?


6
Questo è un problema interessante Mi sono imbattuto in questo from numpy import *; print any(False for i in range(1))- che ombreggia il built-in any()e produce il risultato opposto (come so ora).
Moooeeeep

4
@moooeeeep è terribile. se numpynon può (o non vuole) trattare i generatori come fa Python, almeno dovrebbe sollevare un'eccezione quando riceve un generatore come argomento.
massimo

1
@max Ho calpestato la stessa identica miniera. Apparentemente questo è stato sollevato nell'elenco di NumPy (e precedenti ) concludendo che questo non verrà modificato per aumentare l'eccezione e si dovrebbe sempre usare gli spazi dei nomi.
alexei,

Risposte:


128

Le matrici Numpy richiedono che la loro lunghezza sia impostata esplicitamente al momento della creazione, a differenza delle liste di Python. Ciò è necessario affinché lo spazio per ciascun elemento possa essere allocato consecutivamente in memoria. L'allocazione consecutiva è la caratteristica chiave degli array intorpiditi: questo combinato con l'implementazione del codice nativo consente alle operazioni su di essi di eseguire molto più rapidamente degli elenchi regolari.

Tenendo presente questo, è tecnicamente impossibile prendere un oggetto generatore e trasformarlo in un array a meno che:

  1. può prevedere quanti elementi produrrà durante l'esecuzione:

    my_array = numpy.empty(predict_length())
    for i, el in enumerate(gimme()): my_array[i] = el
    
  2. sono disposti a memorizzare i suoi elementi in un elenco intermedio:

    my_array = numpy.array(list(gimme()))
  3. può creare due generatori identici, eseguire il primo per trovare la lunghezza totale, inizializzare l'array e quindi eseguire nuovamente il generatore per trovare ogni elemento:

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

1 è probabilmente quello che stai cercando. 2 è spazio inefficiente e 3 è tempo inefficiente (è necessario attraversare il generatore due volte).


11
Il builtin array.arrayè un elenco contiguo non collegato e puoi semplicemente array.array('f', generator). Dire che è impossibile è fuorviante. È solo allocazione dinamica.
Cuadue,

1
Perché numpy.array non fa l'allocazione della memoria allo stesso modo dell'array.array incorporato, come dice Cuadue. Qual è il tradeof? Chiedo perché c'è memoria allocata contigua in entrambi gli esempi. O no?
jgomo3,

3
numpy assume che le dimensioni della sua matrice non cambino. Si basa fortemente su diverse viste dello stesso pezzo di memoria, quindi consentire l'espansione e la riallocazione di array richiederebbe un ulteriore livello di indiretta per abilitare le viste, ad esempio.
joeln

2
Usare vuoto è un po 'più veloce. Dato che inizializzerai i valori in ogni modo, non è necessario farlo due volte.
Kaushik Ghose,

Vedi anche la risposta di @ dhill di seguito, che è più veloce di 1.
Bill

206

Un google dietro questo risultato stackoverflow, ho scoperto che esiste un numpy.fromiter(data, dtype, count). L'impostazione predefinita count=-1prende tutti gli elementi dall'iterabile. Richiede dtypedi essere impostato esplicitamente. Nel mio caso, ha funzionato:

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


come applicheresti questo alla domanda? numpy.fromiter(gimme(), float, count=-1)non funziona. Cosa significa something?
Matthias 009,

1
@ Matthias009 numpy.fromiter(gimme(), float, count=-1)funziona per me.
Moooeeeep

14
Un thread che spiega perché fromiterfunziona solo su array 1D: mail.scipy.org/pipermail/numpy-discussion/2007-August/… .
massimo

2
prima, count=-1non è necessario specificare, poiché è l'impostazione predefinita.
askewchan,

5
Se si conosce in anticipo la lunghezza dell'iterabile, specificare countper migliorare le prestazioni. In questo modo alloca la memoria prima di riempirla di valori anziché ridimensionarla su richiesta (vedere la documentazione di numpy.fromiter)
Eddy

15

Mentre puoi creare un array 1D da un generatore con numpy.fromiter(), puoi creare un array ND da un generatore con numpy.stack:

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

Funziona anche con array 1D:

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

Si noti che numpy.stacksta consumando internamente il generatore e creando un elenco intermedio con arrays = [asanyarray(arr) for arr in arrays]. L'implementazione può essere trovata qui .


1
Questa è una soluzione pulita, grazie per averlo sottolineato. Ma sembra essere un po 'più lento (nella mia applicazione) rispetto all'uso np.array(tuple(mygen)). Ecco i risultati del test: %timeit np.stack(permutations(range(10), 7)) 1 loop, best of 3: 1.9 s per looprispetto a%timeit np.array(tuple(permutations(range(10), 7))) 1 loop, best of 3: 427 ms per loop
Bill

13
Sembra fantastico e funziona per me. Ma con Numpy 1.16.1 ricevo questo avvertimento: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

Un po 'tangenziale, ma se il tuo generatore è una comprensione dell'elenco, puoi usarlo numpy.whereper ottenere in modo più efficace il tuo risultato (l'ho scoperto nel mio codice dopo aver visto questo post)


0

Le funzioni vstack , hstack e dstack possono assumere come generatori di input che producono array multidimensionali.


3
Puoi fare un esempio nel caso in cui i collegamenti cambino o qualcosa del genere? :)
Ari Cooper-Davis,

Queste funzioni possono assumere un generatore di array, non un generatore di valori
retnikt
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.