Xgboost: Computación distribuida con Dask

Creado en 13 feb. 2017  ·  46Comentarios  ·  Fuente: dmlc/xgboost

Hola, soy autor de Dask , una biblioteca para computación paralela y distribuida en Python. Tengo curiosidad por saber si hay interés dentro de esta comunidad para colaborar en la distribución de XGBoost en Dask, ya sea para capacitación paralela o para ETL.

Probablemente hay dos componentes de Dask que son relevantes para este proyecto:

  1. Un sistema genérico para computación paralela y distribuida, basado en la programación de tareas dinámicas arbitrarias. Las API relevantes aquí son probablemente dask.delayed y concurrent.futures
  2. Un subconjunto paralelo y distribuido de la API de Pandas, dask.dataframe , útil para la ingeniería de características y el preprocesamiento de datos. Esto no implementa toda la API de Pandas, pero se acerca bastante.

¿Hay interés en colaborar aquí?

Comentario más útil

Cuaderno: https://gist.github.com/19c89d78e34437e061876a9872f4d2df
Screencast corto (seis minutos): https://youtu.be/Cc4E-PdDSro

La retroalimentación crítica es muy bienvenida. Nuevamente, perdonen mi ignorancia en este campo.

Todos 46 comentarios

@mrocklin Pensé que Dask tiene integraciones con sklearn. ¿Le echaste un vistazo a nuestro envoltorio sklearn para ver si funciona con eso?

La integración significativa con un sistema distribuido generalmente debe realizarse a nivel de algoritmo en lugar de a nivel de biblioteca. Hay algunas formas en las que SKLearn y Dask pueden ayudarse mutuamente, sí, pero no son particularmente profundas.

El marco de datos Dask sería un buen comienzo. En nuestra base de código, tenemos una verificación para el marco de datos de pandas. Ahí podría ser donde el marco de datos dask encajaría como un comienzo.

Entonces, ¿qué sucede si alguien llega con un marco de datos dask de varios terabytes? ¿Simplemente lo convierte a Pandas y continúa? ¿O hay alguna manera de paralelizar XGBoost de forma inteligente en un clúster, apuntando a los diversos marcos de datos de pandas que componen un marco de datos dask?

¿Los usuarios pueden especificar el tamaño del lote? Me imagino que los usuarios pueden beneficiarse a través de shared_fit.

cc @tqchen que está más familiarizado con la parte distribuida del código.

La versión distribuida de xgboost se puede conectar a un lanzador de trabajos distribuidos, lo ideal es obtener la alimentación de la partición de datos en xgboost y luego continuar.

@mrocklin Creo que la parte más relevante es el módulo xgboost-spark y xgboost-flink, que incorpora xgboost en la función mapPartition de spark/flink. Supongo que habría algo similar en Dask.

El requisito del lado de xgboost es que XGBoost maneje la conexión entre procesos por rabit, y deberá iniciar un rastreador (que conecta cada trabajo) desde el lado del cliente.

consulte el código relevante en https://github.com/dmlc/xgboost/blob/master/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala#L112

Rabit está diseñado para integrarse en otro sistema distribuido, por lo que creo que no sería demasiado difícil hacer el ajuste en el lado de Python.

Lanzar otros sistemas distribuidos desde Dask suele ser bastante factible. ¿Cómo se mueven los datos del sistema distribuido de alojamiento (spark/flink/dask) a xg-boost? ¿O es esto para entrenamiento distribuido en datos pequeños?

Más concretamente, espero construir un sistema de la siguiente manera:

  • En cada trabajador dask inicio un servidor Rabit. Dask brinda a estos servidores Rabit suficiente información para encontrarse entre sí.
  • Creo un estado XGBoost local en cada trabajador que representa el modelo de entrenamiento actual
  • Repetidamente alimento este objeto por trabajador pandas marcos de datos o matrices numpy
  • Escucho alguna señal de XGBoost que me dice que me detenga

