¿Cómo construyo una matriz numpy desde un generador?

166

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

Déjame 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 matriz no itera sobre el generador, simplemente almacena el generador en sí. El comportamiento que deseo es ese 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 espacio?

saffsd
fuente
66
Este es un tema interesante. Me encontré con esto from numpy import *; print any(False for i in range(1)), que sombrea el incorporado any()y produce el resultado opuesto (como lo sé ahora).
moooeeeep
44
@moooeeeep eso es terrible. si numpyno puede (o no quiere) tratar a los generadores como lo hace Python, al menos debería generar una excepción cuando recibe un generador como argumento.
max
1
@max pisé exactamente el mismo mío. Aparentemente, esto se planteó en la lista NumPy (y anteriormente ) concluyendo que esto no se cambiará para generar una excepción y siempre se deben usar espacios de nombres.
alexei

Respuestas:

128

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 el espacio para cada elemento se pueda asignar consecutivamente en la memoria. La asignación consecutiva es la característica clave de los arreglos numpy: esto combinado con la implementación de código nativo permite que las operaciones en ellos 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:

  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 (debe pasar por el generador dos veces).

shsmurfy
fuente
11
El builtin array.arrayes una lista contigua no vinculada, y puede simplemente array.array('f', generator). Decir decir que es imposible es engañoso. Es solo una asignación dinámica.
Cuadue
1
¿Por qué numpy.array no hace la asignación de memoria de la misma manera que el array.array incorporado, como dice Cuadue? ¿Qué es el tradeof? Pregunto porque hay memoria asignada contigua en ambos ejemplos. ¿O no?
jgomo3
3
numpy asume que el tamaño de su matriz no cambia. Se basa en gran medida en diferentes vistas del mismo fragmento de memoria, por lo que permitir que las matrices se expandan y reasignen requeriría una capa adicional de indirección para habilitar las vistas, por ejemplo.
Joeln
2
Usar vacío es un poco más rápido. Dado que va a inicializar los valores de cualquier manera, no es necesario que lo haga dos veces.
Kaushik Ghose
Vea también la respuesta de @ dhill a continuación, que es más rápida que 1.
Bill
206

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

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

dhill
fuente
¿Cómo aplicarías esto a la pregunta? numpy.fromiter(gimme(), float, count=-1)No funciona. ¿Qué significa something?
Matthias 009
1
@ Matthias009 numpy.fromiter(gimme(), float, count=-1)funciona para mí.
moooeeeep
14
Un hilo que explica por qué fromitersolo funciona en matrices 1D: mail.scipy.org/pipermail/numpy-discussion/2007-August/… .
máximo
2
fwiw, count=-1no necesita ser especificado, ya que es el predeterminado.
askewchan
55
Si conoce de antemano la longitud del iterable, especifique el countpara mejorar el rendimiento. De esta manera, asigna la memoria antes de llenarla con valores en lugar de cambiar el tamaño a pedido (consulte la documentación de numpy.fromiter)
Eddy
15

Si bien puede crear una matriz 1D desde un generador con numpy.fromiter(), puede 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.stackestá consumiendo internamente el generador y creando una lista intermedia con arrays = [asanyarray(arr) for arr in arrays]. La implementación se puede encontrar aquí .

mdeff
fuente
1
Esta es una solución ordenada, gracias por señalar. Pero parece ser un poco más lento (en mi aplicación) que usarlo np.array(tuple(mygen)). Aquí están los resultados de la prueba: en %timeit np.stack(permutations(range(10), 7)) 1 loop, best of 3: 1.9 s per loopcomparación con%timeit np.array(tuple(permutations(range(10), 7))) 1 loop, best of 3: 427 ms per loop
Bill
13
Esto parece genial y funciona para mí. Pero con Numpy 1.16.1 me sale esta advertencia: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

Algo tangencial, pero si su generador es una comprensión de la lista, puede usarlo numpy.wherepara obtener su resultado de manera más efectiva (descubrí esto en mi propio código después de ver esta publicación)

Benjamin Horstman
fuente
0

Las funciones vstack , hstack y dstack pueden tomarse como generadores de entrada que producen matrices multidimensionales.

Mike R
fuente
3
¿Puedes dar un ejemplo en caso de que los enlaces cambien o algo así? :)
Ari Cooper-Davis
Estas funciones pueden tomar un generador de matrices, no un generador de valores
retnikt