Xgboost: Informatique distribuée avec Dask

CrĂ©Ă© le 13 fĂ©vr. 2017  Â·  46Commentaires  Â·  Source: dmlc/xgboost

Bonjour, je suis auteur de Dask , une bibliothĂšque de calcul parallĂšle et distribuĂ© en Python. Je suis curieux de savoir s'il y a un intĂ©rĂȘt au sein de cette communautĂ© Ă  collaborer Ă  la distribution de XGBoost sur Dask, soit pour une formation parallĂšle, soit pour ETL.

Il y a probablement deux composants de Dask qui sont pertinents pour ce projet :

  1. Un systÚme générique pour le calcul parallÚle et distribué, construit sur une planification de tùches dynamique arbitraire. Les API pertinentes ici sont probablement dask.delayed et concurrent.futures
  2. Un sous-ensemble parallÚle et distribué de l'API Pandas, dask.dataframe utile pour l'ingénierie des fonctionnalités et le prétraitement des données. Cela n'implémente pas l'intégralité de l'API Pandas, mais s'en rapproche décemment.

Y a-t-il un intĂ©rĂȘt Ă  collaborer ici?

Commentaire le plus utile

Carnet : https://gist.github.com/19c89d78e34437e061876a9872f4d2df
Capture d'Ă©cran courte (six minutes) : https://youtu.be/Cc4E-PdDSro

Les commentaires critiques sont les bienvenus. Encore une fois, veuillez pardonner mon ignorance dans ce domaine.

Tous les 46 commentaires

@mrocklin Je pensais que Dask avait des intĂ©grations avec sklearn. Avez-vous jetĂ© un coup d'Ɠil Ă  notre wrapper sklearn pour voir si cela fonctionnera avec cela ?

Une intĂ©gration significative avec un systĂšme distribuĂ© doit gĂ©nĂ©ralement ĂȘtre effectuĂ©e au niveau de l'algorithme plutĂŽt qu'au niveau de la bibliothĂšque. SKLearn et Dask peuvent s'entraider de certaines maniĂšres, oui, mais elles ne sont pas particuliĂšrement profondes.

La trame de donnĂ©es Dask serait un bon dĂ©but. Dans notre base de code, nous avons une vĂ©rification de la trame de donnĂ©es pandas. C'est peut-ĂȘtre lĂ  que la trame de donnĂ©es dask conviendrait comme point de dĂ©part.

Alors que se passe-t-il si quelqu'un arrive avec une trame de données dask de plusieurs téraoctets ? Le convertissez-vous simplement en Pandas et continuez? Ou existe-t-il un moyen de paralléliser XGBoost intelligemment sur un cluster, en pointant vers les différentes trames de données pandas qui composent une trame de données dask ?

Les utilisateurs peuvent spécifier la taille du lot ? J'imagine que les utilisateurs peuvent bénéficier de partial_fit.

cc @tqchen qui connaßt mieux la partie distribuée du code.

La version distribuĂ©e de xgboost peut ĂȘtre accrochĂ©e Ă  un lanceur de tĂąches distribuĂ©, idĂ©alement obtenir un flux de partition de donnĂ©es dans xgboost puis continuer.

@mrocklin Je pense que la partie la plus pertinente est le module xgboost-spark et xgboost-flink, qui intĂšgre xgboost dans la fonction mapPartition de spark/flink. Je suppose qu'il y aurait quelque chose de similaire dans Dask

L'exigence du cÎté xgboost est que XGBoost gÚre la connexion inter-processus par rabit, et devra démarrer un tracker (qui connecte chaque travail) du cÎté client.

voir le code pertinent dans https://github.com/dmlc/xgboost/blob/master/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala#L112

Rabit est conçu pour ĂȘtre intĂ©grĂ© dans un autre systĂšme distribuĂ©, donc je pense qu'il ne sera peut-ĂȘtre pas trop difficile de faire l'ajustement cĂŽtĂ© python.

