Xgboost: Verteiltes Rechnen mit Dask

Erstellt am 13. Feb. 2017  ·  46Kommentare  ·  Quelle: dmlc/xgboost

Hallo, ich bin Autor von Dask , einer Bibliothek für paralleles und verteiltes Rechnen in Python. Ich bin gespannt, ob in dieser Community Interesse besteht, bei der Verteilung von XGBoost auf Dask entweder für paralleles Training oder für ETL zusammenzuarbeiten.

Es gibt wahrscheinlich zwei Komponenten von Dask, die für dieses Projekt relevant sind:

  1. Ein generisches System für paralleles und verteiltes Rechnen, das auf beliebiger dynamischer Aufgabenplanung aufbaut. Die relevanten APIs sind hier wahrscheinlich dask.delayed und concurrent.futures
  2. Eine parallele und verteilte Teilmenge der Pandas-API, dask.dataframe , die für das Feature-Engineering und die Datenvorverarbeitung nützlich ist. Dies implementiert nicht die gesamte Pandas-API, kommt aber anständig nahe.

Besteht hier Interesse an einer Zusammenarbeit?

Hilfreichster Kommentar

Notizbuch: https://gist.github.com/19c89d78e34437e061876a9872f4d2df
Kurzer Screencast (sechs Minuten): https://youtu.be/Cc4E-PdDSro

Kritisches Feedback ist sehr willkommen. Bitte entschuldigen Sie noch einmal meine Unwissenheit auf diesem Gebiet.

Alle 46 Kommentare

@mrocklin Ich dachte, Dask hat Integrationen mit sklearn. Haben Sie sich unseren sklearn-Wrapper angesehen, um zu sehen, ob er damit funktioniert?

Eine sinnvolle Integration in ein verteiltes System muss typischerweise eher auf der Ebene pro Algorithmus als auf der Bibliotheksebene erfolgen. Es gibt einige Möglichkeiten, wie sich SKLearn und Dask gegenseitig helfen können, ja, aber sie sind nicht besonders tiefgreifend.

Dask Dataframe wäre ein guter Anfang. In unserer Codebasis haben wir eine Prüfung auf Pandas-Datenrahmen. Da könnte dask dataframe als Anfang passen.

Was passiert also, wenn jemand mit einem Multi-Terabyte-Dask-Datenrahmen ankommt? Konvertieren Sie es einfach in Pandas und fahren Sie fort? Oder gibt es eine Möglichkeit, XGBoost intelligent über einen Cluster hinweg zu parallelisieren und auf die verschiedenen Pandas-Datenrahmen zu verweisen, die einen Dask-Datenrahmen bilden?

Benutzer können Stapelgröße angeben? Ich könnte mir vorstellen, dass Benutzer von partial_fit profitieren können.

cc @tqchen , der mit dem verteilten Teil des Codes besser vertraut ist.

Die verteilte Version von xgboost kann in einen verteilten Jobstarter eingehängt werden, idealerweise erhalten Sie Datenpartitions-Feeds in xgboost und fahren dann fort.

@mrocklin Ich denke, der relevanteste Teil ist das Modul xgboost-spark und xgboost-flink, das xgboost in die mapPartition-Funktion von spark/flink einbettet. Ich schätze, es gäbe etwas Ähnliches in Dask

Die Anforderung von xgboost-Seite ist, dass XGBoost die Verbindung zwischen Prozessen per Rabit handhabt und einen Tracker (der jeden Job verbindet) von der Client-Seite starten muss.

siehe relevanten Code in https://github.com/dmlc/xgboost/blob/master/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala#L112

Rabit ist so konzipiert, dass es in andere verteilte Systeme eingebettet werden kann, daher denke ich, dass es nicht allzu schwer sein könnte, die Anpassung auf der Python-Seite vorzunehmen.

Das Starten anderer verteilter Systeme von Dask aus ist normalerweise ziemlich machbar. Wie verschieben Sie Daten vom verteilten Hosting-System (spark/flink/dask) nach xg-boost? Oder dient dies dem verteilten Training für Small-Data?

Konkret erwarte ich, ein System wie folgt aufzubauen:

  • Auf jedem Dask-Worker starte ich einen Rabit-Server. Dask gibt diesen Rabit-Servern genügend Informationen, um sich gegenseitig zu finden.
  • Ich erstelle einen lokalen XGBoost-Zustand für jeden Worker, der das aktuelle Trainingsmodell darstellt
  • Ich füttere dieses Pro-Worker-Objekt wiederholt mit Pandas-Datenrahmen oder numpy-Arrays
  • Ich lausche auf ein Signal von XGBoost, das mich auffordert, aufzuhören

