9.2. Rechenleistung#

Für einige Anwendungen ist die Leistung (hauptsächlich Latenz und Durchsatz zur Vorhersagezeit) von Schätzern von entscheidender Bedeutung. Es kann auch von Interesse sein, den Trainingsdurchsatz zu berücksichtigen, aber dieser ist in einer Produktionsumgebung (wo er oft offline stattfindet) oft weniger wichtig.

Wir werden hier die Größenordnungen überprüfen, die Sie von einer Reihe von scikit-learn-Schätzern in verschiedenen Kontexten erwarten können, und einige Tipps und Tricks zur Überwindung von Leistungsengpässen geben.

Die Vorhersagelatenz wird als die verstrichene Zeit gemessen, die für eine Vorhersage erforderlich ist (z. B. in Mikrosekunden). Die Latenz wird oft als Verteilung betrachtet und Operationsingenieure konzentrieren sich oft auf die Latenz bei einem bestimmten Perzentil dieser Verteilung (z. B. dem 90. Perzentil).

Der Vorhersagedurchsatz ist definiert als die Anzahl der Vorhersagen, die die Software in einer gegebenen Zeit liefern kann (z. B. in Vorhersagen pro Sekunde).

Ein wichtiger Aspekt der Leistungsoptimierung ist auch, dass sie die Vorhersagegenauigkeit beeinträchtigen kann. In der Tat sind einfachere Modelle (z. B. linear statt nichtlinear oder mit weniger Parametern) oft schneller, aber nicht immer in der Lage, die gleichen Eigenschaften der Daten wie komplexere Modelle zu berücksichtigen.

9.2.1. Vorhersagelatenz#

Eines der direktesten Anliegen, die man bei der Verwendung/Auswahl eines Machine-Learning-Toolkits haben kann, ist die Latenz, mit der Vorhersagen in einer Produktionsumgebung getroffen werden können.

Die wichtigsten Faktoren, die die Vorhersagelatenz beeinflussen, sind:

  1. Anzahl der Merkmale

  2. Eingabedatenrepräsentation und Sparsity

  3. Modellkomplexität

  4. Merkmalsextraktion

Ein letzter wichtiger Parameter ist auch die Möglichkeit, Vorhersagen in Stapeln oder einzeln durchzuführen.

9.2.1.1. Stapel- versus atomarer Modus#

Im Allgemeinen ist die Stapelverarbeitung von Vorhersagen (viele Instanzen gleichzeitig) aus einer Reihe von Gründen effizienter (Branching-Vorhersagbarkeit, CPU-Cache, Optimierungen von lineare Algebra-Bibliotheken usw.). Hier sehen wir in einer Konfiguration mit wenigen Merkmalen, dass der Stapelmodus unabhängig von der Wahl des Schätzers immer schneller ist, und für einige von ihnen um 1 bis 2 Größenordnungen.

atomic_prediction_latency

bulk_prediction_latency

Um verschiedene Schätzer für Ihren Fall zu benchmarken, können Sie einfach den Parameter n_features in diesem Beispiel ändern: Vorhersagelatenz. Dies sollte Ihnen eine Schätzung der Größenordnung der Vorhersagelatenz geben.

9.2.1.2. Scikit-learn für reduzierte Validierungs-Overhead konfigurieren#

Scikit-learn führt einige Validierungen von Daten durch, die den Overhead pro Aufruf von predict und ähnlichen Funktionen erhöhen. Insbesondere die Überprüfung, ob Merkmale endlich sind (nicht NaN oder unendlich), erfordert einen vollständigen Durchlauf über die Daten. Wenn Sie sicherstellen, dass Ihre Daten akzeptabel sind, können Sie die Überprüfung auf Endlichkeit unterdrücken, indem Sie die Umgebungsvariable SKLEARN_ASSUME_FINITE auf eine nicht-leere Zeichenfolge setzen, bevor Sie scikit-learn importieren, oder sie in Python mit set_config konfigurieren. Für mehr Kontrolle als diese globalen Einstellungen ermöglicht ein config_context, diese Konfiguration innerhalb eines bestimmten Kontexts festzulegen.

>>> import sklearn
>>> with sklearn.config_context(assume_finite=True):
...     pass  # do learning/prediction here with reduced validation

Beachten Sie, dass dies alle Verwendungen von assert_all_finite innerhalb des Kontexts beeinflusst.

9.2.1.3. Einfluss der Anzahl der Merkmale#

Offensichtlich steigt mit zunehmender Anzahl von Merkmalen auch der Speicherverbrauch jedes Beispiels. Tatsächlich ist für eine Matrix mit \(M\) Instanzen und \(N\) Merkmalen die Speicherkomplexität \(O(NM)\). Aus rechnerischer Sicht bedeutet dies auch, dass die Anzahl der Basisoperationen (z. B. Multiplikationen für Vektor-Matrix-Produkte in linearen Modellen) ebenfalls zunimmt. Hier ist eine Grafik der Entwicklung der Vorhersagelatenz mit der Anzahl der Merkmale.