¿Coincide esto con sus expectativas? ¿Es fácil para usted señalarme la API de Python relevante?

Sí, consulte la información relevante aquí https://github.com/dmlc/xgboost/blob/master/tests/distributed/ para la API de python.

Lo que deberá hacer adicionalmente es iniciar un rastreador de rabit en el lado del conductor (probablemente sea el lugar que conduce dask), esto se hace en el script dmlc-submit aquí https://github.com/dmlc/dmlc-core /árbol/maestro/rastreador/dmlc_tracker

OK, completando mi esquema de antes:

Antes de ejecutar cualquier código XGBoost, configuramos una red Rabit

En el nodo controlador/programador iniciamos un rastreador de conejos

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

También puedo pasar por un proceso similar para iniciar un PSTracker . ¿Debería estar en la misma máquina centralizada o debería estar en otro lugar dentro de la red? ¿Debería haber algunos de estos? ¿Debería ser configurable por el usuario?

Finalmente, hago que mi rastreador (¿y ptrackers?) se unan a la red rabit y bloqueen.

rabit.join()  # join network

En los nodos de trabajo, necesito volcar estas variables de entorno (que moveré a través de los canales de escritorio normales) en el entorno local. Entonces solo llamar a xgboost.rabit.init() debería ser suficiente

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

Mirando el código de Rabit, parece que las variables de entorno son la única forma de proporcionar esta información. ¿Puedes verificar esto? ¿Hay alguna manera de proporcionar información de host/puerto de rastreador como entradas directas?

Capacitación

Luego convierto mis arreglos numpy / marcos de datos pandas / arreglos dispersos scipy en objetos DMatrix, esto parece relativamente sencillo. Sin embargo, es probable que tenga varios lotes de datos por trabajador. ¿Existe una forma limpia de llamar al tren varias veces con más datos a medida que estén disponibles? Me preocupan los comentarios en estas líneas:

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

¿Tenemos que esperar a que lleguen todos los datos antes de empezar a entrenar?

Ejemplo de conjunto de datos/problema

Suponiendo que tengo todo lo anterior correcto, ¿existe un ejemplo de capacitación distribuida estándar que la gente use para la demostración?

No es necesario iniciar pstracker.

  • El rastreador solo debe iniciarse en un lugar, probablemente en el programador (controlador), no tiene un trabajo pesado de datos y solo sirve para conectar los trabajos.
  • Los argumentos env se pueden pasar como kwargs en rabit.init
  • Dado que el impulso del árbol es un algoritmo por lotes, debemos esperar a que se ingieran todos los datos antes de comenzar a entrenar.

    • Sin embargo, tenga en cuenta que cada trabajador solo necesita tomar un fragmento (subconjunto de filas) de datos.

    • Idealmente, deberíamos usar la interfaz de iteración de datos para pasar los datos a DMatrix en forma de mini lotes, de modo que el conjunto de datos completo no tenga que permanecer en la memoria.

    • Esto se hace a través de https://github.com/dmlc/xgboost/blob/master/include/xgboost/c_api.h#L117 , que aún no tiene un contenedor de python.

    • Para la primera solución, recomendaría pasar directamente por matriz

Tuve algo de tiempo para jugar con esto esta mañana. Resultados aquí: https://github.com/mrocklin/dask-xgboost