Entspricht dies Ihrer Erwartung? Ist es für Sie einfach, mich auf die relevante Python-API hinzuweisen?

Ja, relevante Informationen finden Sie hier https://github.com/dmlc/xgboost/blob/master/tests/distributed/ für die Python-API.

Was Sie zusätzlich tun müssen, ist, einen Rabit-Tracker auf der Treiberseite zu starten (wahrscheinlich der Ort, der dask antreibt), dies geschieht im dmlc-submit-Skript hier https://github.com/dmlc/dmlc-core /tree/master/tracker/dmlc_tracker

OK, fülle meine Gliederung von vorher aus:

Bevor wir XGBoost-Code ausführen, richten wir ein Rabit-Netzwerk ein

Auf dem Driver/Scheduler-Knoten starten wir einen Rabit-Tracker

envs = {'DMLC_NUM_WORKER' : nworker,
        'DMLC_NUM_SERVER' : nserver}

rabit = RabitTracker(hostIP=ip_address, nslave=num_workers)
envs.update(rabit.slave_envs())
rabit.start(args.num_workers)  # manages connections in background thread

Ich kann auch einen ähnlichen Prozess durchlaufen, um ein PSTracker zu starten. Sollte sich dies auf demselben zentralisierten Computer befinden oder an einem anderen Ort innerhalb des Netzwerks? Sollen es ein paar davon sein? Sollte dies vom Benutzer konfigurierbar sein?

Irgendwann lasse ich meinen Tracker (und pstrackers?) dem Rabit-Netzwerk beitreten und blockieren.

rabit.join()  # join network

Auf Worker-Knoten muss ich diese Umgebungsvariablen (die ich durch normale Dask-Kanäle verschieben werde) in die lokale Umgebung ausgeben. Dann sollte es genügen, xgboost.rabit.init() anzurufen

import os
os.environ.update(envs)
xgboost.rabit.init()

Wenn man sich den Rabit-Code ansieht, sieht es so aus, als ob Umgebungsvariablen die einzige Möglichkeit sind, diese Informationen bereitzustellen. Können Sie das verifizieren? Gibt es eine Möglichkeit, Host-/Portinformationen des Trackers als direkte Eingaben bereitzustellen?

Ausbildung

Dann konvertiere ich meine numpy Arrays / Pandas Dataframes / scipy Sparse Arrays in DMatrix-Objekte, das scheint relativ einfach zu sein. Ich habe jedoch wahrscheinlich mehrere Datenstapel pro Arbeiter. Gibt es eine saubere Möglichkeit, den Zug mehrmals mit mehr Daten anzurufen, sobald diese verfügbar sind? Ich bin besorgt über die Kommentare zu diesen Zeilen:

# Run training, all the features in training API is available.
# Currently, this script only support calling train once for fault recovery purpose.
bst = xgb.train(param, dtrain, num_round, watchlist, early_stopping_rounds=2)

Müssen wir warten, bis alle Daten eintreffen, bevor wir mit dem Training beginnen?

Beispieldatensatz / Problem

Angenommen, ich habe alles oben Richtige, gibt es dann ein standardmäßiges verteiltes Trainingsbeispiel, das die Leute zur Demonstration verwenden?

Es ist nicht erforderlich, pstracker zu starten.

  • Der Tracker muss nur an einer Stelle gestartet werden, wahrscheinlich auf dem Scheduler (Treiber), er hat keine datenintensive Aufgabe und dient nur dazu, die Werke zu verbinden.
  • Die env args können als kwargs in rabit.init übergeben werden
  • Da Tree Boosting ein Stapelalgorithmus ist, müssen wir warten, bis alle Daten aufgenommen wurden, bevor wir mit dem Training beginnen.

    • Beachten Sie jedoch, dass jeder Worker nur einen Shard (Teilmenge von Zeilen) von Daten nehmen muss.

    • Idealerweise sollten wir die Data-Iter-Schnittstelle verwenden, um die Daten als Mini-Batch an DMatrix zu übergeben, damit nicht der gesamte Datensatz im Speicher sitzen muss

    • Dies geschieht über https://github.com/dmlc/xgboost/blob/master/include/xgboost/c_api.h#L117 , die noch keinen Python-Wrapper haben.

    • Für die erste Lösung würde ich empfehlen, das Array direkt zu übergeben

Ich hatte heute morgen etwas Zeit damit zu spielen. Ergebnisse hier: https://github.com/mrocklin/dask-xgboost

