Bagaimana cara membuat larik numpy dari generator?

166

Bagaimana saya bisa membangun array numpy dari objek generator?

Biarkan saya menggambarkan masalahnya:

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

Dalam contoh ini, gimme()adalah generator yang outputnya ingin saya ubah menjadi sebuah array. Namun, konstruktor array tidak beralih di atas generator, ia hanya menyimpan generator itu sendiri. Perilaku yang saya inginkan adalah dari numpy.array(list(gimme())), tetapi saya tidak ingin membayar overhead memori memiliki daftar perantara dan array terakhir dalam memori pada saat yang sama. Apakah ada cara yang lebih efisien ruang?

saffsd
sumber
6
Ini adalah masalah yang menarik. Saya datang dengan ini from numpy import *; print any(False for i in range(1))- yang membayangi built-in any()dan menghasilkan hasil sebaliknya (seperti yang saya tahu sekarang).
moooeeeep
4
@ooooeeeeep, itu mengerikan. jika numpytidak bisa (atau tidak mau) untuk memperlakukan generator sebagai Python tidak, setidaknya harus menaikkan pengecualian ketika menerima generator sebagai argumen.
maks
1
@ Max Saya menginjak tambang yang sama persis. Rupanya ini dinaikkan pada daftar NumPy (dan sebelumnya ) menyimpulkan bahwa ini tidak akan diubah untuk meningkatkan pengecualian dan orang harus selalu menggunakan ruang nama.
alexei

Jawaban:

128

Array yang numpy membutuhkan panjangnya untuk diatur secara eksplisit pada waktu pembuatan, tidak seperti daftar python. Ini diperlukan agar ruang untuk setiap item dapat dialokasikan secara berurutan dalam memori. Alokasi berturut-turut adalah fitur utama array numpy: ini dikombinasikan dengan implementasi kode asli yang memungkinkan operasi pada mereka menjalankan jauh lebih cepat daripada daftar biasa.

Dengan mengingat hal ini, secara teknis tidak mungkin untuk mengambil objek generator dan mengubahnya menjadi sebuah array kecuali Anda:

  1. dapat memprediksi berapa banyak elemen yang akan dihasilkan saat dijalankan:

    my_array = numpy.empty(predict_length())
    for i, el in enumerate(gimme()): my_array[i] = el
    
  2. bersedia menyimpan elemen-elemennya dalam daftar perantara:

    my_array = numpy.array(list(gimme()))
  3. dapat membuat dua generator identik, jalankan melalui yang pertama untuk menemukan total panjang, menginisialisasi array, dan kemudian jalankan melalui generator lagi untuk menemukan setiap elemen:

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

1 mungkin yang Anda cari. 2 tidak efisien ruang, dan 3 tidak efisien waktu (Anda harus melewati generator dua kali).

shsmurfy
sumber
11
Builtin array.arrayadalah daftar tidak terhubung yang berdekatan, dan Anda dapat melakukannya dengan mudah array.array('f', generator). Mengatakan mengatakan itu tidak mungkin adalah menyesatkan. Itu hanya alokasi dinamis.
Cuadue
1
Mengapa numpy.array tidak melakukan alokasi memori dengan cara yang sama seperti array.inray bawaan, seperti kata Cuadue. Apa perdagangannya? Saya bertanya karena ada memori yang dialokasikan berdekatan di kedua contoh. Atau tidak?
jgomo3
3
numpy mengasumsikan ukuran array tidak berubah. Ini sangat bergantung pada pandangan yang berbeda dari potongan memori yang sama, sehingga memungkinkan array untuk diperluas dan realokasi akan memerlukan lapisan tipuan tambahan untuk memungkinkan tampilan, misalnya.
joeln
2
Menggunakan kosong sedikit lebih cepat. Karena Anda akan menginisialisasi nilai dengan cara apa pun, tidak perlu melakukan ini dua kali.
Kaushik Ghose
Lihat juga jawaban @ dhill di bawah ini yang lebih cepat dari 1.
Bill
206

Satu google di belakang hasil stackoverflow ini, saya menemukan bahwa ada numpy.fromiter(data, dtype, count). Default count=-1mengambil semua elemen dari iterable. Ini membutuhkan dtypeuntuk diatur secara eksplisit. Dalam kasus saya, ini berhasil:

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

bukit pasir
sumber
bagaimana Anda menerapkan ini pada pertanyaan? numpy.fromiter(gimme(), float, count=-1)tidak bekerja. Apa artinya something?
Matthias 009
1
@ Matthias009 numpy.fromiter(gimme(), float, count=-1)bekerja untuk saya.
moooeeeep
14
Utas yang menjelaskan mengapa fromiterhanya berfungsi pada array 1D: mail.scipy.org/pipermail/numpy-discussion/2007-August/… .
maks
2
fwiw, count=-1tidak perlu ditentukan, karena ini adalah default.
askewchan
5
Jika Anda tahu panjang iterable sebelumnya, tentukan countuntuk meningkatkan kinerja. Dengan cara ini mengalokasikan memori sebelum mengisinya dengan nilai daripada mengubah ukuran sesuai permintaan (lihat dokumentasi numpy.fromiter)
Eddy
15

Meskipun Anda bisa membuat array 1D dari generator dengan numpy.fromiter(), Anda bisa membuat array ND dari generator dengan numpy.stack:

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

Ini juga berfungsi untuk array 1D:

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

Catatan yang numpy.stackmengkonsumsi generator internal dan membuat daftar perantara dengan arrays = [asanyarray(arr) for arr in arrays]. Implementasinya dapat ditemukan di sini .

mdeff
sumber
1
Ini adalah solusi yang rapi, terima kasih telah menunjukkan. Tapi sepertinya agak lambat (di aplikasi saya) daripada menggunakan np.array(tuple(mygen)). Berikut adalah hasil tesnya: %timeit np.stack(permutations(range(10), 7)) 1 loop, best of 3: 1.9 s per loopdibandingkan dengan%timeit np.array(tuple(permutations(range(10), 7))) 1 loop, best of 3: 427 ms per loop
Bill
13
Ini tampak hebat dan bekerja untuk saya. Tetapi dengan Numpy 1.16.1 saya mendapatkan peringatan ini: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

Agak menyinggung, tetapi jika generator Anda adalah pemahaman daftar, Anda dapat menggunakan numpy.whereuntuk lebih efektif mendapatkan hasil Anda (saya menemukan ini dalam kode saya sendiri setelah melihat posting ini)

Benjamin Horstman
sumber
0

Fungsi vstack , hstack , dan dstack dapat digunakan sebagai generator input yang menghasilkan array multi-dimensi.

Mike R
sumber
3
Bisakah Anda memberi contoh jika tautannya berubah atau apa? :)
Ari Cooper-Davis
Fungsi-fungsi ini dapat mengambil generator array, bukan generator nilai
retnikt