Lancer d'autres systÚmes distribués à partir de Dask est généralement assez faisable. Comment déplacez-vous les données du systÚme distribué d'hébergement (spark/flink/dask) vers xg-boost ? Ou est-ce pour une formation distribuée sur les petites données ?

Plus concrÚtement, je prévois de construire un systÚme comme suit :

  • Sur chaque dask worker, je dĂ©marre un serveur Rabit. Dask donne Ă  ces serveurs Rabit suffisamment d'informations pour se retrouver.
  • Je crĂ©e un Ă©tat XGBoost local sur chaque travailleur qui reprĂ©sente le modĂšle en cours de formation
  • Je nourris Ă  plusieurs reprises cet objet par travailleur pandas dataframes ou numpy arrays
  • J'Ă©coute un signal de XGBoost qui me dit d'arrĂȘter

Cela correspond-il à votre attente ? Est-il facile pour vous de m'indiquer l'API Python pertinente ?

Oui, voir les informations pertinentes ici https://github.com/dmlc/xgboost/blob/master/tests/distributed/ pour l'API python.

Ce que vous devrez faire en plus, c'est démarrer un tracker rabit cÎté conducteur (probablement l'endroit qui pilote dask), cela se fait dans le script dmlc-submit ici https://github.com/dmlc/dmlc-core /tree/master/tracker/dmlc_tracker

OK, je remplis mon plan d'avant :

Avant d'exécuter un code XGBoost, nous avons mis en place un réseau Rabit

Sur le nƓud conducteur/planificateur, nous dĂ©marrons un traqueur de rabit

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

Je peux Ă©galement passer par un processus similaire pour dĂ©marrer un PSTracker . Cela devrait-il ĂȘtre sur la mĂȘme machine centralisĂ©e ou devrait-il ĂȘtre ailleurs dans le rĂ©seau ? Devrait-il y en avoir quelques-uns? Cela devrait-il ĂȘtre configurable par l'utilisateur ?

Finalement, mon tracker (et mes pstrackers ?) rejoint le réseau rabit et le bloque.

rabit.join()  # join network

Sur les nƓuds de travail, je dois vider ces variables d'environnement (que je dĂ©placerai via les canaux dask normaux) dans l'environnement local. Alors juste appeler xgboost.rabit.init() devrait suffire

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

En regardant le code Rabit, il semble que les variables d'environnement soient le seul moyen de fournir ces informations. Pouvez-vous vérifier cela ? Existe-t-il un moyen de fournir des informations sur l'hÎte/le port du tracker en tant qu'entrées directes ?

Formation

Ensuite, je convertis mes tableaux numpy / pandas dataframes / scipy sparse arrays en objets DMatrix, cela semble relativement simple. Cependant, je suis susceptible d'avoir plusieurs lots de données par travailleur. Existe-t-il un moyen propre d'appeler le train plusieurs fois avec plus de données dÚs qu'elles sont disponibles ? Je suis préoccupé par les commentaires sur ces lignes:

# 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)

Doit-on attendre que toutes les données arrivent avant de commencer l'entraßnement ?

Exemple de jeu de données / problÚme

En supposant que tout ce qui précÚde est correct, existe-t-il un exemple standard de formation distribuée que les gens utilisent pour la démonstration ?

Il n'est pas nécessaire de démarrer ptracker.

  • Tracker n'a besoin d'ĂȘtre dĂ©marrĂ© qu'Ă  un seul endroit, probablement sur le planificateur (pilote), il n'a pas de travail lourd en donnĂ©es et ne sert qu'Ă  connecter les travaux.
  • Les arguments env peuvent ĂȘtre passĂ©s en tant que kwargs dans rabit.init
  • Étant donnĂ© que l'amĂ©lioration de l'arbre est un algorithme par lots, nous devons attendre que toutes les donnĂ©es soient ingĂ©rĂ©es avant de commencer l'entraĂźnement.

    • Notez cependant que chaque travailleur n'a besoin de prendre qu'un fragment (sous-ensemble de lignes) de donnĂ©es.

    • IdĂ©alement, nous devrions utiliser l'interface d'itĂ©ration de donnĂ©es pour transmettre les donnĂ©es Ă  DMatrix sous forme de mini-lot, de sorte que l'ensemble de donnĂ©es complet n'ait pas Ă  rester en mĂ©moire.

    • Cela se fait via https://github.com/dmlc/xgboost/blob/master/include/xgboost/c_api.h#L117 , qui n'ont pas encore de wrapper python.

    • Pour la premiĂšre solution, je recommanderais de passer directement par array