influence_of_n_features_on_latency

Insgesamt können Sie erwarten, dass die Vorhersagezeit mindestens linear mit der Anzahl der Merkmale zunimmt (nichtlineare Fälle können je nach globalem Speicherbedarf und Schätzer auftreten).

9.2.1.4. Einfluss der Eingabedatenrepräsentation#

Scipy bietet spärliche Matrix-Datenstrukturen, die für die Speicherung spärlicher Daten optimiert sind. Das Hauptmerkmal von spärlichen Formaten ist, dass keine Nullen gespeichert werden, sodass bei spärlichen Daten viel weniger Speicher verbraucht wird. Ein Nicht-Null-Wert in einer spärlichen (CSR oder CSC) Darstellung belegt im Durchschnitt nur eine 32-Bit-Integer-Position + den 64-Bit-Gleitkommawert + zusätzliche 32 Bit pro Zeile oder Spalte in der Matrix. Die Verwendung spärlicher Eingaben für ein dichtes (oder spärliches) lineares Modell kann die Vorhersage erheblich beschleunigen, da nur die Nicht-Null-Merkmale das Skalarprodukt und damit die Modellvorhersagen beeinflussen. Wenn Sie also 100 Nicht-Null-Werte in einem 1e6-dimensionalen Raum haben, benötigen Sie nur 100 Multiplikations- und Additionsoperationen anstelle von 1e6.

Die Berechnung über eine dichte Darstellung kann jedoch von hochoptimierten Vektoroperationen und Multithreading in BLAS profitieren und tendiert dazu, weniger CPU-Cache-Fehler zu verursachen. Daher sollte die Sparsity typischerweise recht hoch sein (maximal 10 % Nicht-Null-Werte, je nach Hardware zu prüfen), damit die spärliche Eingaberepräsentation auf einer Maschine mit vielen CPUs und einer optimierten BLAS-Implementierung schneller ist als die dichte Eingaberepräsentation.

Hier ist ein Beispielcode, um die Sparsity Ihrer Eingaben zu testen.

def sparsity_ratio(X):
    return 1.0 - np.count_nonzero(X) / float(X.shape[0] * X.shape[1])
print("input sparsity ratio:", sparsity_ratio(X))

Als Faustregel können Sie davon ausgehen, dass Sie wahrscheinlich von spärlichen Formaten profitieren können, wenn das Sparsity-Verhältnis größer als 90 % ist. Weitere Informationen zum Erstellen (oder Konvertieren Ihrer Daten in) spärliche Matrixformate finden Sie in der Dokumentation von Scipy. Meistens funktionieren die CSR- und CSC-Formate am besten.

9.2.1.5. Einfluss der Modellkomplexität#

Im Allgemeinen wird erwartet, dass mit zunehmender Modellkomplexität die Vorhersagekraft und die Latenz zunehmen. Eine Steigerung der Vorhersagekraft ist in der Regel interessant, aber für viele Anwendungen wäre es besser, die Vorhersagelatenz nicht zu stark zu erhöhen. Wir werden diese Idee nun für verschiedene Familien von überwachten Modellen untersuchen.

Für sklearn.linear_model (z. B. Lasso, ElasticNet, SGDClassifier/Regressor, Ridge & RidgeClassifier, LinearSVC, LogisticRegression...) ist die Entscheidungfunktion, die zur Vorhersagezeit angewendet wird, dieselbe (ein Skalarprodukt), sodass die Latenz äquivalent sein sollte.

Hier ist ein Beispiel mit SGDClassifier mit der elasticnet-Strafe. Die Regularisierungsstärke wird global durch den Parameter alpha gesteuert. Mit einem ausreichend hohen alpha kann dann der Parameter l1_ratio von elasticnet erhöht werden, um verschiedene Grade von Sparsity in den Modellkoeffizienten zu erzwingen. Höhere Sparsity wird hier als geringere Modellkomplexität interpretiert, da weniger Koeffizienten benötigt werden, um sie vollständig zu beschreiben. Natürlich beeinflusst Sparsity wiederum die Vorhersagezeit, da das spärliche Skalarprodukt eine Zeit benötigt, die grob proportional zur Anzahl der Nicht-Null-Koeffizienten ist.

en_model_complexity