Bisher übernimmt es nur das verteilte Lernen eines einzelnen In-Memory-Datensatzes. Es kamen einige Fragen auf:

  1. Was ist der beste Weg, DMatrix-Objekte zu serialisieren und weiterzugeben?
  2. Was ist der beste Weg, um ein Booster-Ergebnis zu serialisieren und zurückzugeben?
  3. Wie werden die oben aufgeführten Umgebungsvariablen den Argumenten in rabit.init ? Was genau ist die erwartete Form von Eingaben für rabit.init ? Das Ergebnis von slave_envs() an rabit.init zu übergeben, wird offensichtlich nicht funktionieren, weil es eine Liste erwartet. Sollten wir jeden Schlüsselnamen in --key umwandeln, vielleicht das Präfix DMLC weglassen und in Kleinbuchstaben umwandeln?
  4. Gibt es eine gute Möglichkeit, die Korrektheit zu testen? Wie vergleichen wir zwei Booster-Objekte? Sollten wir erwarten, dass verteiltes Training genau das gleiche Ergebnis liefert wie sequenzielles Training?
  • Normalerweise serialisieren Sie DMatrix nicht, es ist eher wie ein Datenhalter für die Trainingszeit. Ich gehe davon aus, dass Daten herumgereicht und von dask (Array/Datenrahmen) geteilt und dann an xgboost weitergegeben werden

    • Wir können bessere Wege erkunden, um Daten anders als direkt über ein In-Memory-Array zu übergeben, möglicherweise indem wir xgboost einen Daten-Iterator zur Verfügung stellen

  • Sie können Booster beizen, solange xgboost auf beiden Seiten installiert ist.
  • Tut mir leid, dass ich nicht erläutere, wie die Dinge übergeben werden, es sollte so sein
rabit.init(['DMLC_KEY1=VALUE1', 'DMLC_KEY2=VALUE2']
  • Normalerweise sind die von verteilten und einzelnen Maschinen trainierten Booster nicht gleich, aber hier sind ein paar Dinge zu überprüfen

    • Der von allen Arbeitern zurückgegebene Booster sollte identisch sein

    • Bei der Suche nach dem prädiktiven Validierungsfehler sollte dieser etwa so niedrig sein wie bei einer einzelnen Maschine

Zwei weitere allgemeine Fragen dazu, wie dies verwendet wird (ich habe keine Erfahrung mit XGBoost und nur wenig Erfahrung mit maschinellem Lernen, bitte verzeihen Sie meine Unwissenheit).

  1. Ist es sinnvoll, mehrere Worker mit denselben Eingabedaten zu verwenden? (XGBoost ist rechnerisch gebunden?)
  2. Wenn wir mit größeren Datensätzen arbeiten, muss ich irgendetwas Besonderes tun, um jedem XGBoost-Worker mitzuteilen, dass sich seine Daten von denen seiner Kollegen unterscheiden?

Welcher Anwendungsfall ist häufiger?

Jede Arbeit sollte an einer anderen Datenpartition (nach Zeilen) arbeiten, sie sollten NICHT dieselben Eingabedaten betrachten.

  • Wenn die Daten nicht groß genug sind, sollte eine Multithread-Version ausreichen
  • Jede Arbeit wird Statistiken separat auf ihrer Partition sammeln und miteinander synchronisieren

Dies entspricht normalerweise dem mapPartition-Betrieb in Frameworks wie Spark/Flink

Angenommen, mein Datensatz hat 8 Zeilen und 4 Spalten, wenn wir zwei Worker starten

  • Arbeiter 0 liest aus Zeile 0-3
  • Arbeiter 1 liest von Zeile 4-7

OK, was da oben jetzt ist, ist etwas sauberer. Es wäre schön, wenn wir die Möglichkeit hätten, Ergebnisse so zu konsumieren, wie sie auf jedem Worker generiert wurden, aber wir haben das vorerst umgangen. Hier die aktuelle Lösung:

  1. Lassen Sie das Dask-Array oder den Datenrahmen auf dem Cluster bestehen und warten Sie, bis es fertig ist
  2. Finden Sie heraus, wo jeder Chunk/jede Partition gelandet ist
  3. Sagen Sie jedem Worker, dass er genau diese Chunks/Partitionen verketten und mit ihnen trainieren soll

Diese Lösung erscheint handhabbar, ist aber nicht ideal. Es wäre praktisch, wenn xgboost-python Ergebnisse akzeptieren könnte, sobald sie ankommen. Aber ich denke, das nächste, was zu tun ist, ist es, es in der Praxis zu versuchen.

Ich werde mich im Internet nach Beispielen umsehen. Wenn jemand zufällig ein künstliches Problem hat, das ich einfach mit der numpy- oder Pandas-API generieren kann, wäre das willkommen. Bis dahin hier ein triviales Beispiel auf meinem Laptop mit zufälligen Daten:

In [1]: import dask.dataframe as dd

In [2]: df = dd.demo.make_timeseries('2000', '2001', {'x': float, 'y': float, 'z': int}, freq='1s', partition_freq=
   ...: '1D')  # some random time series data

In [3]: df.head()
Out[3]: 
                            x         y     z
2000-01-01 00:00:00  0.778864  0.824796   977
2000-01-01 00:00:01 -0.019888 -0.173454  1023
2000-01-01 00:00:02  0.552826  0.051995  1083
2000-01-01 00:00:03 -0.761811  0.780124   959
2000-01-01 00:00:04 -0.643525  0.679375   980

In [4]: labels = df.z > 1000

In [5]: del df['z']

In [6]: df.head()
Out[6]: 
                            x         y
2000-01-01 00:00:00  0.778864  0.824796
2000-01-01 00:00:01 -0.019888 -0.173454
2000-01-01 00:00:02  0.552826  0.051995
2000-01-01 00:00:03 -0.761811  0.780124
2000-01-01 00:00:04 -0.643525  0.679375

In [7]: labels.head()
Out[7]: 
2000-01-01 00:00:00    False
2000-01-01 00:00:01     True
2000-01-01 00:00:02     True
2000-01-01 00:00:03    False
2000-01-01 00:00:04    False
Name: z, dtype: bool

In [8]: from dask.distributed import Client

In [9]: c = Client()  # creates a local "cluster" on my laptop

In [10]: from dask_xgboost import train
/home/mrocklin/Software/anaconda/lib/python3.5/site-packages/sklearn/cross_validation.py:44: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)

