- Penulis menjelaskan berbagai masalah pada NumPy beserta beberapa contohnya
- Operasi array sederhana memang mudah dengan NumPy, tetapi ketika jumlah dimensinya bertambah, kompleksitas dan kebingungan meningkat drastis
- Desain NumPy seperti broadcasting dan advanced indexing kurang memadai dari sisi kejelasan dan abstraksi
- Menulis kode yang bergantung pada tebakan dan trial-and-error menjadi keharusan, alih-alih menentukan axis secara eksplisit
- Penulis mengajukan ide untuk bahasa array yang lebih baik dan berencana memperkenalkan alternatif konkretnya di tulisan berikutnya
Pendahuluan: hubungan cinta-benci dengan NumPy
- Penulis menyatakan telah lama menggunakan NumPy, tetapi juga sangat kecewa dengan berbagai keterbatasannya
- NumPy adalah library penting dan berpengaruh untuk operasi array di Python
- Masalah serupa dengan NumPy juga ada di library machine learning modern seperti PyTorch
Hal yang mudah dan sulit di NumPy
- Operasi sederhana seperti menyelesaikan persamaan linear dasar dapat dilakukan dengan sintaks yang jelas dan elegan
- Namun, ketika dimensi array bertambah atau operasinya menjadi lebih kompleks, pemrosesan batch tanpa for loop pun diperlukan
- Di lingkungan yang tidak memungkinkan penggunaan loop langsung, seperti komputasi GPU, dibutuhkan sintaks vektorisasi yang aneh atau cara pemanggilan fungsi yang khusus
- Namun, cara penggunaan yang tepat dari fungsi-fungsi ini sering kali ambigu dan sulit dipahami dengan jelas hanya dari dokumentasinya
- Dalam praktiknya, untuk fungsi
linalg.solve milik NumPy, sulit bagi siapa pun untuk benar-benar yakin bagaimana cara menggunakannya dengan benar pada array berdimensi tinggi
Masalah pada NumPy
- NumPy tidak memiliki teori yang konsisten untuk menerapkan operasi pada sebagian atau axis tertentu dari array multidimensi
- Ketika dimensi array 2 atau kurang, semuanya jelas, tetapi pada 3 dimensi atau lebih, tidak jelas axis mana yang menjadi target operasi pada tiap array
- Pengguna dipaksa memakai cara-cara rumit seperti None, broadcasting, atau
np.tensordot untuk menyesuaikan dimensi secara eksplisit
- Pendekatan seperti ini memicu kesalahan, menurunkan keterbacaan kode, dan meningkatkan kemungkinan bug
Perulangan dan kejelasan
- Jika perulangan benar-benar diizinkan, penulisan kode yang jauh lebih ringkas dan jelas justru menjadi mungkin
- Kode berbasis perulangan mungkin terlihat kurang anggun, tetapi memiliki keunggulan besar dari sisi kejelasan
- Sebaliknya, ketika dimensi array berubah, kita harus memikirkan transpose atau urutan axis satu per satu, sehingga kompleksitas bertambah
np.einsum: fungsi yang bagus sebagai pengecualian
- np.einsum sangat kuat karena menyediakan bahasa khusus domain yang fleksibel untuk memberi nama pada axis
- Einsum membuat maksud operasi menjadi jelas dan juga sangat mudah digeneralisasi, sehingga operasi axis yang kompleks dapat diimplementasikan secara eksplisit
- Namun, dukungan operasi dengan gaya seperti einsum hanya tersedia untuk sebagian operasi, dan misalnya tidak bisa dipakai untuk
linalg.solve
Masalah pada broadcasting
- Broadcasting, trik inti NumPy, adalah fitur yang secara otomatis menyesuaikan dimensi ketika tidak cocok
- Pada kasus sederhana memang nyaman, tetapi dalam praktiknya justru menyulitkan pemahaman dimensi secara jelas dan sering menimbulkan kesalahan
- Karena broadcasting bersifat implisit, saat membaca kode kita harus terus memeriksa bagaimana operasi itu sebenarnya bekerja
Ketidakjelasan indexing
- Advanced indexing di NumPy membuat prediksi shape array menjadi sangat sulit dan tidak jelas
- Shape array hasil dapat berubah tergantung kombinasi indexing yang digunakan, sehingga sulit diprediksi tanpa pengalaman langsung menanganinya
- Dokumentasi yang menjelaskan aturan indexing juga panjang dan kompleks, sehingga memakan banyak waktu untuk dipelajari
- Bahkan jika ingin memakai indexing sederhana saja, pada operasi tertentu kita tetap terpaksa menggunakan advanced indexing
Keterbatasan desain fungsi NumPy
- Banyak fungsi NumPy dioptimalkan hanya untuk shape array tertentu
- Untuk array berdimensi tinggi, kita harus memakai argumen axes, nama fungsi terpisah, atau konvensi khusus tambahan, dan semuanya tidak konsisten antar fungsi
- Struktur ini bertentangan dengan prinsip pemrograman yang menjadikan abstraksi dan penggunaan ulang sebagai dasar
- Bahkan setelah memakai fungsi untuk menyelesaikan masalah tertentu, jika ingin menerapkannya lagi ke berbagai array dan axis, kita sering harus menulis ulang kodenya dengan cara yang sama sekali berbeda
Contoh nyata: implementasi self-attention
- Saat menulis implementasi self-attention dengan NumPy, penggunaan perulangan membuatnya jelas, tetapi pemaksaan vektorisasi justru membuat kode menjadi rumit
- Ketika dibutuhkan operasi berdimensi tinggi seperti multi-head attention, kita harus menggabungkan einsum dan transformasi axis, sehingga kodenya menjadi sulit dipahami
Kesimpulan dan alternatif
- Penulis menyatakan bahwa NumPy adalah "satu-satunya pilihan yang menjadi sepenting ini di pasar, meski memiliki lebih banyak kelemahan dibanding bahasa array lain"
- Untuk mengatasi berbagai masalah NumPy seperti broadcasting, ketidakjelasan indexing, dan inkonsistensi fungsi, penulis mengisyaratkan bahwa ia telah membuat prototipe bahasa array yang ditingkatkan
- Usulan perbaikan yang konkret, yaitu API bahasa array baru, rencananya akan diperkenalkan dalam tulisan terpisah di masa mendatang
4 komentar
Ini terasa seperti cerita tentang mengapa Julia lahir. Meski perlu mempelajari berbagai library, ini tampaknya benar-benar pilihan yang menarik karena mampu menyelesaikan banyak masalah di NumPy.
Kalau tidak bisa memanfaatkan vectorization di NumPy dengan baik, performanya jadi berantakan. Menulis dengan mempertimbangkan hal-hal seperti itu bikin stres dan sulit.
Sepertinya banyak library Python yang sudah agak lama punya masalah yang mirip.
Komentar Hacker News
blalu membaca dokumentasinya, ini sulit dipahami, tetapi karena ada penjelasan tentangshapeyang dikembalikan, perlu dicek apakah vektorbsebenarnya berbentuk matriks, terutama saatK=1transpose, Xarray memang lebih lemah daripada NumPy dari sisi aljabar linear tetapi mudah kembali ke NumPy dan cukup membuat fungsi pembantu, dengan Xarray produktivitas sangat meningkat saat menangani data 3 dimensi atau lebihda.sel(x=some_x).isel(t=-1).mean(["y", "z"])mudah dilakukan, broadcasting juga jelas karena nama dimensi dihormati, dan kuat untuk memproses data geospasial dengan beberapa CRS, pemakaiannya bersama Arviz juga sangat baik sehingga penanganan dimensi tambahan dalam analisis Bayesian menjadi mudah, beberapa array juga bisa diikat dalam satu dataset dan berbagi koordinat yang sama sehingga bisa dengan mudah diterapkan ke semua array yang memiliki sumbu waktu sepertids.isel(t=-1)array[:, :, None]tidak nyaman, senang ada yang sependapatnp.linalg.solvekarena dianggap paling cepat itu benar, ada banyak alasan mengapa menulis kernel khusus masalah secara langsung bisa lebih baik'\\'transpose/reshapejuga tidak konsisten sehingga terasa samarjaxvmaplayak dicobasqueeze, masalah ini sendiri terasa terlalu samar sampai sulit dipahamireshapepoly1dP, jika dikalikan dari kanan denganz0hasilnyapoly1d, tetapi jika dikalikan dari kiri dalam bentukz0*Pyang dikembalikan hanya array sehingga konversi tipe terjadi diam-diam, koefisien terdepan dari quadratic juga bisa diakses dengan dua caraP.coef[0]danP[2]sehingga mudah membingungkan, secara resmipoly1dadalah API “lama” dan kode baru disarankan memakai kelasPolynomial, tetapi nyatanya bahkan tidak ada peringatan deprecated, seperti konversi tipe dan ketidakcocokan datatype ini, ada ranjau di seluruh pustaka sehingga debugging jadi mimpi buruknumarray, jadi bukan hanya berhenti di 2Dto_numpy(), akibatnya waktu yang dihabiskan untuk konversi format data lebih banyak daripada waktu untuk menyelesaikan masalah, Julia juga bukan tanpa kekurangan tetapi integrasi antarpustaka untuk hal-hal seperti satuan dan ketidakpastian berjalan baik, sedangkan di Python selalu perlu banyak boilerplate codearray-apisedang berupaya menstandarkan API manipulasi array di seluruh ekosistem Pythonnumpysanedangnuplotlib, setelah kombinasi ini ada saya aktif memakai numpy untuk semua pekerjaan, tanpa itu rasanya benar-benar tidak bisa dipakainumpysanepada akhirnya tetap loop Python, jadi bukan vektorisasi yang sesungguhnyaeinsumdan memakai algoritma perkalian rantai matriks denganoptimize="optimal"untuk meningkatkan performa, memang menjadi sekitar 2x lebih cepat dibanding implementasi vektorisasi umum, tetapi mengejutkannya implementasi naif berbasis loop justru lebih cepat, bagi yang penasaran alasannya silakan lihat kodenya, saya menduga masih ada ruang untuk perbaikan pada cache coherency di dalameinsum