Xgboost: Computação distribuída com Dask

Criado em 13 fev. 2017  ·  46Comentários  ·  Fonte: dmlc/xgboost

Olá, sou autor do Dask , uma biblioteca para computação paralela e distribuída em Python. Estou curioso para saber se há interesse nesta comunidade em colaborar na distribuição do XGBoost no Dask para treinamento paralelo ou para ETL.

Existem provavelmente dois componentes do Dask que são relevantes para este projeto:

  1. Um sistema genérico para computação paralela e distribuída, construído sobre agendamento de tarefas dinâmicas arbitrárias. As APIs relevantes aqui são provavelmente dask.delayed e concurrent.futures
  2. Um subconjunto paralelo e distribuído da API do Pandas, dask.dataframe , útil para engenharia de recursos e pré-processamento de dados. Isso não implementa toda a API do Pandas, mas chega bem perto.

Há interesse em colaborar aqui?

Comentários muito úteis

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

Comentários críticos são muito bem-vindos. Mais uma vez, por favor, perdoe minha ignorância neste campo.

Todos 46 comentários

@mrocklin Achei que o Dask tem integrações com o sklearn. Você deu uma olhada no nosso wrapper sklearn para ver se ele funcionará com isso?

A integração significativa com um sistema distribuído normalmente deve ser feita no nível do algoritmo, e não no nível da biblioteca. Existem algumas maneiras pelas quais o SKLearn e o Dask podem ajudar um ao outro, sim, mas não são particularmente profundos.

Dask dataframe seria um bom começo. Em nossa base de código, temos uma verificação do dataframe do pandas. Isso pode ser onde o dataframe dask se encaixaria como um começo.

Então, o que acontece se alguém chegar com um dataframe dask de vários terabytes? Você acabou de convertê-lo em Pandas e prosseguir? Ou existe uma maneira de paralelizar o XGBoost de forma inteligente em um cluster, apontando para os vários dataframes de pandas que compõem um dataframe dask?

Os usuários podem especificar o tamanho do lote? Eu imagino que os usuários podem ser beneficiados através do parcial_fit.

cc @tqchen que está mais familiarizado com a parte distribuída do código.

A versão distribuída do xgboost pode ser conectada a um lançador de tarefas distribuído, de preferência, obtenha o feed da partição de dados no xgboost e continue.

@mrocklin Acho que a parte mais relevante é o módulo xgboost-spark e xgboost-flink, que incorpora xgboost na função mapPartition de spark/flink. Eu acho que haveria algo semelhante no Dask

O requisito do lado do xgboost é que o XGBoost lide com a conexão entre processos por rabit e precisará iniciar um rastreador (que conecta cada trabalho) do lado do cliente.

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

O Rabit foi projetado para ser incorporado em outro sistema distribuído, então acho que pode não ser muito difícil fazer o ajuste no lado do python.

O lançamento de outros sistemas distribuídos do Dask geralmente é bastante factível. Como você move dados do sistema distribuído de hospedagem (spark/flink/dask) para xg-boost? Ou isso é para treinamento distribuído em dados pequenos?

Mais concretamente, espero construir um sistema da seguinte forma:

  • Em cada trabalhador dask eu inicio um servidor Rabit. Dask fornece a esses servidores Rabit informações suficientes para se encontrarem.
  • Eu crio algum estado XGBoost local em cada trabalhador que representa o modelo de treinamento atual
  • Eu alimento repetidamente esse objeto por trabalhador pandas dataframes ou matrizes numpy
  • Eu escuto algum sinal do XGBoost que me diz para parar

Isso corresponde à sua expectativa? É fácil para você me apontar para a API Python relevante?

Sim, veja informações relevantes aqui https://github.com/dmlc/xgboost/blob/master/tests/distributed/ para API python.

O que você precisará fazer adicionalmente é iniciar um rastreador de rabit no lado do motorista (provavelmente o local que dirige o dask), isso é feito no script dmlc-submit aqui https://github.com/dmlc/dmlc-core /tree/master/tracker/dmlc_tracker

OK, preenchendo meu esboço de antes:

Antes de executar qualquer código XGBoost, configuramos uma rede Rabit

No nó do driver/scheduler, iniciamos um rastreador 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

Eu também posso passar por um processo semelhante para iniciar um PSTracker . Isso deveria estar na mesma máquina centralizada ou deveria estar em outro lugar dentro da rede? Deve haver alguns destes? Isso deve ser configurável pelo usuário?

Eventualmente eu tenho meu rastreador (e ptrackers?) entrando na rede rabit e bloqueando.

rabit.join()  # join network

Em nós de trabalho, preciso despejar essas variáveis ​​de ambiente (que passarei pelos canais dask normais) no ambiente local. Então apenas chamar xgboost.rabit.init() deve ser suficiente

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

Olhando para o código Rabit, parece que as variáveis ​​de ambiente são a única maneira de fornecer essas informações. Você pode verificar isso? Existe uma maneira de fornecer informações de host/porta do rastreador como entradas diretas?

Treinamento

Então eu converto meus arrays numpy/pandas dataframes/scipy sparse arrays em objetos DMatrix, isso parece relativamente simples. No entanto, é provável que tenha vários lotes de dados por trabalhador. Existe uma maneira limpa de chamar train várias vezes com mais dados à medida que ficam disponíveis? Estou preocupado com os comentários nestas linhas:

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

Precisamos esperar que todos os dados cheguem antes de iniciar o treinamento?

Exemplo de conjunto de dados/problema

Supondo que eu tenha tudo correto acima, existe um exemplo de treinamento distribuído padrão que as pessoas usam para demonstração?

Não há necessidade de iniciar o pstracker.

  • Tracker só precisa ser iniciado em um lugar, provavelmente no scheduler(driver), ele não tem trabalho pesado de dados, e serve apenas para conectar os trabalhos.
  • Os env args podem ser passados ​​como kwargs em rabit.init
  • Como o aumento de árvore é um algoritmo em lote, precisamos aguardar que todos os dados sejam ingeridos antes de iniciar o treinamento.

    • Observe, no entanto, que cada trabalhador só precisa pegar um fragmento (subconjunto de linhas) de dados.

    • Idealmente, devemos usar a interface do iter de dados para passar os dados para o DMatrix como mini-lote, para que todo o conjunto de dados não precise ficar na memória

    • Isso é feito via https://github.com/dmlc/xgboost/blob/master/include/xgboost/c_api.h#L117 , que ainda não possui um wrapper python.

    • Para a primeira solução, eu recomendaria passar diretamente por array

Eu tive algum tempo para brincar com isso esta manhã. Resultados aqui: https://github.com/mrocklin/dask-xgboost

Até agora, ele lida apenas com o aprendizado distribuído de um único conjunto de dados na memória. Algumas questões surgiram:

  1. Qual é a melhor maneira de serializar e passar objetos DMatrix?
  2. Qual é a melhor maneira de serializar e retornar um resultado Booster?
  3. Como as variáveis ​​de ambiente listadas acima são mapeadas para argumentos em rabit.init ? Qual é precisamente a forma esperada de entradas para rabit.init ? Passar o resultado de slave_envs() para rabit.init obviamente não funcionará porque espera uma lista. Devemos converter cada keyname para --key , talvez descartando o prefixo DMLC e convertendo para minúsculas?
  4. Existe uma boa maneira de testar a correção? Como comparamos dois objetos Booster? Devemos esperar que o treinamento distribuído produza exatamente o mesmo resultado e o treinamento sequencial?
  • Você normalmente não serializa o DMatrix, é mais como um detentor de dados de tempo de treinamento, suponho que os dados sejam passados ​​e compartilhados por dask (array/dataframe), depois passados ​​para xgboost

    • Podemos explorar melhores maneiras de passar dados que não sejam diretamente através do array na memória, possivelmente expondo um iterador de dados ao xgboost

  • Você pode escolher o Booster, desde que o xgboost esteja instalado em ambos os lados.
  • Desculpe por não elaborar como as coisas são passadas, deveria ser