Hasta ahora, solo maneja el aprendizaje distribuido de un solo conjunto de datos en memoria. Surgieron algunas preguntas:

  1. ¿Cuál es la mejor manera de serializar y pasar objetos DMatrix?
  2. ¿Cuál es la mejor manera de serializar y devolver un resultado de Booster?
  3. ¿Cómo se asignan las variables de entorno enumeradas anteriormente a argumentos en rabit.init ? ¿Cuál es exactamente la forma esperada de las entradas de rabit.init ? Pasar el resultado de slave_envs() a rabit.init obviamente no funcionará porque espera una lista. ¿Deberíamos convertir cada nombre clave a --key , tal vez eliminando el prefijo DMLC y convirtiéndolo a minúsculas?
  4. ¿Hay una buena manera de probar la corrección? ¿Cómo comparamos dos objetos Booster? ¿Deberíamos esperar que el entrenamiento distribuido produzca exactamente el mismo resultado y el entrenamiento secuencial?
  • Normalmente no serializa DMatrix, es más como un contenedor de datos de tiempo de entrenamiento, supongo que los datos se pasan y comparten por dask (matriz/marco de datos), luego se pasan a xgboost

    • Podemos explorar mejores formas de pasar datos que no sean directamente a través de la matriz en memoria, posiblemente exponiendo un iterador de datos a xgboost

  • Puede encurtir Booster, siempre que xgboost esté instalado en ambos lados.
  • Lo siento por no elaborar cómo se pasan las cosas, debería ser