Für die sklearn.svm-Algorithmenfamilie mit einem nichtlinearen Kernel ist die Latenz an die Anzahl der Stützvektoren gebunden (je weniger, desto schneller). Latenz und Durchsatz sollten (asymptotisch) linear mit der Anzahl der Stützvektoren in einem SVC- oder SVR-Modell wachsen. Der Kernel wird auch die Latenz beeinflussen, da er verwendet wird, um den Projektionsvektor des Eingabevektors einmal pro Stützvektor zu berechnen. In der folgenden Grafik wurde der Parameter nu von NuSVR verwendet, um die Anzahl der Stützvektoren zu beeinflussen.

nusvr_model_complexity

Für sklearn.ensemble-Bäume (z. B. RandomForest, GBT, ExtraTrees usw.) spielen die Anzahl der Bäume und ihre Tiefe die wichtigste Rolle. Latenz und Durchsatz sollten linear mit der Anzahl der Bäume skalieren. In diesem Fall haben wir direkt den Parameter n_estimators von GradientBoostingRegressor verwendet.

gbt_model_complexity

In jedem Fall seien Sie gewarnt, dass die Verringerung der Modellkomplexität die Genauigkeit beeinträchtigen kann, wie oben erwähnt. Zum Beispiel kann ein nichtlinear trennbares Problem mit einem schnellen linearen Modell behandelt werden, aber die Vorhersagekraft wird wahrscheinlich darunter leiden.

9.2.1.6. Latenz der Merkmalsextraktion#

Die meisten scikit-learn-Modelle sind in der Regel recht schnell, da sie entweder mit kompilierten Cython-Erweiterungen oder optimierten Berechnungsbibliotheken implementiert sind. Auf der anderen Seite, in vielen realen Anwendungen, steuert der Prozess der Merkmalsextraktion (d. h. die Umwandlung von Rohdaten wie Datenbankzeilen oder Netzwerkpaketen in Numpy-Arrays) die gesamte Vorhersagezeit. Beispielsweise dauert bei der Reuters-Textklassifizierungsaufgabe die gesamte Vorbereitung (Lesen und Parsen von SGML-Dateien, Tokenisieren des Textes und Hashing in einen gemeinsamen Vektorraum) je nach gewähltem Modell 100- bis 500-mal länger als der eigentliche Vorhersagecode.

prediction_time

In vielen Fällen wird daher empfohlen, Ihren Merkmalsextraktionscode sorgfältig zu messen und zu profilieren, da dies ein guter Ansatzpunkt für Optimierungen sein kann, wenn Ihre allgemeine Latenz für Ihre Anwendung zu langsam ist.

9.2.2. Vorhersagedurchsatz#

Eine weitere wichtige Metrik, auf die man bei der Dimensionierung von Produktionssystemen achten sollte, ist der Durchsatz, d. h. die Anzahl der Vorhersagen, die man in einer bestimmten Zeit treffen kann. Hier ist ein Benchmark aus dem Beispiel Vorhersagelatenz, das diese Größe für eine Reihe von Schätzern auf synthetischen Daten misst.

throughput_benchmark

Diese Durchsätze werden auf einem einzelnen Prozess erzielt. Eine naheliegende Möglichkeit, den Durchsatz Ihrer Anwendung zu erhöhen, besteht darin, zusätzliche Instanzen (normalerweise Prozesse in Python aufgrund des GIL) zu starten, die dasselbe Modell teilen. Man könnte auch Maschinen hinzufügen, um die Last zu verteilen. Eine detaillierte Erklärung, wie dies erreicht werden kann, liegt jedoch außerhalb des Rahmens dieser Dokumentation.

9.2.3. Tipps und Tricks#

9.2.3.1. Lineare Algebra-Bibliotheken#

Da scikit-learn stark auf Numpy/Scipy und lineare Algebra im Allgemeinen angewiesen ist, ist es sinnvoll, sich explizit um die Versionen dieser Bibliotheken zu kümmern. Grundsätzlich sollten Sie sicherstellen, dass Numpy mit einer optimierten BLAS / LAPACK-Bibliothek kompiliert ist.

Nicht alle Modelle profitieren von optimierten BLAS- und Lapack-Implementierungen. Zum Beispiel basieren Modelle, die auf (zufälligen) Entscheidungsbäumen basieren, typischerweise nicht auf BLAS-Aufrufen in ihren inneren Schleifen, ebenso wie Kernel-SVMs (SVC, SVR, NuSVC, NuSVR). Auf der anderen Seite wird ein lineares Modell, das mit einem BLAS DGEMM-Aufruf (über numpy.dot) implementiert ist, typischerweise enorm von einer abgestimmten BLAS-Implementierung profitieren und zu einer Beschleunigung um Größenordnungen gegenüber einem nicht optimierten BLAS führen.

Sie können die BLAS / LAPACK-Implementierung, die von Ihrer NumPy / SciPy / scikit-learn-Installation verwendet wird, mit dem folgenden Befehl anzeigen.