J'ai eu un peu de temps pour jouer avec ça ce matin. Résultats ici : https://github.com/mrocklin/dask-xgboost

Jusqu'à présent, il ne gÚre que l'apprentissage distribué d'un seul ensemble de données en mémoire. Quelques questions se sont posées :

  1. Quelle est la meilleure façon de sérialiser et de faire circuler des objets DMatrix ?
  2. Quelle est la meilleure façon de sérialiser et de renvoyer un résultat Booster ?
  3. Comment les variables d'environnement rĂ©pertoriĂ©es ci-dessus correspondent-elles aux arguments dans rabit.init ? Quelle est prĂ©cisĂ©ment la forme attendue des entrĂ©es de rabit.init ? Passer le rĂ©sultat de slave_envs() Ă  rabit.init ne fonctionnera Ă©videmment pas car il attend une liste. Devrions-nous convertir chaque nom de clĂ© en --key , en supprimant peut-ĂȘtre le prĂ©fixe DMLC et en le convertissant en minuscules ?
  4. Existe-t-il un bon moyen de tester l'exactitude? Comment comparer deux objets Booster ? Doit-on s'attendre Ă  ce que la formation distribuĂ©e produise exactement le mĂȘme rĂ©sultat et la formation sĂ©quentielle ?
  • Normalement, vous ne sĂ©rialisez pas DMatrix, cela ressemble plus Ă  un dĂ©tenteur de donnĂ©es de temps de formation, je suppose que les donnĂ©es sont transmises et partagĂ©es par dask (tableau/dataframe), puis transmises Ă  xgboost

    • Nous pouvons explorer de meilleures façons de transmettre des donnĂ©es autrement que directement via un tableau en mĂ©moire, Ă©ventuellement en exposant un itĂ©rateur de donnĂ©es Ă  xgboost

  • Vous pouvez dĂ©caper Booster, tant que xgboost est installĂ© des deux cĂŽtĂ©s.
  • DĂ©solĂ© de ne pas avoir expliquĂ© comment les choses sont passĂ©es, ça devrait ĂȘtre
