Jak zbudować tablicę numpy z generatora?

166

Jak mogę zbudować tablicę numpy z obiektu generatora?

Pozwólcie, że zilustruję problem:

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

W tym przypadku gimme()jest to generator, którego wyjście chciałbym zamienić na tablicę. Jednak konstruktor tablicy nie wykonuje iteracji po generatorze, po prostu przechowuje sam generator. Zachowanie, którego pragnę, jest takie numpy.array(list(gimme())), ale nie chcę płacić narzutu pamięci związanego z posiadaniem listy pośredniej i ostatniej tablicy w pamięci w tym samym czasie. Czy istnieje sposób bardziej efektywny przestrzennie?

saffsd
źródło
6
To ciekawa kwestia. Przeszedłem przez to from numpy import *; print any(False for i in range(1))- co zacienia wbudowane any()i daje odwrotny skutek (jak już wiem).
moooeeeep
4
@moooeeeep to straszne. jeśli numpynie może (lub nie chce) traktować generatorów tak, jak robi to Python, powinien przynajmniej zgłosić wyjątek, gdy otrzyma generator jako argument.
maksymalnie
1
@max Wszedłem na dokładnie to samo moje. Najwyraźniej zostało to podniesione na liście NumPy (i wcześniej ), stwierdzając, że nie zostanie to zmienione, aby zgłosić wyjątek i zawsze należy używać przestrzeni nazw.
alexei

Odpowiedzi:

128

Tablice Numpy wymagają jawnego ustawienia ich długości podczas tworzenia, w przeciwieństwie do list Pythona. Jest to konieczne, aby miejsce dla każdego elementu mogło być kolejno przydzielane w pamięci. Kolejna alokacja jest kluczową cechą tablic numpy: w połączeniu z implementacją kodu natywnego operacje na nich są wykonywane znacznie szybciej niż zwykłe listy.

Mając to na uwadze, technicznie niemożliwe jest pobranie obiektu generatora i przekształcenie go w tablicę, chyba że:

  1. potrafi przewidzieć, ile elementów da po uruchomieniu:

    my_array = numpy.empty(predict_length())
    for i, el in enumerate(gimme()): my_array[i] = el
  2. chcą przechowywać swoje elementy na liście pośredniej:

    my_array = numpy.array(list(gimme()))
  3. może utworzyć dwa identyczne generatory, przejść przez pierwszy, aby znaleźć całkowitą długość, zainicjować tablicę, a następnie ponownie uruchomić generator, aby znaleźć każdy element:

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

1 jest prawdopodobnie tym, czego szukasz. 2 to nieefektywne miejsce, a 3 to nieefektywne czasowo (musisz dwukrotnie przejść przez generator).

shsmurfy
źródło
11
Wbudowana array.arraylista jest ciągłą, niepołączoną listą i możesz po prostu array.array('f', generator). Powiedzieć, że to niemożliwe, jest mylące. To tylko alokacja dynamiczna.
Cuadue,
1
Dlaczego numpy.array nie alokuje pamięci w taki sam sposób, jak wbudowana tablica.array, jak mówi Cuadue. Jaka jest wymiana? Pytam, ponieważ w obu przykładach jest ciągła przydzielona pamięć. Albo nie?
jgomo3
3
numpy zakłada, że ​​rozmiary tablic nie ulegną zmianie. Opiera się w dużej mierze na różnych widokach tego samego fragmentu pamięci, więc zezwolenie na rozszerzanie i ponowne przydzielanie tablic wymagałoby na przykład dodatkowej warstwy pośredniej, aby umożliwić widoki.
joeln
2
Używanie pustego jest nieco szybsze. Ponieważ zamierzasz w jakikolwiek sposób zainicjalizować wartości, nie musisz robić tego dwa razy.
Kaushik Ghose
Zobacz także odpowiedź @ dhill poniżej, która jest szybsza niż 1.
Bill
206

Jedno z Google za tym wynikiem przepełnienia stosu okazało się, że istnieje plik numpy.fromiter(data, dtype, count). Domyślnie count=-1pobiera wszystkie elementy z iterowalnego. Wymaga dtypejawnego ustawienia. W moim przypadku zadziałało:

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

dhill
źródło
jak zastosowałbyś to do pytania? numpy.fromiter(gimme(), float, count=-1)nie działa. Co to znaczy something?
Matthias 009,
1
@ Matthias009 numpy.fromiter(gimme(), float, count=-1)działa dla mnie.
moooeeeep,
14
Wątek wyjaśniający, dlaczego fromiterdziała tylko na tablicach 1D: mail.scipy.org/pipermail/numpy-discussion/2007-August/… .
max
2
fwiw, count=-1nie trzeba określać, ponieważ jest to ustawienie domyślne.
askewchan
5
Jeśli znasz wcześniej długość iterowalnej, określ, countaby poprawić wydajność. W ten sposób alokuje pamięć przed wypełnieniem jej wartościami zamiast zmiany rozmiaru na żądanie (patrz dokumentacja numpy.fromiter)
Eddy
15

Chociaż możesz utworzyć tablicę 1D z generatora za pomocą numpy.fromiter(), możesz utworzyć tablicę ND z generatora za pomocą numpy.stack:

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

Działa również w przypadku tablic 1D:

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

Zauważ, że numpy.stackwewnętrznie zużywa generator i tworzy listę pośrednią z arrays = [asanyarray(arr) for arr in arrays]. Implementację można znaleźć tutaj .

mdeff
źródło
1
To fajne rozwiązanie, dziękuję za wskazanie. Ale wydaje się, że jest trochę wolniejszy (w mojej aplikacji) niż używanie np.array(tuple(mygen)). Oto wyniki testu: w %timeit np.stack(permutations(range(10), 7)) 1 loop, best of 3: 1.9 s per loopporównaniu do%timeit np.array(tuple(permutations(range(10), 7))) 1 loop, best of 3: 427 ms per loop
Bill
13
Wydaje się to świetne i działa dla mnie. Ale z Numpy 1.16.1 otrzymuję ostrzeżenie: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

Nieco styczne, ale jeśli twój generator jest listą, możesz użyć, numpy.whereaby skuteczniej uzyskać wynik (odkryłem to we własnym kodzie po obejrzeniu tego postu)

Benjamin Horstman
źródło
0

Funkcje vstack , hstack i dstack mogą służyć jako generatory danych wejściowych, które generują wielowymiarowe tablice.

Mike R.
źródło
3
Czy możesz podać przykład na wypadek zmiany linków czy coś? :)
Ari Cooper-Davis
Te funkcje mogą pobierać generator tablic, a nie generator wartości
retnikt