¿Cómo construyo una matriz numpy a partir de un generador?
¿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?
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=-1
toma todos los elementos del iterable. Requiere que dtype
se establezca explícitamente. En mi caso, esto funcionó:
numpy.fromiter(something.generate(from_this_input), float)
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:
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
están dispuestos a almacenar sus elementos en una lista intermedia:
my_array = numpy.array(list(gimme()))
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).
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.stack
consume 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.