rabit.init(['DMLC_KEY1=VALUE1', 'DMLC_KEY2=VALUE2']
  • Normalmente, el refuerzo entrenado desde una sola máquina distribuida no es el mismo, pero aquí hay algunas cosas que debe verificar

    • El refuerzo devuelto por todos los trabajadores debe ser idéntico

    • Buscando el error de validación predictivo, debería ser tan bajo como el caso de una sola máquina

Dos preguntas más en general sobre cómo se usa esto (no tengo experiencia con XGBoost y solo un poco de experiencia con el aprendizaje automático, perdone mi ignorancia).

  1. ¿Es razonable utilizar varios trabajadores en los mismos datos de entrada? (¿XGBoost está vinculado computacionalmente?)
  2. Si operamos en conjuntos de datos más grandes, ¿tengo que hacer algo especial para decirle a cada trabajador de XGBoost que sus datos difieren de los de sus compañeros?

¿Qué caso de uso es más común?

Cada trabajo debe funcionar en una partición diferente de datos (por filas), NO deben mirar los mismos datos de entrada.

  • Si los datos no son lo suficientemente grandes, una versión de subprocesos múltiples debería ser suficiente
  • Cada trabajo recopilará estadísticas por separado en su partición y se sincronizará entre sí.

Esto normalmente corresponde a la operación mapPartition en marcos como spark/flink

Digamos que mi conjunto de datos tiene 8 filas, 4 columnas, si comenzamos con dos trabajadores

  • trabajador 0 lee de la fila 0-3
  • el trabajador 1 lee de la fila 4 -7

Bien, lo que hay ahora está un poco más limpio. Sería bueno si tuviéramos alguna capacidad para consumir los resultados a medida que se generaron en cada trabajador, pero por ahora lo hemos solucionado. Aquí está la solución actual:

  1. Persista la matriz dask o el marco de datos en el clúster, espere a que termine
  2. Encuentre dónde terminó cada fragmento/partición
  3. Dígale a cada trabajador que concatene exactamente esos fragmentos/particiones y entrene en ellos

Esta solución parece ser manejable, pero no es ideal. Sería conveniente que xgboost-python pudiera aceptar los resultados tal como llegaron. Sin embargo, creo que lo siguiente que debe hacer es probarlo en la práctica.

Voy a buscar en Internet ejemplos. Si alguien tiene un problema artificial que puedo generar fácilmente con la API numpy o pandas, sería bienvenido. Hasta entonces, aquí hay un ejemplo trivial en mi computadora portátil con datos aleatorios:

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>

El código relevante está aquí si alguien quiere echar un vistazo: https://github.com/mrocklin/dask-xgboost/blob/master/dask_xgboost/core.py

Como dije, soy nuevo en XGBoost, así que probablemente me esté perdiendo cosas.

un ejemplo típico de juguete para probar está en https://github.com/dmlc/xgboost/tree/master/demo/data
Sin embargo, está en formato libsvm y necesita un poco de análisis para convertirlo en numpy

¿Algo más grande (para lo cual realmente necesitarías un clúster)? ¿O hay una forma estándar de generar un conjunto de datos de tamaño arbitrario?

O, tal vez, una mejor pregunta es: "¿Qué le gustaría ver aquí a usted (oa cualquier otra persona que lea este número)?"

Edificio predecir ahora. Si vuelvo a mover el modelo a un trabajador (pasando por el proceso de encurtido/desencurtido) y luego llamo a bst.predict en algunos datos, obtengo el siguiente error:

Doing rabit call after Finalize

Mi suposición era que, en este punto, el modelo es autónomo y ya no necesita usar rabit. Parece funcionar bien en la máquina cliente. ¿Alguna idea de por qué podría recibir este error al llamar a predict ?

Una parte de la predicción todavía usa rabit, principalmente porque el predictor todavía usa el alumno con algunas rutinas de inicialización que se comparten con el entrenamiento. Eventualmente, esto debería arreglarse, pero este es el caso por ahora.

Creo que mientras funcione bien para el conjunto de datos común, es un punto de partida interesante.

De todos modos, hay razones para usar un clúster para datos medianos (facilidad de programación en entorno de clúster), algunos de los usuarios de pyspark podrían estar interesados ​​​​en probarlo si lo publicitamos un poco

Probar el conjunto de datos que realmente importa fue difícil, por ejemplo (pruebe 1 conjunto de datos con mil millones de filas). Kaggle podría tener un gran conjunto de datos que podría ser relevante, alrededor de 10 millones.

Este repositorio muestra experimentos contra el conjunto de datos de las aerolíneas, que creo que está en decenas de millones de filas y decenas de columnas (¿miles después de una codificación en caliente?) Para su punto de referencia, parece que tomaron una muestra de 100k filas y generaron artificialmente conjuntos de datos más grandes de esta muestra. Presumiblemente, podríamos escalar esto si fuera necesario.

Aquí hay un ejemplo que usa estos datos con pandas y xgboost en un solo núcleo. Cualquier recomendación sobre la preparación de datos, los parámetros o cómo hacerlo correctamente sería bienvenida.

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

De todos modos, aquí hay una opción. El conjunto de datos de las aerolíneas parece bien conocido y puede ser inconvenientemente grande en la práctica. Sin embargo, una vez más, el aprendizaje automático no es mi especialidad, por lo que no sé si esto es apropiado o no.

cc @TomAugspurger , que parece el tipo de persona que podría tener pensamientos sobre esto.

Con respecto a Dask y predecir, siempre puedo configurar rabit nuevamente. Sin embargo, esto se siente un poco sucio porque obliga a la evaluación en lugar de mantener las cosas perezosas. Pero este no es un bloqueador serio para usar.

Me encontré con algunos problemas con predict. Dos preguntas:

  1. ¿Puedo llamar a Booster.predict varias veces dentro de la misma sesión de rabit?
  2. ¿Puedo llamar a rabit.init , Booster.predict y rabit.finalize en subprocesos separados?

Actualmente, creo un nuevo rastreador y llamo a rabit.init en el hilo principal del trabajador. Esto funciona bien. Sin embargo, cuando llamo a Booster.predict en subprocesos de trabajo (cada trabajador de dask mantiene un grupo de subprocesos para el cálculo) obtengo errores como Doing rabit call after Finalize . ¿Alguna recomendación?

Una parte de la predicción todavía usa rabit, principalmente porque el predictor todavía usa el alumno con algunas rutinas de inicialización que se comparten con el entrenamiento. Eventualmente, esto debería arreglarse, pero este es el caso por ahora.

Tengo curiosidad sobre esto. Después de serializar, transferir y deserializar el modelo entrenado de un trabajador a mi máquina cliente, parece funcionar bien con datos normales aunque no haya una red rabit. Parece que un modelo entrenado con Rabit se puede usar para predecir datos sin rabit. Esto también parece que sería necesario en la producción. ¿Puede decir más sobre las limitaciones de usar un modelo entrenado en conejos aquí?

Ejemplo de conjunto de datos/problema
Suponiendo que tengo todo lo anterior correcto, ¿existe un ejemplo de capacitación distribuida estándar que la gente use para la demostración?

Sería bueno reproducir los resultados de este experimento:

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

con la nueva opción binning + hist rápido de XGBoost (#1950), debería ser posible obtener resultados similares.

un ejemplo típico de juguete para probar está en https://github.com/dmlc/xgboost/tree/master/demo/data
Sin embargo, está en formato libsvm y necesita un poco de análisis para convertirlo en numpy

Puede que le interese este PR en sklearn: https://github.com/scikit-learn/scikit-learn/pull/935

@mrocklin No hay restricciones para reutilizar el modelo. Por lo tanto, el modelo entrenado en versión distribuida se puede usar en versión en serie. Es solo que la limitación actual del predictor (cuando se compila con rabit) tiene una función mixta con la función de entrenamiento (por lo que sucedió la llamada rabit).

Ahora que lo dices, creo que podríamos tener una solución para el problema. Simplemente haga un rabit.init (sin pasar nada, y haga que el predictor piense que es el único trabajador) antes de que la predicción resuelva el problema

Si. Efectivamente eso resuelve el problema. dask-xgboost ahora admite predicción: https://github.com/mrocklin/dask-xgboost/commit/827a03d96977cda8d104899c9f42f52dac446165

¡Gracias por la solución @tqchen !

Aquí hay un flujo de trabajo con dask.dataframe y xgboost en una pequeña muestra del conjunto de datos de aerolíneas en mi computadora portátil local. ¿Esto les parece bien a todos? ¿Hay elementos API de XGBoost que me faltan aquí?

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

Mi objetivo a corto plazo es escribir una breve publicación de blog sobre esto para que, con suerte, llegue alguien más con más experiencia con XGBoost y con más tiempo que adopte este proyecto y lo impulse. (Yo, como todos los demás aquí, estoy trabajando en algunos otros proyectos como este al mismo tiempo).

Soy partidario del conjunto de datos de las aerolíneas solo porque ya lo tengo en un depósito S3. Sin embargo, estoy de acuerdo en que el conjunto de datos de Criteo sería una mejor demostración a escala.

Todavía no estoy seguro de qué parámetros usar o cómo juzgar el resultado. Para los parámetros, puedo usar el experimento de @szilard aquí . ¿Hay una buena manera de juzgar las predicciones? Por ejemplo, ¿estamos buscando predictions > 0.5 para que coincida con labels_test ?

Quizás la forma más común de evaluar el rendimiento predictivo para la clasificación binaria (especialmente en entornos de investigación o competencia) es usar el área bajo la curva ROC (AUC), aunque en las aplicaciones del mundo real se deben usar métricas que estén alineadas con los valores "comerciales". producido mediante el uso de los modelos.

Por ejemplo, ¿buscamos predicciones > 0,5 para que coincidan con las etiquetas_prueba?

Si. Si toma la media de eso en el conjunto de prueba, esta es la precisión de la prueba. Pero es probable que el conjunto de datos esté desequilibrado (mucha más ausencia de clics que clics). En ese caso, la puntuación ROC AUC es una mejor métrica.

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

suponiendo que predictions es una matriz 1D de probabilidades positivas estimadas por el modelo para cada fila del conjunto de prueba.

@mrocklin Una pregunta de seguimiento, ¿dask permite trabajos de trabajadores de subprocesos múltiples? Sé que esto no es muy relevante para Python debido a GIL. Pero xgboost puede permitir la capacitación de subprocesos múltiples por trabajador mientras aún se coordinan entre sí de forma distribuida. Siempre debemos configurar los argumentos nthread de xgboost para que sean el número de núcleos de trabajo de ese trabajador

La respuesta corta es "sí". La mayor parte del uso de Dask es con proyectos como NumPy, Pandas, SKLearn y otros que en su mayoría son solo código C y Fortran, envueltos con Python. El GIL no afecta a estas bibliotecas. Algunas personas usan Dask para aplicaciones similares a PySpark RDD (consulte dask.bag ) y se verán afectadas. Aunque este grupo es minoritario.

Entonces sí, Dask permite tareas de subprocesos múltiples. ¿Cómo le decimos a XGBoost que use múltiples subprocesos? En mis experimentos hasta ahora, veo un alto uso de la CPU sin cambiar ningún parámetro, ¿así que tal vez todo funcione bien de manera predeterminada?

XGBoost usa subprocesos múltiples de forma predeterminada y utilizará todos los subprocesos de CPU disponibles en la máquina (en lugar de en ese trabajador) si nthread no está configurado. Esto puede crear una condición de carrera cuando se asignan varios trabajadores a la misma máquina.

Por lo tanto, siempre es bueno establecer el parámetro nthread en la cantidad máxima de núcleos que el trabajador puede usar. Por lo general, una buena práctica es usar alrededor de 4 subprocesos por trabajador.

Claro, debe lograrse en
https://github.com/mrocklin/dask-xgboost/commit/c22d066b67c78710d5ad99b8620edc55182adc8f

El lunes 20 de febrero de 2017 a las 6:31 p. m., Tianqi Chen [email protected]
escribió:

XGBoost utiliza subprocesos múltiples de forma predeterminada y utilizará toda la CPU disponible
subprocesos en la máquina (en lugar de en ese trabajador) si nthread no está configurado.
Esto puede crear una condición de carrera cuando se asignan varios trabajadores al mismo
máquina.

Por lo tanto, siempre es bueno establecer el parámetro nthread en el número máximo de
núcleos que el trabajador permitió usar. Por lo general, una buena práctica es usar alrededor de, por ejemplo,
4 hilos por trabajador


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/dmlc/xgboost/issues/2032#issuecomment-281205747 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/AASszPELRoeIvqEzyJhkKumIs-vd0PHiks5reiJngaJpZM4L_PXa
.

Cuaderno: https://gist.github.com/19c89d78e34437e061876a9872f4d2df
Screencast corto (seis minutos): https://youtu.be/Cc4E-PdDSro

La retroalimentación crítica es muy bienvenida. Nuevamente, perdonen mi ignorancia en este campo.

@mrocklin gran demostración! Creo que el rendimiento del tiempo de ejecución (y posiblemente el uso de la memoria) podría mejorarse mucho usando 'tree_method': 'hist', 'grow_policy': 'lossguide' en el dictado de parámetros.

Gracias @ogrisel. Con esos parámetros el tiempo de entrenamiento pasa de seis minutos a un minuto. Sin embargo, el uso de la memoria parece permanecer casi igual.

Bien, volviendo a esto. ¿Hay alguna operación de XGBoost además de entrenar y predecir que deberíamos implementar?

@tqchen o @ogrisel si alguno de ustedes tiene tiempo para revisar la implementación en https://github.com/mrocklin/dask-xgboost/blob/master/dask_xgboost/core.py Se lo agradecería. Sin embargo, entiendo que mirar a través de una base de código extranjera no siempre ocupa un lugar destacado en las listas de prioridades.

Si todo está bien, agregaré un poco más al LÉAME, publicaré en PyPI y luego probablemente podamos cerrar este problema.

Creo que solo entrenar y predecir necesitan ser distribuidos. No es necesario distribuir otras cosas, ya que no responden en el conjunto de datos.

Empujé dask-xgboost a PyPI y lo moví a https://github.com/dask/dask-xgboost

Gracias @tqchen y @ogrisel por su ayuda aquí. La colaboración hizo esto relativamente fácil.

Estaría feliz de ayudar a las personas si quisieran ejecutar puntos de referencia. Hasta entonces, cerrando.

¿Fue útil esta página
0 / 5 - 0 calificaciones