In [11]: param = {'max_depth': 2, 'eta': 1, 'silent': 1, 'objective': 'binary:logistic'}  # taken from example

In [12]: bst = train(c, param, df, labels)
/home/mrocklin/Software/anaconda/lib/python3.5/site-packages/sklearn/cross_validation.py:44: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)
/home/mrocklin/Software/anaconda/lib/python3.5/site-packages/sklearn/cross_validation.py:44: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)
/home/mrocklin/Software/anaconda/lib/python3.5/site-packages/sklearn/cross_validation.py:44: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)
/home/mrocklin/Software/anaconda/lib/python3.5/site-packages/sklearn/cross_validation.py:44: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)
[14:46:20] Tree method is automatically selected to be 'approx' for faster speed. to use old behavior(exact greedy algorithm on single machine), set tree_method to 'exact'
[14:46:20] Tree method is automatically selected to be 'approx' for faster speed. to use old behavior(exact greedy algorithm on single machine), set tree_method to 'exact'
[14:46:20] Tree method is automatically selected to be 'approx' for faster speed. to use old behavior(exact greedy algorithm on single machine), set tree_method to 'exact'
[14:46:20] Tree method is automatically selected to be 'approx' for faster speed. to use old behavior(exact greedy algorithm on single machine), set tree_method to 'exact'

In [13]: bst
Out[13]: <xgboost.core.Booster at 0x7fbaacfd17b8>

Relevanter Code ist hier, falls jemand einen Blick darauf werfen möchte: https://github.com/mrocklin/dask-xgboost/blob/master/dask_xgboost/core.py

Wie gesagt, ich bin neu bei XGBoost, also vermisse ich wahrscheinlich Dinge.

Ein typisches Spielzeugbeispiel zum Ausprobieren finden Sie unter https://github.com/dmlc/xgboost/tree/master/demo/data
Es ist jedoch im libsvm-Format und muss ein wenig analysiert werden, um es in numpy zu bringen

Etwas Größeres (für das Sie eigentlich einen Cluster benötigen würden)? Oder gibt es eine Standardmethode, um einen Datensatz beliebiger Größe zu generieren?

Oder vielleicht ist eine bessere Frage: "Was würden Sie (oder jemand anderes, der diese Ausgabe liest) hier gerne sehen?"

Gebäudevorhersage jetzt. Wenn ich das Modell zurück zu einem Worker verschiebe (der Pickle/Unpickle-Prozess durchläuft) und dann bst.predict für einige Daten aufrufe, erhalte ich die folgende Fehlermeldung:

Doing rabit call after Finalize

Meine Annahme war, dass das Modell zu diesem Zeitpunkt in sich geschlossen ist und kein Rabit mehr verwenden muss. Es scheint auf dem Client-Rechner gut zu funktionieren. Irgendwelche Gedanken, warum ich diesen Fehler erhalten könnte, wenn ich predict ?

Ein Teil von Predict verwendet immer noch Rabit, hauptsächlich weil der Predictor noch Learner mit einigen Initialisierungsroutinen verwendet, die mit dem Training geteilt werden. Irgendwann sollte dies behoben werden, aber das ist vorerst der Fall.

