- Yazar, NumPy hakkındaki memnuniyetsizliğini konu alarak çeşitli örneklerle sorunları açıklıyor
- Basit dizi işlemleri NumPy ile kolay olsa da, boyutlar arttıkça karmaşıklık ve kafa karışıklığı hızla büyüyor
- Broadcasting ve gelişmiş indeksleme gibi NumPy tasarımı, açıklık ve soyutlama açısından yetersiz kalıyor
- Eksenleri açıkça belirtmek yerine tahmin ve deneme-yanılmaya dayalı kod yazmak neredeyse zorunlu hale geliyor
- Yazar, geliştirilmiş bir dizi dili için fikirler sunuyor ve somut alternatifleri bir sonraki yazıda tanıtmayı planlıyor
Giriş: NumPy'ye duyulan sevgi-nefret
- Yazar, NumPy'yi uzun zamandır kullandığını ancak sınırları nedeniyle büyük hayal kırıklığı yaşadığını belirtiyor
- NumPy, Python'da dizi işlemleri için vazgeçilmez ve etkili bir kütüphane
- PyTorch gibi modern makine öğrenimi kütüphanelerinde de NumPy'ye benzer sorunlar bulunuyor
NumPy'nin kolay ve zor yanları
- Temel doğrusal denklem çözümü gibi basit işlemler, açık ve zarif bir sözdizimiyle yapılabiliyor
- Ancak dizi boyutu büyüdüğünde veya işlemler karmaşıklaştığında, for döngüsü olmadan toplu işleme ihtiyaç duyuluyor
- Döngü kullanılamayan ortamlarda (GPU işlemleri gibi), alışılmadık vektörleştirme sözdizimi veya özel fonksiyon çağrı biçimleri gerekiyor
- Ne var ki bu fonksiyonların doğru kullanım biçimi belirsiz ve yalnızca dokümantasyondan net biçimde anlaşılması zor
- Gerçekte NumPy'nin
linalg.solve fonksiyonunun yüksek boyutlu dizilerde nasıl doğru kullanılacağını kimsenin kesin biçimde bildiğini söylemek zor
NumPy'nin sorunları
- NumPy, çok boyutlu dizilerin bir kısmına veya belirli eksenlerine işlem uygulamak için tutarlı bir kuram sunmuyor
- Dizi boyutu 2 veya altındayken durum açıkken, 3 boyut ve sonrasında her dizide hangi eksenlerin işleneceği belirsizleşiyor
- Boyutları açıkça eşlemek için None kullanımı, broadcasting,
np.tensordot gibi karmaşık yöntemleri zorunlu kılıyor
- Bu yaklaşım hata yapmayı kolaylaştırıyor, kod okunabilirliğini düşürüyor ve bug olasılığını artırıyor
Döngüler ve açıklık
- Aslında döngülere izin verilse, çok daha kısa ve açık kod yazmak mümkün olabiliyor
- Döngülü kod daha az şık görünebilir, ancak açıklık açısından büyük avantaj sağlıyor
- Buna karşılık, dizi boyutu değiştiğinde transpose veya eksen sırasını tek tek düşünmek gerekiyor ve karmaşıklık artıyor
np.einsum: İstisnai derecede iyi bir fonksiyon
- np.einsum, eksen adlarını belirtmeye izin veren esnek bir alan-özel dil sunduğu için güçlü
- einsum, işlemin niyetini açık hale getiriyor ve iyi genellenerek karmaşık eksen işlemlerini açıkça ifade etmeyi sağlıyor
- Ancak einsum benzeri yaklaşımın desteklediği işlemler yalnızca bazı işlemlerle sınırlı; örneğin
linalg.solve için kullanılamıyor
Broadcasting'in sorunları
- NumPy'nin temel hilelerinden biri olan broadcasting, boyutlar uyuşmadığında bunları otomatik olarak eşleştiren bir özellik
- Basit durumlarda kullanışlı olsa da, gerçekte boyutları net biçimde anlamayı zorlaştırıyor ve hata örnekleri çok fazla
- Broadcasting örtük olduğu için kodu okurken işlemin her seferinde nasıl çalıştığını yeniden kontrol etmek gerekiyor
İndekslemenin belirsizliği
- NumPy'nin gelişmiş indekslemesi, dizi shape'ini öngörmeyi son derece zor ve belirsiz hale getiriyor
- Farklı indeksleme kombinasyonlarına göre sonuç dizisinin shape'i değiştiğinden, pratik deneyim olmadan bunu tahmin etmek güç
- İndeksleme kurallarını anlatan dokümanlar da uzun ve karmaşık olduğu için öğrenme maliyeti yüksek
- Sadece basit indeksleme kullanmak isteseniz bile, bazı işlemlerde kaçınılmaz olarak gelişmiş indeksleme kullanmanız gerekiyor
NumPy fonksiyon tasarımının sınırları
- Birçok NumPy fonksiyonu yalnızca belirli dizi shape'leri için optimize edilmiş durumda
- Yüksek boyutlu diziler için ek axes argümanı, ayrı fonksiyon adları veya farklı teamüller kullanmak gerekiyor ve bu yapı fonksiyondan fonksiyona tutarlı değil
- Bu, soyutlama ve yeniden kullanımın temel olduğu programlama ilkelerine ters düşüyor
- Belirli bir sorunu çözen bir fonksiyon kullansanız bile, bunu farklı dizilere ve eksenlere yeniden uygulamak istediğinizde kodu baştan farklı şekilde yazmanız gerekebiliyor
Gerçek örnek: self-attention uygulaması
- NumPy ile self-attention yazarken, döngü kullanımı daha açık olsa da vektörleştirme zorunlu tutulduğunda kod karmaşıklaşıyor
- Multi-head attention gibi yüksek boyutlu işlemler gerektiğinde, einsum ile eksen dönüşümlerini birlikte kullanmak gerekiyor ve kod anlaşılmaz hale gelebiliyor
Sonuç ve alternatif
- Yazar, NumPy'nin "diğer dizi dillerine kıyasla daha çok kötü yanı olsa da, piyasada bu kadar önemli hale gelmiş tek seçenek" olduğunu söylüyor
- NumPy'nin çeşitli sorunlarını (broadcasting, indeksleme belirsizliği, fonksiyonlardaki tutarsızlık vb.) aşmak için geliştirilmiş bir dizi dili prototipi geliştirdiğini duyuruyor
- Somut iyileştirme önerilerini (yeni dizi dili API'si) daha sonra ayrı bir yazıda tanıtmayı planlıyor
4 yorum
Bu biraz Julia'nın neden ortaya çıktığıyla ilgili bir hikâye gibi görünüyor. Kütüphaneleri öğrenmek gerekiyor ama NumPy'nin birçok sorununu çözmesi açısından gerçekten çok cazip bir seçenek gibi duruyor.
numpy'de vectorization'ı iyi kullanamazsanız performans mahvoluyor. Bunu hesaba katarak yazmak da stresli ve zor.Biraz eski Python kütüphanelerinin hepsinde benzer sorunlar var gibi görünüyor.
Hacker News görüşleri
b'nin tipine bakıp dokümantasyona başvurunca okumak zor, ancak dönenshapeile ilgili açıklama olduğundanbvektörünün gerçekten matris biçiminde olup olmadığını, özellikle deK=1durumunda, kontrol etmek gerekiyortransposeişlemleri olmadan broadcasting/hizalama otomatik olduğu için bu tür sorunların çoğu çözülüyor. Xarray lineer cebir açısından NumPy'den daha zayıf ama kolayca NumPy'ye geri dönülebiliyor ve yalnızca yardımcı fonksiyonlar yazmak yeterli oluyor. Xarray kullanınca 3 boyut ve üzerindeki verilerle çalışırken üretkenlik ciddi biçimde artıyorda.sel(x=some_x).isel(t=-1).mean(["y", "z"])gibi indeksleme kolay, boyut adlarına saygı duyulduğu için broadcasting de net oluyor. Birden fazla CRS içeren coğrafi verileri işlemede güçlü, Arviz ile birlikte de çok iyi çalıştığından Bayesyen analizde ek boyutları yönetmek kolay. Birden fazla diziyi tek bir dataset içinde toplayıp ortak koordinatları paylaşmak da mümkün; böyleceds.isel(t=-1)gibi bir işlem zaman ekseni olan tüm dizilere kolayca uygulanabiliyorarray[:, :, None]gibi sözdiziminden rahatsız olanın yalnızca kendisi olmadığına sevinilmişnp.linalg.solvegibi şeylerin en hızlı yöntem olduğu varsayılarak herkesten buna uymasını bekleyen “kara kutu” yaklaşımının doğru olmadığı düşünülüyor; probleme özel kernel'ları doğrudan yazmanın daha iyi olduğu pek çok durum da var\operatörü gibi tamamen optimize edilmiş kara kutulartranspose/reshapegibi tip uydurma işlemleri yapmak gerekiyor; bu da tutarsız ve muğlak hissettiriyorjaxiçindekivmapdenenebilirslicevesqueezegibi işlemlerle çözülebileceği, hatta sorunun kendisinin anlaşılması zor derecede muğlak olduğu söyleniyorreshapeile de çözülebilirpoly1dnesnesiP, sağdanz0ile çarpılırsa yinepoly1ddönerken soldanz0*Pbiçiminde çarpıldığında yalnızca dizi dönüyor ve tip dönüşümü sessizce gerçekleşiyor. Bir ikinci dereceden polinomun leading coefficient değeri için hemP.coef[0]hem deP[2]kullanılabilmesi de kolayca kafa karıştırıyor. Resmî olarakpoly1d“eski” bir API ve yeni kod içinPolynomialsınıfı öneriliyor ama ortada fiilen bir deprecated uyarısı da yok. Bu tür tip dönüşümleri ve veri tipi uyumsuzlukları gibi mayınlar kütüphanenin her yerine dağılmış durumda ve debug sürecini kâbusa çeviriyorto_numpy()düzeyi. Sonuçta asıl problemi çözmeye harcanandan daha fazla zaman veri formatı dönüştürmeye gidiyor. Julia'nın da yalnızca artıları yok ama birimler, belirsizlikler vb. alanlarda farklı kütüphaneler arası entegrasyon iyi çalışıyor; Python'da ise sürekli bol miktarda boilerplate kod gerekiyorarray-apiprojesi, Python ekosisteminin genelinde dizi işleme API'sini standartlaştırmaya çalışıyorsageyerinenumpykullandığı merak ediliyornumpysanevegnuplotlibile çözülebildiği söyleniyor. Bu ikili ortaya çıktıktan sonra NumPy her işte aktif biçimde kullanılmaya başlanmış; bunlar olmasa katlanılamayacak düzeyde olacağı düşünülüyornumpysanesonuçta Python döngüsü; gerçek vektörleştirme değileinsumiçine alınmış veoptimize="optimal"ile matrix chain çarpım algoritması kullanılarak performans artırılmaya çalışılmış. Gerçekten de genel vektörleştirilmiş uygulamaya göre yaklaşık 2 kat hızlanma elde edilmiş, ancak şaşırtıcı biçimde döngü kullanan saf uygulama daha da hızlı çıkmış. Nedenini merak edenlerin koda bakması öneriliyor; tahmin olarakeinsumiçindeki cache coherency tarafında hâlâ iyileştirme payı olduğu düşünülüyor