¿Cómo construyo una matriz numpy a partir de un generador?

Resuelto saffsd asked hace 15 años • 5 respuestas

¿Cómo puedo construir una matriz numpy a partir de un objeto generador?

Permítanme ilustrar el 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])

En este caso, gimme()es el generador cuya salida me gustaría convertir en una matriz. Sin embargo, el constructor de la matriz no itera sobre el generador, simplemente almacena el generador mismo. El comportamiento que deseo es el de numpy.array(list(gimme())), pero no quiero pagar la sobrecarga de memoria de tener la lista intermedia y la matriz final en la memoria al mismo tiempo. ¿Existe una forma más eficiente en cuanto a espacio?

saffsd avatar Dec 15 '08 12:12 saffsd
Aceptado

Al buscar en Google detrás de este resultado de stackoverflow, encontré que hay un archivo numpy.fromiter(data, dtype, count). El valor predeterminado count=-1toma todos los elementos del iterable. Requiere que dtypese establezca explícitamente. En mi caso, esto funcionó:

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

dhill avatar Feb 24 '2009 03:02 dhill

Las matrices Numpy requieren que su longitud se establezca explícitamente en el momento de la creación, a diferencia de las listas de Python. Esto es necesario para que se pueda asignar espacio consecutivamente a cada elemento en la memoria. La asignación consecutiva es la característica clave de las matrices numpy: esto, combinado con la implementación de código nativo, permite que las operaciones en ellas se ejecuten mucho más rápido que las listas normales.

Teniendo esto en cuenta, es técnicamente imposible tomar un objeto generador y convertirlo en una matriz a menos que:

  1. Puede predecir cuántos elementos producirá cuando se ejecute:

    my_array = numpy.empty(predict_length())
    for i, el in enumerate(gimme()): my_array[i] = el
    
  2. están dispuestos a almacenar sus elementos en una lista intermedia:

    my_array = numpy.array(list(gimme()))
    
  3. Puede hacer dos generadores idénticos, ejecutar el primero para encontrar la longitud total, inicializar la matriz y luego ejecutar el generador nuevamente para encontrar cada elemento:

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

1 es probablemente lo que estás buscando. 2 es ineficiente en espacio y 3 es ineficiente en tiempo (tienes que pasar por el generador dos veces).

shsmurfy avatar Dec 15 '2008 06:12 shsmurfy

Si bien puedes crear una matriz 1D desde un generador con numpy.fromiter(), puedes crear una matriz ND desde un generador con numpy.stack:

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

También funciona para matrices 1D:

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

Tenga en cuenta que numpy.stackconsume internamente el generador y crea una lista intermedia con arrays = [asanyarray(arr) for arr in arrays]. La implementación se puede encontrar aquí .

[ADVERTENCIA] Como señaló @Joseh Seedy, Numpy 1.16 genera una advertencia que anula el uso de dicha función con generadores.

mdeff avatar Aug 31 '2017 11:08 mdeff