Ich denke, solange es für den gemeinsamen Datensatz gut funktioniert, ist es ein interessanter Ausgangspunkt.

Es gibt sowieso Gründe, einen Cluster für mittlere Daten zu verwenden (einfache Planung in Cluster env), einige der Pyspark-Benutzer könnten daran interessiert sein, es auszuprobieren, wenn wir es ein wenig bewerben

Das Testen des wirklich wichtigen Datensatzes war schwierig, z. B. (versuchen Sie 1 Datensatz mit 1 Milliarde Zeilen). Kaggle könnte einen großen Datensatz haben, der relevant sein könnte, der etwa 10 Millionen beträgt.

Dieses Repository zeigt Experimente mit dem Datensatz der Fluggesellschaften, der meiner Meinung nach in zig Millionen Zeilen und zig Spalten liegt (Tausend nach One-Hot-Codierung?). Für ihren Benchmark sieht es so aus, als hätten sie eine Stichprobe von 100.000 Zeilen genommen und künstlich generiert größere Datensätze aus dieser Stichprobe. Vermutlich könnten wir dies bei Bedarf skalieren.

Hier ist ein Beispiel, in dem diese Daten mit Pandas und xgboost auf einem einzelnen Kern verwendet werden. Alle Empfehlungen zur Datenvorbereitung, zu Parametern oder zur richtigen Vorgehensweise sind willkommen.

In [1]: import pandas as pd

In [2]: df = pd.read_csv('train-0.1m.csv')

In [3]: df.head()
Out[3]: 
  Month DayofMonth DayOfWeek  DepTime UniqueCarrier Origin Dest  Distance  \
0   c-8       c-21       c-7     1934            AA    ATL  DFW       732   
1   c-4       c-20       c-3     1548            US    PIT  MCO       834   
2   c-9        c-2       c-5     1422            XE    RDU  CLE       416   
3  c-11       c-25       c-6     1015            OO    DEN  MEM       872   
4  c-10        c-7       c-6     1828            WN    MDW  OMA       423   

  dep_delayed_15min  
0                 N  
1                 N  
2                 N  
3                 N  
4                 Y  

In [4]: labels = df.dep_delayed_15min == 'Y'

In [5]: del df['dep_delayed_15min']

In [6]: df = pd.get_dummies(df)

In [7]: len(df.columns)
Out[7]: 652

In [8]: import xgboost as xgb
/home/mrocklin/Software/anaconda/lib/python3.5/site-packages/sklearn/cross_validation.py:44: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)

In [9]: dtrain = xgb.DMatrix(df, label=labels)

In [10]: param = {}  # Are there better choices for parameters?  I could use help here

In [11]: bst = xgb.train(param, dtrain)  # or other parameters here?
[17:50:28] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 124 extra nodes, 0 pruned nodes, max_depth=6
[17:50:30] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 120 extra nodes, 0 pruned nodes, max_depth=6
[17:50:32] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 120 extra nodes, 0 pruned nodes, max_depth=6
[17:50:33] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 116 extra nodes, 0 pruned nodes, max_depth=6
[17:50:35] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 112 extra nodes, 0 pruned nodes, max_depth=6
[17:50:36] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 114 extra nodes, 0 pruned nodes, max_depth=6
[17:50:38] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 106 extra nodes, 0 pruned nodes, max_depth=6
[17:50:39] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 116 extra nodes, 0 pruned nodes, max_depth=6
[17:50:41] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 104 extra nodes, 0 pruned nodes, max_depth=6
[17:50:43] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 100 extra nodes, 0 pruned nodes, max_depth=6

In [12]: test = pd.read_csv('test.csv')

In [13]: test.head()
Out[13]: 
  Month DayofMonth DayOfWeek  DepTime UniqueCarrier Origin Dest  Distance  \
0   c-7       c-25       c-3      615            YV    MRY  PHX       598   
1   c-4       c-17       c-2      739            WN    LAS  HOU      1235   
2  c-12        c-2       c-7      651            MQ    GSP  ORD       577   
3   c-3       c-25       c-7     1614            WN    BWI  MHT       377   
4   c-6        c-6       c-3     1505            UA    ORD  STL       258   

  dep_delayed_15min  
0                 N  
1                 N  
2                 N  
3                 N  
4                 Y  

In [14]: test_labels = test.dep_delayed_15min == 'Y'

In [16]: del test['dep_delayed_15min']

In [17]: test = pd.get_dummies(test)

In [18]: len(test.columns)  # oops, looks like the columns don't match up
Out[18]: 670

In [19]: dtest = xgb.DMatrix(test)

In [20]: predictions = bst.predict(dtest)  # this fails because of mismatched columns