rabit.init(['DMLC_KEY1=VALUE1', 'DMLC_KEY2=VALUE2']
  • Normalement, le booster formĂ© Ă  partir d'une machine distribuĂ©e et d'une seule machine n'est pas le mĂȘme, mais voici quelques points Ă  vĂ©rifier

    • Le rappel renvoyĂ© par tous les travailleurs doit ĂȘtre identique

    • À la recherche de l'erreur de validation prĂ©dictive, elle devrait ĂȘtre Ă  peu prĂšs aussi faible que le cas d'une seule machine

Deux autres questions généralement sur la façon dont cela est utilisé (je n'ai aucune expérience avec XGBoost et seulement une petite expérience avec l'apprentissage automatique, veuillez pardonner mon ignorance).

  1. Est-il raisonnable d'utiliser plusieurs travailleurs sur les mĂȘmes donnĂ©es d'entrĂ©e ? (XGBoost est liĂ© au calcul ?)
  2. Si nous opérons sur des ensembles de données plus volumineux, dois-je faire quelque chose de spécial pour dire à chaque travailleur XGBoost que ses données diffÚrent de celles de ses pairs ?

Quel cas d'utilisation est le plus courant ?

Chaque travail doit fonctionner sur une partition de donnĂ©es diffĂ©rente (par lignes), ils ne doivent PAS regarder les mĂȘmes donnĂ©es d'entrĂ©e.

  • Si les donnĂ©es ne sont pas assez volumineuses, une version multithread devrait suffire
  • Chaque travail collectera des statistiques sĂ©parĂ©ment sur sa partition et se synchronisera les uns avec les autres

Cela correspond normalement à l'opération mapPartition dans des frameworks comme spark/flink

Disons que mon ensemble de données a 8 lignes, 4 colonnes, si nous commençons deux travailleurs

  • travailleur 0 lit Ă  partir de la ligne 0-3
  • travailleur 1 lit de la ligne 4 Ă  7

OK, ce qu'il y a maintenant est un peu plus propre. Ce serait bien si nous avions la possibilité de consommer les résultats tels qu'ils ont été générés sur chaque travailleur, mais nous avons travaillé autour de cela pour l'instant. Voici la solution actuelle :

  1. Persistez le tableau dask ou la trame de données sur le cluster, attendez qu'il se termine
  2. Trouver oĂč chaque morceau/partition s'est terminĂ©
  3. Dites à chaque travailleur de concaténer exactement ces morceaux/partitions et de s'entraßner sur eux

Cette solution semble gérable, mais n'est pas idéale. Ce serait pratique si xgboost-python pouvait accepter les résultats au fur et à mesure qu'ils arrivaient. Cependant, je pense que la prochaine chose à faire est de l'essayer dans la pratique.

Je vais chercher sur internet des exemples. Si quelqu'un a un problÚme artificiel que je peux facilement générer avec l'API numpy ou pandas, ce serait le bienvenu. En attendant, voici un exemple trivial sur mon ordinateur portable avec des données aléatoires :

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>

Le code pertinent est ici si quelqu'un veut jeter un coup d'Ɠil : https://github.com/mrocklin/dask-xgboost/blob/master/dask_xgboost/core.py

Comme je l'ai dit, je suis nouveau sur XGBoost, donc il me manque probablement des choses.

un exemple de jouet typique Ă  essayer est dans https://github.com/dmlc/xgboost/tree/master/demo/data
Il est cependant au format libsvm et nécessite un peu d'analyse pour le mettre dans numpy

Quelque chose de plus grand (pour lequel vous auriez réellement besoin d'un cluster) ? Ou existe-t-il un moyen standard de générer un ensemble de données de taille arbitraire ?

Ou, peut-ĂȘtre une meilleure question est : "Qu'est-ce que vous (ou quelqu'un d'autre lisant ce numĂ©ro) aimeriez voir ici ?"

Construire prévoir maintenant. Si je ramÚne le modÚle à un travailleur (en passant par le processus de pickle/unpickle) et que j'appelle ensuite bst.predict sur certaines données, j'obtiens l'erreur suivante :

Doing rabit call after Finalize

Mon hypothÚse était qu'à ce stade, le modÚle est autonome et n'a plus besoin d'utiliser rabit. Cela semble fonctionner correctement sur la machine cliente. Des idées pourquoi je pourrais recevoir cette erreur lors de l'appel predict ?

Une partie de prĂ©dire utilise encore rabit, principalement parce que le prĂ©dicteur utilise toujours l'apprenant avec certaines routines d'initialisation partagĂ©es avec la formation. Finalement, cela devrait ĂȘtre corrigĂ©, mais c'est le cas pour l'instant.

Je pense que tant que cela fonctionne bien pour l'ensemble de données commun, c'est un point de départ intéressant.

Il y a de toute façon des raisons d'utiliser un cluster pour les donnĂ©es moyennes (facilitĂ© de planification dans l'environnement de cluster), certains utilisateurs de pyspark pourraient ĂȘtre intĂ©ressĂ©s Ă  l'essayer si nous en faisons un peu la publicitĂ©

Tester sur l'ensemble de donnĂ©es qui compte vraiment Ă©tait difficile, par exemple (essayez 1 ensemble de donnĂ©es avec 1 milliard de lignes). Kaggle pourrait ĂȘtre un grand ensemble de donnĂ©es qui pourrait ĂȘtre pertinent, soit environ 10 millions.

Ce référentiel montre des expériences sur l'ensemble de données des compagnies aériennes, qui, je pense, se compose de dizaines de millions de lignes et de dizaines de colonnes (mille aprÚs un codage à chaud?) Pour leur référence, il semble qu'ils aient pris un échantillon de 100 000 lignes et généré artificiellement ensembles de données plus grands de cet échantillon. Nous pourrions probablement augmenter cela si nécessaire.

Voici un exemple utilisant ces donnĂ©es avec pandas et xgboost sur un seul cƓur. Toute recommandation sur la prĂ©paration des donnĂ©es, les paramĂštres ou la maniĂšre de le faire correctement serait la bienvenue.

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

Quoi qu'il en soit, voici une option. L'ensemble de donnĂ©es des compagnies aĂ©riennes semble bien connu et peut ĂȘtre trop volumineux dans la pratique. Encore une fois, l'apprentissage automatique n'est pas ma spĂ©cialitĂ©, donc je ne sais pas si c'est appropriĂ© ou non.

cc @TomAugspurger , qui semble ĂȘtre le genre de gars qui pourrait avoir des idĂ©es Ă  ce sujet.

En ce qui concerne Dask et prédire, je peux toujours configurer à nouveau rabit. Cela semble un peu impur, car cela oblige à évaluer plutÎt que de garder les choses paresseuses. Mais ce n'est pas un bloqueur sérieux à utiliser.

Vous rencontrez des problÚmes avec la prévision. Deux questions:

  1. Puis-je appeler Booster.predict plusieurs fois au cours de la mĂȘme session de rabit ?
  2. Puis-je appeler rabit.init , Booster.predict et rabit.finalize sur des threads séparés ?

Actuellement, je crée un nouveau tracker et j'appelle rabit.init sur le thread principal du travailleur. Cela fonctionne bien. Cependant, lorsque j'appelle Booster.predict dans les threads de travail (chaque travailleur dask maintient un pool de threads pour le calcul), j'obtiens des erreurs comme Doing rabit call after Finalize . Des recommandations ?

Une partie de prĂ©dire utilise encore rabit, principalement parce que le prĂ©dicteur utilise toujours l'apprenant avec certaines routines d'initialisation partagĂ©es avec la formation. Finalement, cela devrait ĂȘtre corrigĂ©, mais c'est le cas pour l'instant.

Je suis curieux Ă  ce sujet. AprĂšs avoir sĂ©rialisĂ©-transfĂ©rĂ©-dĂ©sĂ©rialisĂ© le modĂšle formĂ© d'un travailleur Ă  ma machine cliente, il semble fonctionner correctement sur des donnĂ©es normales, mĂȘme s'il n'y a pas de rĂ©seau rabit. Il semble qu'un modĂšle entraĂźnĂ© avec Rabit puisse ĂȘtre utilisĂ© pour prĂ©dire des donnĂ©es sans rabit. Cela semble Ă©galement ĂȘtre nĂ©cessaire en production. Pouvez-vous en dire plus sur les contraintes liĂ©es Ă  l'utilisation d'un modĂšle entraĂźnĂ© par rabit ici ?

Exemple de jeu de données / problÚme
En supposant que tout ce qui précÚde est correct, existe-t-il un exemple standard de formation distribuée que les gens utilisent pour la démonstration ?

Je serais bien de reproduire les résultats de cette expérience:

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

avec la nouvelle option binning + fast hist de XGBoost (#1950), il devrait ĂȘtre possible d'obtenir des rĂ©sultats similaires.

un exemple de jouet typique Ă  essayer est dans https://github.com/dmlc/xgboost/tree/master/demo/data
Il est cependant au format libsvm et nécessite un peu d'analyse pour le mettre dans numpy

Vous pourriez ĂȘtre intĂ©ressĂ© par ce PR dans sklearn : https://github.com/scikit-learn/scikit-learn/pull/935

@mrocklin Il n'y a aucune contrainte sur la rĂ©utilisation du modĂšle. Ainsi, le modĂšle formĂ© en version distribuĂ©e peut ĂȘtre utilisĂ© en version sĂ©rie. C'est juste que la limitation actuelle du prĂ©dicteur (lorsqu'il est compilĂ© avec rabit) a une fonction mixte avec la fonction d'entraĂźnement (donc l'appel de rabit s'est produit).

Maintenant que vous le dites, je pense que nous pourrions avoir une solution au problÚme. Faites simplement un rabit.init (sans rien transmettre, et faites croire au prédicteur qu'il est le seul travailleur) avant que la prédiction ne résolve le problÚme

Oui. En effet cela résout le problÚme. dask-xgboost prend désormais en charge la prédiction : https://github.com/mrocklin/dask-xgboost/commit/827a03d96977cda8d104899c9f42f52dac446165

Merci pour la solution de contournement @tqchen !

Voici un flux de travail avec dask.dataframe et xgboost sur un petit échantillon de l'ensemble de données des compagnies aériennes sur mon ordinateur portable local. Est-ce que cela semble OK pour tout le monde ? Y a-t-il des éléments API de XGBoost qui me manquent ici ?

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

Mon objectif Ă  court terme est d'Ă©crire un court article de blog Ă  ce sujet afin que quelqu'un d'autre avec plus d'expĂ©rience avec XGBoost et avec plus de temps vienne adopter ce projet et le faire avancer. (Comme tout le monde ici, je travaille sur quelques autres projets comme celui-ci en mĂȘme temps.)

Je suis partisan de l'ensemble de données des compagnies aériennes simplement parce que je l'ai déjà dans un compartiment S3. Je conviens cependant que l'ensemble de données Criteo permettrait une meilleure démonstration à grande échelle.

Je ne sais toujours pas quels paramÚtres utiliser ni comment juger du résultat. Pour les paramÚtres, je peux utiliser l'expérience de @szilard ici . Existe-t-il un bon moyen de juger les prédictions ? Par exemple, recherchons-nous predictions > 0.5 pour correspondre à labels_test ?

La façon la plus courante d'Ă©valuer les performances prĂ©dictives pour la classification binaire (en particulier dans les contextes de recherche ou de compĂ©tition) consiste peut-ĂȘtre Ă  utiliser l'aire sous la courbe ROC (AUC), bien que dans les applications du monde rĂ©el, il convient d'utiliser des mĂ©triques alignĂ©es sur les valeurs « mĂ©tier ». rĂ©alisĂ© Ă  l'aide des modĂšles.

Par exemple, recherchons-nous des prédictions > 0,5 pour correspondre à labels_test ?

Oui. Si vous prenez la moyenne de cela sur l'ensemble de test, c'est la précision du test. Mais il est probable que le jeu de données soit déséquilibré (beaucoup plus d'absence de clic que de clics). Dans ce cas, le score ROC AUC est une meilleure mesure.

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

en supposant que predictions est un tableau 1D de probabilités positives estimées par le modÚle pour chaque ligne de l'ensemble de test.

@mrocklin Une question de suivi, dask autorise-t-il les travaux de travail multithread ? Je sais que ce n'est pas trĂšs pertinent pour python Ă  cause de GIL. Mais xgboost peut permettre une formation multithread par travailleur tout en se coordonnant les uns avec les autres de maniĂšre distribuĂ©e. Nous devons toujours dĂ©finir les arguments nthread de xgboost comme Ă©tant le nombre de cƓurs de travail de ce travailleur

La réponse courte est "oui". Dask est principalement utilisé avec des projets tels que NumPy, Pandas, SKLearn et d'autres qui ne sont principalement que du code C et Fortran, enveloppés de Python. Le GIL n'affecte pas ces bibliothÚques. Certaines personnes utilisent Dask pour des applications similaires au PySpark RDD (voir dask.bag ) et seront affectées. Ce groupe est cependant minoritaire.

Alors oui, Dask autorise les tĂąches multi-thread. Comment dire Ă  XGBoost d'utiliser plusieurs threads ? Dans mes expĂ©riences jusqu'Ă  prĂ©sent, je constate une utilisation Ă©levĂ©e du processeur sans modifier aucun paramĂštre, alors peut-ĂȘtre que tout fonctionne bien par dĂ©faut ?

XGBoost utilise le multi-thread par dĂ©faut et utilisera tous les threads cpu disponibles sur la machine (au lieu de sur ce travailleur) si nthread n'est pas dĂ©fini. Cela peut crĂ©er une condition de concurrence lorsque plusieurs travailleurs sont affectĂ©s Ă  la mĂȘme machine.

Il est donc toujours bon de dĂ©finir le paramĂštre nthread sur le nombre maximum de cƓurs que le travailleur est autorisĂ© Ă  utiliser. Habituellement, une bonne pratique consiste Ă  utiliser environ 4 threads par travailleur

Bien sĂ»r, devrait ĂȘtre accompli dans
https://github.com/mrocklin/dask-xgboost/commit/c22d066b67c78710d5ad99b8620edc55182adc8f

Le lundi 20 fĂ©vrier 2017 Ă  18h31, Tianqi Chen [email protected]
a Ă©crit:

XGBoost utilise le multithread par défaut et utilisera tout le processeur disponible
threads sur la machine (au lieu de sur ce travailleur) si nthread n'est pas défini.
Cela peut crĂ©er une condition de concurrence lorsque plusieurs travailleurs sont affectĂ©s au mĂȘme
machine.

Il est donc toujours bon de définir le paramÚtre nthread sur le nombre maximum de
noyaux que le travailleur est autorisé à utiliser. Habituellement, une bonne pratique est d'utiliser autour de dire
4 fils par travailleur

—
Vous recevez ceci parce que vous avez été mentionné.
RĂ©pondez directement Ă  cet e-mail, consultez-le sur GitHub
https://github.com/dmlc/xgboost/issues/2032#issuecomment-281205747 , ou muet
le fil
https://github.com/notifications/unsubscribe-auth/AASszPELRoeIvqEzyJhkKumIs-vd0PHiks5reiJngaJpZM4L_PXa
.

Carnet : https://gist.github.com/19c89d78e34437e061876a9872f4d2df
Capture d'Ă©cran courte (six minutes) : https://youtu.be/Cc4E-PdDSro

Les commentaires critiques sont les bienvenus. Encore une fois, veuillez pardonner mon ignorance dans ce domaine.

@mrocklin super dĂ©mo ! Je pense que les performances d'exĂ©cution (et Ă©ventuellement l'utilisation de la mĂ©moire) pourraient ĂȘtre grandement amĂ©liorĂ©es en utilisant 'tree_method': 'hist', 'grow_policy': 'lossguide' dans le param dict.

Merci @ogrisel. Avec ces paramĂštres, le temps d'entraĂźnement passe de six minutes Ă  une minute. L'utilisation de la mĂ©moire semble rester Ă  peu prĂšs la mĂȘme.

OK, revenons Ă  cela. Y a-t-il des opĂ©rations XGBoost autres que former et prĂ©dire que nous devrions mettre en Ɠuvre ?

@tqchen ou @ogrisel si l'un d'entre vous a le temps de parcourir l'implĂ©mentation sur https://github.com/mrocklin/dask-xgboost/blob/master/dask_xgboost/core.py , je vous en serais reconnaissant. Je comprends cependant que la recherche dans une base de code Ă©trangĂšre n'est pas toujours en tĂȘte des listes de prioritĂ©s.

Si tout va bien, j'ajouterai un peu plus au README, publierai sur PyPI, et nous pourrons probablement fermer ce problĂšme.

Je pense que seulement former et prĂ©dire doivent ĂȘtre distribuĂ©s. D'autres choses n'ont pas Ă  ĂȘtre distribuĂ©es puisqu'elles ne rĂ©pondent pas sur le jeu de donnĂ©es

J'ai poussé dask-xgboost vers PyPI et l'ai déplacé vers https://github.com/dask/dask-xgboost

Merci @tqchen et @ogrisel pour votre aide ici. La collaboration a rendu cela relativement facile.

Je serais heureux d'aider les gens s'ils voulaient faire des benchmarks. D'ici lĂ , fermeture.

Cette page vous a été utile?
0 / 5 - 0 notes