- Der Autor erläutert seine Kritik an NumPy anhand mehrerer Beispiele.
- Einfache Array-Operationen sind mit NumPy leicht, aber mit steigender Dimensionalität nehmen Komplexität und Verwirrung rapide zu.
- Broadcasting und Advanced Indexing zeigen, dass NumPys Design bei Klarheit und Abstraktion Schwächen hat.
- Statt Achsen explizit anzugeben, muss man beim Schreiben von Code auf Vermutungen und Trial-and-Error setzen.
- Er skizziert Ideen für eine verbesserte Arraysprache und will im nächsten Beitrag konkrete Alternativen vorstellen.
Einleitung: Hassliebe zu NumPy
- Der Autor erklärt, dass er NumPy lange verwendet hat, aber von seinen Grenzen vielfach enttäuscht wurde.
- NumPy ist eine unverzichtbare und einflussreiche Bibliothek für Array-Operationen in Python.
- Auch moderne Machine-Learning-Bibliotheken wie PyTorch haben ähnliche Probleme wie NumPy.
Was an NumPy leicht ist – und was nicht
- Einfache Operationen wie das Lösen grundlegender linearer Gleichungen sind mit klarer und eleganter Syntax möglich.
- Sobald Arrays jedoch höherdimensional werden oder Operationen komplexer sind, braucht man Batch-Verarbeitung ohne for-Schleifen.
- In Umgebungen, in denen Schleifen nicht möglich sind, etwa bei GPU-Berechnungen, sind ungewöhnliche Vektorisierungssyntax oder spezielle Funktionsaufrufe nötig.
- Die korrekte Verwendung dieser Funktionen ist jedoch oft unklar und selbst mit der Dokumentation schwer eindeutig zu verstehen.
- Bei der Funktion
linalg.solve von NumPy ist es in der Praxis schwer, bei höherdimensionalen Arrays sicher zu wissen, wie sie korrekt eingesetzt werden muss.
Die Probleme von NumPy
- NumPy fehlt eine konsistente Theorie dafür, wie Operationen auf Teile oder bestimmte Achsen mehrdimensionaler Arrays angewendet werden.
- Bei Arrays mit bis zu zwei Dimensionen ist vieles klar, ab drei oder mehr Dimensionen ist jedoch bei jedem Array unklar, auf welche Achsen sich eine Operation bezieht.
- Um Dimensionen explizit anzugleichen, wird man zu komplizierten Verfahren wie None, Broadcasting oder
np.tensordot gezwungen.
- Das führt leicht zu Fehlern, verschlechtert die Lesbarkeit des Codes und erhöht die Wahrscheinlichkeit von Bugs.
Schleifen und Klarheit
- Wenn Schleifen tatsächlich erlaubt sind, lässt sich oft kürzerer und klarerer Code schreiben.
- Schleifencode mag weniger elegant wirken, hat aber beim Aspekt der Verständlichkeit große Vorteile.
- Wenn sich dagegen die Array-Dimensionen ändern, muss man sich einzeln mit transpose oder der Reihenfolge der Achsen befassen, was die Komplexität erhöht.
np.einsum: eine ausnahmsweise gute Funktion
np.einsum ist leistungsstark, weil es eine flexible domänenspezifische Sprache bietet, in der sich Achsen benennen lassen.
- Mit einsum ist die Absicht einer Operation klar, und die Generalisierung funktioniert hervorragend, sodass komplexe Achsenoperationen explizit implementiert werden können.
- Allerdings ist diese Art von Operationsunterstützung nur auf einige Operationen beschränkt; für
linalg.solve lässt sie sich zum Beispiel nicht verwenden.
Die Probleme mit Broadcasting
- Broadcasting, der zentrale Trick von NumPy, passt Dimensionen automatisch an, wenn sie nicht übereinstimmen.
- In einfachen Fällen ist das praktisch, in der Praxis macht es aber oft schwer, die Dimensionen klar zu erkennen, und führt zu vielen Fehlersituationen.
- Weil Broadcasting implizit ist, muss man beim Lesen des Codes jedes Mal erneut prüfen, wie die Operation tatsächlich funktioniert.
Die Unklarheit beim Indexing
- NumPys Advanced Indexing macht die Vorhersage der Array-Shape sehr schwierig und unklar.
- Je nach Kombination der Indexing-Varianten ändert sich die Shape des Ergebnis-Arrays, sodass Vorhersagen ohne praktische Erfahrung schwerfallen.
- Auch die Dokumentation der Indexing-Regeln ist lang und komplex und kostet viel Zeit beim Erlernen.
- Selbst wenn man nur einfaches Indexing verwenden möchte, zwingen manche Operationen einen letztlich doch zu Advanced Indexing.
Grenzen im Design der NumPy-Funktionen
- Viele NumPy-Funktionen sind nur für bestimmte Array-Shapes optimiert.
- Für höherdimensionale Arrays braucht man zusätzliche axes-Argumente, separate Funktionsnamen oder Konventionen, und das ist von Funktion zu Funktion inkonsistent.
- Diese Struktur läuft den Grundprinzipien der Programmierung entgegen, bei denen Abstraktion und Wiederverwendung zentral sind.
- Selbst wenn man eine Funktion für ein bestimmtes Problem gefunden hat, muss man sie für verschiedene Arrays und Achsen oft mit völlig anderem Code neu schreiben.
Praxisbeispiel: Implementierung von Self-Attention
- Wenn man Self-Attention in NumPy implementiert, ist Code mit Schleifen klarer, während er bei erzwungener Vektorisierung schnell komplex wird.
- Für höherdimensionale Operationen wie Multi-Head-Attention muss man einsum und Achsentransformationen kombinieren, wodurch der Code schwer verständlich wird.
Fazit und Alternativen
- Der Autor erklärt, NumPy sei „in vieler Hinsicht schlechter als andere Array-Sprachen, aber zugleich die einzige Option, die am Markt entsprechend wichtig geworden ist“.
- Um NumPys verschiedene Probleme wie Broadcasting, Unklarheit beim Indexing und Inkonsistenz der Funktionen zu überwinden, kündigt er an, einen Prototyp einer verbesserten Arraysprache gebaut zu haben.
- Konkrete Verbesserungsvorschläge, also eine neue Arraysprachen-API, will er in einem späteren separaten Beitrag vorstellen.
4 Kommentare
Das klingt so, als würde es erzählen, warum Julia entstanden ist. Man muss zwar die Bibliotheken erst lernen, aber weil es viele Probleme von NumPy löst, wirkt es wie eine wirklich attraktive Option.
Wenn man
vectorizationin NumPy nicht richtig nutzt, ist die Performance dahin. Das unter Berücksichtigung solcher Dinge zu schreiben, ist stressig und schwierig.Ich glaube, viele etwas ältere Python-Bibliotheken haben alle ein ähnliches Problem.
Hacker-News-Kommentare
bschaut. Da aber die zurückgegebeneshapebeschrieben ist, muss man prüfen, ob der Vektorbtatsächlich als Matrixform behandelt wird, insbesondere wennK=1ist.transposeaufrufen muss, wodurch sich die meisten dieser Probleme erledigen. Xarray ist bei linearer Algebra schwächer als NumPy, aber man kann leicht zu NumPy zurückwechseln und braucht dafür nur ein paar Hilfsfunktionen. Mit Xarray steigt die Produktivität beim Arbeiten mit Daten ab drei Dimensionen deutlich.da.sel(x=some_x).isel(t=-1).mean(["y", "z"])ist einfach, und weil Dimensionsnamen berücksichtigt werden, ist auch Broadcasting klarer. Es ist stark bei der Verarbeitung georäumlicher Daten mit mehreren CRSs. Auch mit Arviz lässt es sich hervorragend nutzen, sodass zusätzliche Dimensionen in der Bayes-Analyse leichter handhabbar sind. Mehrere Arrays lassen sich zu einem Dataset bündeln und gemeinsame Koordinaten teilen, sodass man mit etwas wieds.isel(t=-1)leicht auf alle Arrays mit Zeitachse gleichzeitig arbeiten kann.array[:, :, None]unbequem, daher freut es mich, dieselbe Meinung zu lesen.np.linalg.solveals maximal schnell gelten und man sie deshalb immer verwenden soll. Es gibt viele Gründe, problemspezifische Kernel selbst zu schreiben.\-Operator.transpose,reshapeusw. nicht konsistent ist und dadurch unerquicklich wird.jaxmitvmapausprobieren.squeezeusw. Dieses Problem ist so vage, dass ich es kaum nachvollziehen kann.reshapelösen.poly1d-ObjektPvon rechts mitz0multipliziert, erhält man wieder einpoly1d; multipliziert man aber von links in der Formz0*P, wird nur ein Array zurückgegeben, sodass stillschweigend eine Typumwandlung stattfindet. Selbst beim führenden Koeffizienten eines quadratischen Polynoms gibt es zwei Schreibweisen,P.coef[0]undP[2], was leicht verwirrend wird. Offiziell istpoly1deine „alte“ API und für neuen Code wird die KlassePolynomialempfohlen, aber tatsächlich gibt es nicht einmal eine Deprecated-Warnung. Solche Typumwandlungen und Inkonsistenzen bei Datentypen sind überall in der Bibliothek versteckte Minenfelder und machen das Debugging zum Albtraum.to_numpy()ist nur so halbwegs vereinheitlicht. Am Ende verbringt man mehr Zeit mit der Umwandlung von Datenformaten als mit dem eigentlichen Lösen des Problems. Auch Julia hat nicht nur Vorteile, aber die Interoperabilität zwischen Bibliotheken für Einheiten, Unsicherheiten usw. funktioniert dort gut, während Python immer viel Boilerplate-Code verlangt.array-apiversucht, die API für Array-Manipulationen im gesamten Python-Ökosystem zu standardisieren.numpysaneist letztlich nur eine Python-Schleife, also keine echte Vektorisierung.einsumgepackt und mitoptimize="optimal"den Matrix-Chain-Multiplikationsalgorithmus genutzt, um die Performance zu verbessern. Tatsächlich war das gegenüber einer gewöhnlichen vektorisierten Implementierung etwa doppelt so schnell, aber überraschenderweise war eine naive Implementierung mit Schleifen noch schneller. Wer wissen will, warum, sollte sich den Code ansehen. Ich vermute, dass es bei der Cache-Kohärenz innerhalb voneinsumnoch Raum für Verbesserungen gibt.