Wie auch immer, hier ist eine Option. Der Datensatz der Fluggesellschaften scheint bekannt zu sein und kann in der Praxis unpraktisch groß sein. Auch hier ist maschinelles Lernen nicht meine Spezialität, daher weiß ich nicht, ob dies angemessen ist oder nicht.

cc @TomAugspurger , der der Typ zu sein scheint, der sich Gedanken darüber machen könnte.

In Bezug auf Dask und Predict kann ich Rabit immer wieder neu einrichten. Das fühlt sich allerdings ein bisschen unsauber an, weil es die Bewertung erzwingt, anstatt die Dinge faul zu halten. Aber dies ist kein ernsthafter Blocker.

Es treten einige Probleme mit der Vorhersage auf. Zwei Fragen:

  1. Kann ich Booster.predict innerhalb derselben Rabit-Session mehrmals anrufen?
  2. Kann ich rabit.init , Booster.predict und rabit.finalize in separaten Threads anrufen?

Derzeit erstelle ich einen neuen Tracker und rufe rabit.init im Haupt-Thread des Workers auf. Das funktioniert gut. Wenn ich jedoch Booster.predict in Worker-Threads aufrufe (jeder Dask-Worker verwaltet einen Thread-Pool für die Berechnung), erhalte ich Fehler wie Doing rabit call after Finalize . Irgendwelche Empfehlungen?

Ein Teil von Predict verwendet immer noch Rabit, hauptsächlich weil der Predictor noch Learner mit einigen Initialisierungsroutinen verwendet, die mit dem Training geteilt werden. Irgendwann sollte dies behoben werden, aber das ist vorerst der Fall.

Ich bin neugierig. Nachdem wir das trainierte Modell von einem Worker auf meinen Client-Computer serialisiert, übertragen und deserialisiert haben, scheint es mit normalen Daten gut zu funktionieren, obwohl kein Rabit-Netzwerk vorhanden ist. Es scheint, als ob ein mit Rabit trainiertes Modell verwendet werden kann, um Daten ohne Rabit vorherzusagen. Dies scheint auch in der Produktion notwendig zu sein. Können Sie hier mehr über die Einschränkungen bei der Verwendung eines mit Kaninchen trainierten Modells sagen?

Beispieldatensatz / Problem
Angenommen, ich habe alles oben Richtige, gibt es dann ein standardmäßiges verteiltes Trainingsbeispiel, das die Leute zur Demonstration verwenden?

Ich würde gerne die Ergebnisse dieses Experiments reproduzieren:

https://github.com/Microsoft/LightGBM/wiki/Experiments#parallel -experiment