python -c "import sklearn; sklearn.show_versions()"

Optimierte BLAS / LAPACK-Implementierungen umfassen:

  • Atlas (benötigt hardware-spezifische Abstimmung durch Neukompilierung auf der Zielmaschine)

  • OpenBLAS

  • MKL

  • Apple Accelerate und vecLib Frameworks (nur OSX)

Weitere Informationen finden Sie auf der NumPy-Installationsseite und in diesem Blogbeitrag von Daniel Nouri, der einige schöne Schritt-für-Schritt-Installationsanweisungen für Debian / Ubuntu enthält.

9.2.3.2. Begrenzung des Arbeitsspeichers#

Einige Berechnungen, wenn sie mit Standard-Numpy-vektorisierten Operationen implementiert werden, erfordern die Verwendung einer großen Menge temporären Speichers. Dies kann den Systemspeicher erschöpfen. Wo Berechnungen in festen Speicherblöcken durchgeführt werden können, versuchen wir dies zu tun und erlauben dem Benutzer, die maximale Größe dieses Arbeitsspeichers (standardmäßig 1 GB) über set_config oder config_context anzugeben. Das Folgende schlägt vor, den temporären Arbeitsspeicher auf 128 MiB zu begrenzen.

>>> import sklearn
>>> with sklearn.config_context(working_memory=128):
...     pass  # do chunked work here

Ein Beispiel für eine blockweise Operation, die diese Einstellung berücksichtigt, ist pairwise_distances_chunked, die die zeilenweise Reduktion einer paarweisen Distanzmatrix erleichtert.

9.2.3.3. Modellkomprimierung#

Modellkomprimierung in scikit-learn betrifft derzeit nur lineare Modelle. In diesem Kontext bedeutet dies, dass wir die Sparsity des Modells (d. h. die Anzahl der Nicht-Null-Koordinaten in den Modellvektoren) steuern wollen. Es ist im Allgemeinen eine gute Idee, Modell-Sparsity mit spärlicher Eingabedatenrepräsentation zu kombinieren.

Hier ist ein Beispielcode, der die Verwendung der Methode sparsify() veranschaulicht.

clf = SGDRegressor(penalty='elasticnet', l1_ratio=0.25)
clf.fit(X_train, y_train).sparsify()
clf.predict(X_test)

In diesem Beispiel bevorzugen wir die elasticnet-Strafe, da sie oft ein guter Kompromiss zwischen Modellkompaktheit und Vorhersagekraft ist. Man kann auch den Parameter l1_ratio (in Kombination mit der Regularisierungsstärke alpha) weiter abstimmen, um diesen Kompromiss zu steuern.

Ein typisches Benchmark auf synthetischen Daten ergibt eine Verringerung der Latenz um über 30 %, wenn sowohl das Modell als auch die Eingabe spärlich sind (mit einer Nicht-Null-Koeffizienten-Ratio von 0,000024 bzw. 0,027400). Ihre Ergebnisse können je nach Sparsity und Größe Ihrer Daten und Ihres Modells variieren. Darüber hinaus kann die Sparsifizierung sehr nützlich sein, um den Speicherverbrauch von Vorhersagemodellen zu reduzieren, die auf Produktionsservern eingesetzt werden.

9.2.3.4. Modell-Reshaping#

Modell-Reshaping besteht darin, nur einen Teil der verfügbaren Merkmale auszuwählen, um ein Modell anzupassen. Mit anderen Worten, wenn ein Modell Merkmale während der Lernphase verwirft, können wir diese aus der Eingabe entfernen. Dies hat mehrere Vorteile. Erstens reduziert es den Speicher- (und damit Zeit-) Overhead des Modells selbst. Es ermöglicht auch, explizite Merkmalsauswahlkomponenten in einer Pipeline zu entfernen, sobald wir wissen, welche Merkmale aus einer früheren Ausführung beibehalten werden sollen. Schließlich kann es helfen, die Verarbeitungszeit und den I/O-Verbrauch in den vorgelagerten Datenzugriffs- und Merkmalsextraktionsschichten zu reduzieren, indem keine Merkmale gesammelt und erstellt werden, die vom Modell verworfen werden. Wenn die Rohdaten beispielsweise aus einer Datenbank stammen, ist es möglich, einfachere und schnellere Abfragen zu schreiben oder den I/O-Verbrauch zu reduzieren, indem die Abfragen leichtere Datensätze zurückgeben. Derzeit muss das Reshaping in scikit-learn manuell durchgeführt werden. Im Falle von spärlicher Eingabe (insbesondere im CSR-Format) ist es im Allgemeinen ausreichend, die relevanten Merkmale nicht zu generieren und ihre Spalten leer zu lassen.