rabit.init(['DMLC_KEY1=VALUE1', 'DMLC_KEY2=VALUE2']
  • Normalmente, o booster treinado a partir de uma máquina distribuída e única não é o mesmo, mas aqui estão algumas coisas para verificar

    • O reforço devolvido por todos os trabalhadores deve ser idêntico

    • Procurando o erro de validação preditiva, ele deve ser quase tão baixo quanto o caso de uma única máquina

Mais duas perguntas gerais sobre como isso é usado (não tenho experiência com XGBoost e apenas um pouco de experiência com aprendizado de máquina, por favor, perdoe minha ignorância).

  1. É razoável usar vários trabalhadores nos mesmos dados de entrada? (XGBoost é computacionalmente vinculado?)
  2. Se operarmos em conjuntos de dados maiores, preciso fazer algo especial para informar a cada trabalhador do XGBoost que seus dados diferem de seus pares?

Qual caso de uso é mais comum?

Cada trabalho deve funcionar em uma partição diferente de dados (por linhas), eles NÃO devem olhar para os mesmos dados de entrada.

  • Se os dados não forem grandes o suficiente, uma versão multi-thread deve fazer
  • Cada trabalho coletará estatísticas separadamente em sua partição e sincronizará entre si

Isso normalmente corresponde à operação mapPartition em estruturas como spark/flink

Digamos que meu conjunto de dados tenha 8 linhas, 4 colunas, se iniciarmos dois trabalhadores

  • trabalhador 0 lê da linha 0-3
  • trabalhador 1 lê da linha 4 -7

OK, o que está lá em cima agora é um pouco mais limpo. Seria bom se tivéssemos alguma capacidade de consumir resultados à medida que eles fossem gerados em cada trabalhador, mas trabalhamos em torno disso por enquanto. Aqui está a solução atual:

  1. Persista a matriz dask ou dataframe no cluster, aguarde a conclusão
  2. Descubra onde cada pedaço/partição foi parar
  3. Diga a cada trabalhador para concatenar exatamente esses pedaços/partições e treiná-los

Esta solução parece ser gerenciável, mas não é ideal. Seria conveniente se o xgboost-python pudesse aceitar os resultados assim que eles chegassem. No entanto, acho que a próxima coisa a fazer é experimentá-lo na prática.

Vou procurar exemplos na internet. Se alguém tiver um problema artificial que eu possa gerar facilmente com a API numpy ou pandas, seria bem-vindo. Até então, aqui está um exemplo trivial no meu laptop com dados aleatórios:

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>

O código relevante está aqui se alguém quiser dar uma olhada: https://github.com/mrocklin/dask-xgboost/blob/master/dask_xgboost/core.py

Como eu disse, sou novo no XGBoost, então provavelmente estou perdendo algumas coisas.

um exemplo típico de brinquedo para tentar está em https://github.com/dmlc/xgboost/tree/master/demo/data
No entanto, está no formato libsvm e precisa de um pouco de análise para colocá-lo em numpy

Algo maior (para o qual você realmente precisaria de um cluster)? Ou existe uma maneira padrão de gerar um conjunto de dados de tamanho arbitrário?

Ou, talvez, uma pergunta melhor seja: "O que você (ou qualquer outra pessoa lendo esta edição) gostaria de ver aqui?"

Edifício prever agora. Se eu mover o modelo de volta para um trabalhador (passando pelo processo pickle/unpickle) e chamar bst.predict em alguns dados, recebo o seguinte erro:

Doing rabit call after Finalize

Minha suposição era que, neste ponto, o modelo é autocontido e não precisa mais usar rabit. Parece funcionar bem na máquina cliente. Alguma ideia de por que eu poderia receber esse erro ao chamar predict ?

Parte do preditor ainda usa rabit, principalmente porque o preditor ainda usa o aprendiz com algumas rotinas de inicialização que são compartilhadas com o treinamento. Eventualmente, isso deve ser corrigido, mas este é o caso por enquanto.

Acho que, desde que funcione bem para o conjunto de dados comum, é um ponto de partida interessante.

Há razões para usar um cluster para dados médios de qualquer maneira (facilidade de agendamento no cluster env), alguns dos usuários do pyspark podem estar interessados ​​em experimentá-lo se o anunciarmos um pouco

Testar no conjunto de dados que realmente importa foi difícil, por exemplo (experimente 1 conjunto de dados com 1 bilhão de linhas). Kaggle pode ter um grande conjunto de dados que pode ser relevante, cerca de 10 milhões.

Este repositório mostra experimentos com o conjunto de dados das companhias aéreas, que eu acho que está nas dezenas de milhões de linhas e dezenas de colunas (mil após uma codificação a quente?) conjuntos de dados maiores desta amostra. Presumivelmente, poderíamos aumentar isso, se necessário.

Aqui está um exemplo usando esses dados com pandas e xgboost em um único núcleo. Quaisquer recomendações sobre preparação de dados, parâmetros ou como fazer isso corretamente serão bem-vindas.

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 qualquer forma, aqui está uma opção. O conjunto de dados das companhias aéreas parece bem conhecido e pode ser inconvenientemente grande na prática. Novamente, o aprendizado de máquina não é minha especialidade, então não sei se isso é apropriado ou não.

cc @TomAugspurger , que parece ser o tipo de cara que pode ter pensamentos sobre isso.

Em relação ao Dask e à previsão, sempre posso configurar o rabit novamente. Isso parece um pouco sujo, porque força a avaliação em vez de manter as coisas preguiçosas. Mas este não é um bloqueador sério para usar.

Correndo em alguns problemas com previsão. Duas questões:

  1. Posso ligar para Booster.predict várias vezes na mesma sessão de rabit?
  2. Posso chamar rabit.init , Booster.predict e rabit.finalize em tópicos separados?

Atualmente eu crio um novo rastreador e chamo rabit.init no thread principal do trabalhador. Isso funciona bem. No entanto, quando chamo Booster.predict em threads de trabalho (cada trabalhador dask mantém um pool de threads para computação), recebo erros como Doing rabit call after Finalize . Alguma recomendação?

Parte do preditor ainda usa rabit, principalmente porque o preditor ainda usa o aprendiz com algumas rotinas de inicialização que são compartilhadas com o treinamento. Eventualmente, isso deve ser corrigido, mas este é o caso por enquanto.

Estou curioso sobre isso. Depois de serializar-transferir-desserializar o modelo treinado de um trabalhador para minha máquina cliente, parece funcionar bem em dados normais, mesmo que não haja rede rabit. Parece que um modelo treinado com Rabit pode ser usado para prever dados sem rabit. Isso também parece ser necessário na produção. Você pode dizer mais sobre as restrições de usar um modelo treinado por rabit aqui?

Exemplo de conjunto de dados/problema
Supondo que eu tenha tudo correto acima, existe um exemplo de treinamento distribuído padrão que as pessoas usam para demonstração?

Eu seria bom para reproduzir os resultados deste experimento:

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

com a nova opção binning + fast hist do XGBoost (#1950), deve ser possível obter resultados semelhantes.

um exemplo típico de brinquedo para tentar está em https://github.com/dmlc/xgboost/tree/master/demo/data
No entanto, está no formato libsvm e precisa de um pouco de análise para colocá-lo em numpy

Você pode estar interessado neste PR em sklearn: https://github.com/scikit-learn/scikit-learn/pull/935

@mrocklin Não há restrição para reutilizar o modelo. Assim, o modelo treinado em versão distribuída pode ser utilizado em versão serial. É apenas que a limitação atual do preditor (quando compilado com rabit) misturou a função com a função de treinamento (então a chamada do rabit aconteceu).

Agora que você diz isso, acho que podemos ter uma solução para o problema. Simplesmente faça um rabit.init (sem passar nada, e faça o preditor pensar que é o único trabalhador) antes do preditivo resolver o problema

sim. De fato, isso resolve o problema. dask-xgboost agora suporta previsão: https://github.com/mrocklin/dask-xgboost/commit/827a03d96977cda8d104899c9f42f52dac446165

Obrigado pela solução @tqchen !

Aqui está um fluxo de trabalho com dask.dataframe e xgboost em uma pequena amostra do conjunto de dados de companhias aéreas no meu laptop local. Isso parece bom para todos? Existem elementos de API do XGBoost que estou perdendo aqui?

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

Meu objetivo de curto prazo é escrever um post curto sobre isso para que, espero, alguém com mais experiência com o XGBoost e com mais tempo venha adotar este projeto e impulsioná-lo. (Eu, como todo mundo aqui, estou trabalhando em alguns outros projetos como este ao mesmo tempo.)

Sou parcial com o conjunto de dados das companhias aéreas apenas porque já o tenho em um bucket do S3. Concordo, porém, que o conjunto de dados Criteo daria uma melhor demonstração em escala.

Ainda não tenho certeza de quais parâmetros usar ou como julgar o resultado. Para parâmetros, posso usar o experimento de @szilard aqui . Existe uma boa maneira de julgar as previsões? Por exemplo, estamos procurando predictions > 0.5 para corresponder a labels_test ?

Talvez a maneira mais comum de avaliar o desempenho preditivo para classificação binária (especialmente em configurações de pesquisa ou competição) seja usar a área sob a curva ROC (AUC), embora em aplicações do mundo real se deva usar métricas alinhadas com os valores de "negócios" produzidos usando os modelos.

Por exemplo, estamos procurando previsões > 0,5 para corresponder a labels_test?

sim. Se você tomar a média disso no conjunto de teste, esta é a precisão do teste. Mas é provável que o conjunto de dados esteja desequilibrado (muito mais ausência de cliques do que cliques). Nesse caso, a pontuação ROC AUC é uma métrica melhor.

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

assumindo que predictions é uma matriz 1D de probabilidades positivas estimadas pelo modelo para cada linha no conjunto de teste.

@mrocklin Uma pergunta de acompanhamento, o dask permite trabalhos de trabalho com vários segmentos? Eu sei que isso não é muito relevante para python devido ao GIL. Mas o xgboost pode permitir treinamento multi-thread por trabalhador enquanto ainda coordena uns com os outros de forma distribuída. Devemos sempre definir os argumentos nthread de xgboost para ser o número de núcleos de trabalho desse trabalhador

A resposta curta é "sim". A maior parte do uso do Dask é com projetos como NumPy, Pandas, SKLearn e outros que são principalmente apenas código C e Fortran, envolvidos com Python. O GIL não afeta essas bibliotecas. Algumas pessoas usam o Dask para aplicativos semelhantes ao PySpark RDD (consulte dask.bag ) e serão afetadas. Este grupo é a minoria embora.

Então, sim, o Dask permite tarefas multi-thread. Como dizemos ao XGBoost para usar vários threads? Em meus experimentos até agora, vejo alto uso da CPU sem alterar nenhum parâmetro, então talvez tudo funcione bem por padrão?

O XGBoost usa multi-thread por padrão e usará todos os threads de CPU disponíveis na máquina (em vez de naquele trabalhador) se nthread não estiver definido. Isso pode criar condição de corrida quando vários trabalhadores são atribuídos à mesma máquina.

Portanto, é sempre bom definir o parâmetro nthread para o número máximo de núcleos que o trabalhador pode usar. Normalmente, uma boa prática é usar cerca de 4 threads por trabalhador

Claro, deve ser realizado em
https://github.com/mrocklin/dask-xgboost/commit/c22d066b67c78710d5ad99b8620edc55182adc8f

Em segunda-feira, 20 de fevereiro de 2017 às 18h31, Tianqi Chen [email protected]
escrevi:

O XGBoost usa multi-thread por padrão e usará toda a CPU disponível
threads na máquina (em vez de nesse trabalhador) se nthread não estiver definido.
Isso pode criar condição de corrida quando vários trabalhadores são atribuídos ao mesmo
máquina.

Portanto, é sempre bom definir o parâmetro nthread para o número máximo de
núcleos que o trabalhador pode usar. Normalmente, uma boa prática é usar em torno de dizer
4 fios por trabalhador


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/dmlc/xgboost/issues/2032#issuecomment-281205747 ou silenciar
o segmento
https://github.com/notifications/unsubscribe-auth/AASszPELRoeIvqEzyJhkKumIs-vd0PHiks5reiJngaJpZM4L_PXa
.

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

Comentários críticos são muito bem-vindos. Mais uma vez, por favor, perdoe minha ignorância neste campo.

@mrocklin ótima demonstração! Eu acho que o desempenho do tempo de execução (e possivelmente o uso de memória) poderia ser bastante melhorado usando 'tree_method': 'hist', 'grow_policy': 'lossguide' no parâmetro dict.

Obrigado @ogrisel. Com esses parâmetros o tempo de treino passa de seis minutos para um minuto. O uso de memória parece permanecer o mesmo.

OK, voltando a isso. Existem outras operações do XGBoost além de treinar e prever que devemos implementar?

@tqchen ou @ogrisel se algum de vocês tiver tempo para analisar a implementação em https://github.com/mrocklin/dask-xgboost/blob/master/dask_xgboost/core.py eu ficaria grato. Eu entendo que olhar através de uma base de código estrangeira nem sempre está no topo das listas de prioridade.

Se tudo estiver ok, adicionarei um pouco mais ao README, publicarei no PyPI e provavelmente poderemos fechar esse problema.

Acho que apenas treinar e prever precisam ser distribuídos. Outras coisas não precisam ser distribuídas, pois não respondem no conjunto de dados

Eu empurrei dask-xgboost para PyPI e o movi para https://github.com/dask/dask-xgboost

Obrigado @tqchen e @ogrisel por sua ajuda aqui. A colaboração tornou isso relativamente fácil.

Eu ficaria feliz em ajudar as pessoas se elas quisessem executar benchmarks. Até lá, fechando.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

frankzhangrui picture frankzhangrui  ·  3Comentários

ivannz picture ivannz  ·  3Comentários

hx364 picture hx364  ·  3Comentários

nnorton24 picture nnorton24  ·  3Comentários

uasthana15 picture uasthana15  ·  4Comentários