mit der neuen Option binning + fast hist von XGBoost (#1950) sollten ähnliche Ergebnisse möglich sein.

Ein typisches Spielzeugbeispiel zum Ausprobieren finden Sie unter https://github.com/dmlc/xgboost/tree/master/demo/data
Es ist jedoch im libsvm-Format und muss ein wenig analysiert werden, um es in numpy zu bringen

Diese PR in sklearn könnte Sie interessieren: https://github.com/scikit-learn/scikit-learn/pull/935

@mrocklin Es gibt keine Einschränkung für die Wiederverwendung des Modells. So kann das in der verteilten Version trainierte Modell in der seriellen Version verwendet werden. Es ist nur so, dass die aktuelle Einschränkung des Prädiktors (wenn er mit Rabit kompiliert wird) eine gemischte Funktion mit der Trainingsfunktion hat (so dass der Rabit-Call passiert ist).

Jetzt wo du es sagst, denke ich, dass wir vielleicht eine Lösung für das Problem haben. Führen Sie einfach ein rabit.init durch (ohne etwas einzugeben, und lassen Sie den Prädiktor glauben, er sei der einzige Arbeiter ), bevor die Vorhersage das Problem lösen sollte

Jawohl. Tatsächlich löst das das Problem. dask-xgboost unterstützt jetzt Predict: https://github.com/mrocklin/dask-xgboost/commit/827a03d96977cda8d104899c9f42f52dac446165

Danke für die Problemumgehung @tqchen !

Hier ist ein Workflow mit dask.dataframe und xgboost auf einem kleinen Beispiel des Datasets der Fluggesellschaften auf meinem lokalen Laptop. Sieht das für alle OK aus? Gibt es API-Elemente von XGBoost, die mir hier fehlen?

In [1]: import dask.dataframe as dd

In [2]: import dask_xgboost as dxgb

In [3]: df = dd.read_csv('train-0.1m.csv')

In [4]: df.head()
Out[4]: 
  Month DayofMonth DayOfWeek  DepTime UniqueCarrier Origin Dest  Distance  \
0   c-8       c-21       c-7     1934            AA    ATL  DFW       732   
1   c-4       c-20       c-3     1548            US    PIT  MCO       834   
2   c-9        c-2       c-5     1422            XE    RDU  CLE       416   
3  c-11       c-25       c-6     1015            OO    DEN  MEM       872   
4  c-10        c-7       c-6     1828            WN    MDW  OMA       423   

  dep_delayed_15min  
0                 N  
1                 N  
2                 N  
3                 N  
4                 Y  

In [5]: labels = df.dep_delayed_15min == 'Y'

In [6]: del df['dep_delayed_15min']

In [7]: df = df.categorize()

In [8]: df = dd.get_dummies(df)

In [9]: data_train, data_test = df.random_split([0.9, 0.1], random_state=123)

In [10]: labels_train, labels_test = labels.random_split([0.9, 0.1], random_state=123)

In [11]: from dask.distributed import Client

In [12]: client = Client()  # in a large-data situation I probably should have done this before calling categorize above (which requires computation)

In [13]: param = {}  # Are there better choices for parameters?

In [14]: bst = dxgb.train(client, {}, data_train, labels_train)
[14:00:46] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 120 extra nodes, 0 pruned nodes, max_depth=6
[14:00:48] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 120 extra nodes, 0 pruned nodes, max_depth=6
[14:00:50] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 122 extra nodes, 0 pruned nodes, max_depth=6
[14:00:53] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 118 extra nodes, 0 pruned nodes, max_depth=6
[14:00:55] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 120 extra nodes, 0 pruned nodes, max_depth=6
[14:00:57] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 114 extra nodes, 0 pruned nodes, max_depth=6
[14:00:59] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 118 extra nodes, 0 pruned nodes, max_depth=6
[14:01:01] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 118 extra nodes, 0 pruned nodes, max_depth=6
[14:01:04] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 94 extra nodes, 0 pruned nodes, max_depth=6
[14:01:06] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 102 extra nodes, 0 pruned nodes, max_depth=6

In [15]: bst
Out[15]: <xgboost.core.Booster at 0x7f689803af60>

In [16]: predictions = dxgb.predict(client, bst, data_test)

In [17]: predictions
Out[17]: 
Dask Series Structure:
npartitions=1
None    float32
None        ...
Name: predictions, dtype: float32
Dask Name: _predict_part, 9 tasks

Mein kurzfristiges Ziel ist es, einen kurzen Blogpost darüber zu schreiben, damit hoffentlich jemand anderes mit mehr Erfahrung mit XGBoost und mehr Zeit kommt, um dieses Projekt zu übernehmen und voranzutreiben. (Ich arbeite, wie alle anderen hier, gleichzeitig an ein paar anderen Projekten wie diesem.)

Ich bin parteiisch für den Datensatz der Fluggesellschaften, nur weil ich ihn bereits in einem S3-Bucket habe. Ich stimme jedoch zu, dass der Criteo-Datensatz für eine bessere Demonstration in großem Maßstab sorgen würde.

Ich bin mir immer noch nicht sicher, welche Parameter ich verwenden oder wie ich das Ergebnis beurteilen soll. Für Parameter kann ich hier das Experiment von @szilard verwenden . Gibt es eine gute Möglichkeit, die Vorhersagen zu beurteilen? Suchen wir zum Beispiel nach predictions > 0.5 passend zu labels_test ?

Die vielleicht gebräuchlichste Methode zur Bewertung der Vorhersageleistung für die binäre Klassifizierung (insbesondere in Forschungs- oder Wettbewerbsumgebungen) besteht darin, die Fläche unter der ROC-Kurve (AUC) zu verwenden, obwohl man in realen Anwendungen Metriken verwenden sollte, die an den „Geschäftswerten“ ausgerichtet sind anhand der Modelle hergestellt.

Suchen wir beispielsweise nach Vorhersagen > 0,5, die mit labels_test übereinstimmen?

Jawohl. Mittelt man das am Testgerät, ist das die Testgenauigkeit. Aber es ist wahrscheinlich, dass der Datensatz unausgewogen ist (es fehlen viel mehr Klicks als Klicks). In diesem Fall ist der ROC-AUC- Wert eine bessere Metrik.

from sklearn.metrics import roc_auc_score
print(roc_auc_score(labels_test, predictions))

Angenommen, predictions ist ein 1D-Array positiver Wahrscheinlichkeiten, die vom Modell für jede Zeile im Testsatz geschätzt werden.

@mrocklin Eine Folgefrage: Erlaubt dask Multithread-Worker-Jobs? Ich weiß, dass dies für Python aufgrund von GIL nicht sehr relevant ist. Aber xgboost kann Multi-Thread-Training pro Worker ermöglichen und sich dennoch verteilt miteinander abstimmen. Wir sollten die nthread-Argumente von xgboost immer auf die Anzahl der Arbeitskerne dieses Workers setzen

Kurze Antwort ist "ja". Die meiste Verwendung von Dask findet bei Projekten wie NumPy, Pandas, SKLearn und anderen statt, die meistens nur C- und Fortran-Code sind, der mit Python verpackt ist. Die GIL betrifft diese Bibliotheken nicht. Einige Leute verwenden Dask für ähnliche Anwendungen wie PySpark RDD (siehe dask.bag ) und werden davon betroffen sein. Diese Gruppe ist jedoch in der Minderheit.

Also ja, Dask erlaubt Multithreading-Aufgaben. Wie weisen wir XGBoost an, mehrere Threads zu verwenden? In meinen bisherigen Experimenten sehe ich eine hohe CPU-Auslastung, ohne irgendwelche Parameter zu ändern, also funktioniert vielleicht standardmäßig alles gut?

XGBoost verwendet standardmäßig Multithreading und verwendet alle verfügbaren CPU-Threads auf dem Computer (statt auf diesem Worker), wenn nthread nicht festgelegt ist. Dies kann zu einer Race-Bedingung führen, wenn mehrere Worker derselben Maschine zugewiesen werden.

Daher ist es immer gut, den nthread-Parameter auf die maximale Anzahl von Kernen zu setzen, die der Worker verwenden darf. Normalerweise ist es eine gute Praxis, etwa 4 Threads pro Arbeiter zu verwenden

Sicher, sollte in vollendet werden
https://github.com/mrocklin/dask-xgboost/commit/c22d066b67c78710d5ad99b8620edc55182adc8f

Am Montag, 20. Februar 2017 um 18:31 Uhr, Tianqi Chen [email protected]
schrieb:

XGBoost verwendet standardmäßig Multithreading und verwendet die gesamte verfügbare CPU
Threads auf dem Computer (statt auf diesem Worker), wenn nthread nicht festgelegt ist.
Dies kann zu einer Race-Bedingung führen, wenn mehrere Worker demselben zugewiesen werden
Maschine.

Daher ist es immer gut, den nthread-Parameter auf die maximale Anzahl von zu setzen
Kerne, die der Arbeiter verwenden darf. Normalerweise ist es eine gute Praxis, sagen wir, es zu verwenden
4 Threads pro Arbeiter


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/dmlc/xgboost/issues/2032#issuecomment-281205747 , oder stumm
der Faden
https://github.com/notifications/unsubscribe-auth/AASszPELRoeIvqEzyJhkKumIs-vd0PHiks5reiJngaJpZM4L_PXa
.

Notizbuch: https://gist.github.com/19c89d78e34437e061876a9872f4d2df
Kurzer Screencast (sechs Minuten): https://youtu.be/Cc4E-PdDSro

Kritisches Feedback ist sehr willkommen. Bitte entschuldigen Sie noch einmal meine Unwissenheit auf diesem Gebiet.

@mrocklin tolle Demo! Ich denke, die Laufzeitleistung (und möglicherweise die Speichernutzung) könnte durch die Verwendung von 'tree_method': 'hist', 'grow_policy': 'lossguide' im param dict erheblich verbessert werden.

Danke @ogrisel. Mit diesen Parametern geht die Trainingszeit von sechs Minuten auf eine Minute. Die Speicherauslastung scheint jedoch ungefähr gleich zu bleiben.

OK, darauf zurückkommen. Gibt es andere XGBoost-Operationen als Trainieren und Vorhersagen, die wir implementieren sollten?

@tqchen oder @ogrisel , wenn einer von euch die Zeit hat, die Implementierung unter https://github.com/mrocklin/dask-xgboost/blob/master/dask_xgboost/core.py durchzusehen, wäre ich dankbar. Ich verstehe jedoch, dass das Durchsuchen einer fremden Codebasis nicht immer ganz oben auf der Prioritätsliste steht.

Wenn alles in Ordnung ist, werde ich der README etwas mehr hinzufügen, auf PyPI veröffentlichen, und dann können wir dieses Problem wahrscheinlich schließen.

Ich denke, nur trainieren und vorhersagen müssen verteilt werden. Andere Dinge müssen nicht verteilt werden, da sie nicht auf den Datensatz antworten

Ich habe dask-xgboost zu PyPI gepusht und nach https://github.com/dask/dask-xgboost verschoben

Danke @tqchen und @ogrisel für eure Hilfe hier. Die Zusammenarbeit machte dies relativ einfach.

Ich würde gerne Leuten helfen, wenn sie Benchmarks durchführen möchten. Bis dahin Schließung.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen