¿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?


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).


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)


¿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í .


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)


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.