2008-12-15 20 views
112

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

Vamos a 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, dame() 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. 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. ¿Hay una manera más eficiente en el uso del espacio?

+5

Este es un tema interesante. Me encontré con esto por 'de numpy import *; print any (False for i in range (1)) '- que sombrea el built-in [' any() '] (http://docs.python.org/library/functions.html#any) y produce lo opuesto resultado (como sé ahora). – moooeeeep

+4

@moooeeeep eso es terrible. si 'numpy' no puede (o no quiere) tratar generadores como lo hace Python, al menos debería generar una excepción cuando recibe un generador como argumento. – max

+1

@max pisé exactamente la misma mina. Al parecer, esto se planteó [en la lista NumPy] (http://thread.gmane.org/gmane.comp.python.numeric.general/47681/focus=47702) (y [anterior] (http: //thread.gmane). .org/gmane.comp.python.numeric.general/13197)) concluyendo que esto no se cambiará para generar una excepción y que uno siempre debe usar espacios de nombres. – alexei

Respuesta

93

Las matrices de 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 el espacio para cada elemento se pueda asignar consecutivamente en la memoria. La asignación consecutiva es la característica clave de las matrices numpy: esto combinado con la implementación del código nativo permite que las operaciones en ellas se ejecuten mucho más rápido que las listas regulares.

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

  1. puede predecir cuántos elementos se producirá cuando se ejecuta:

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

    my_array = numpy.array(list(gimme())) 
    
  3. pueden hacer dos generadores idénticos, funcionamiento a través de la primera uno para encontrar la longitud total, inicializar la matriz, y luego ejecutar a través del generador de nuevo 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 
    

es probablemente Que estas buscando. es ineficiente en el espacio, y es ineficiente en el tiempo (tiene que pasar por el generador dos veces).

+7

El 'array.array' incorporado es una lista contigua no vinculada, y usted puede simplemente' array.array ('f', generator) '. Decir que es imposible es engañoso. Es solo asignación dinámica. – Cuadue

+0

Por qué numpy.array no hace la asignación de memoria de la misma manera que el array.array incorporado, como dice Cuadue. ¿Cuál es el tradeof? Lo pregunto porque hay memoria asignada contigua en ambos ejemplos. ¿O no? – jgomo3

+2

numpy supone que sus tamaños de matriz no cambian. Se basa en gran medida en diferentes vistas de la misma porción de memoria, por lo que permitir que las matrices se expandan y reasignen requeriría una capa adicional de direccionamiento indirecto para habilitar las vistas, por ejemplo. – joeln

150

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

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

+0

¿cómo aplicaría esto a la pregunta? 'numpy.fromiter (gimme(), float, count = -1)' no funciona. ¿Qué significa 'algo'? –

+0

something.generate es solo el nombre del generador –

+1

@ Matthias009 'numpy.fromiter (gimme(), float, count = -1)' funciona para mí. – moooeeeep

5

Algo tangencial, pero si el generador es una lista por comprensión, puede utilizar numpy.where para conseguir más eficazmente su resultado (descubrí esto en mi propio código después de ver este post)

3

Si bien se puede crear una matriz 1D de un generador con numpy.fromiter(), se puede crear una matriz ND de 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 w orks para matrices 1D:

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

en cuenta que numpy.stack está consumiendo internamente el generador y la creación de una lista intermedia con arrays = [asanyarray(arr) for arr in arrays]. La implementación se puede encontrar here.

+0

Esta es una solución ordenada, gracias por señalarla. Pero parece ser bastante más lento (en mi aplicación) que usar 'np.array (tuple (mygen))'. Aquí están los resultados de la prueba: '% timeit np.stack (permutaciones (rango (10), 7)) 1 loop, mejor de 3: 1.9 s por loop' comparado con'% timeit np.array (tuple (permutaciones (rango (10), 7))) 1 loop, lo mejor de 3: 427 ms por loop' – Bill

Cuestiones relacionadas