O tópico de pipelining e paralelização de aprendizado de máquina está em nosso trabalho há muito tempo. Em particular, eu me pergunto se um livro especializado com ênfase em Python é suficiente para isso, ou se uma visão geral e possivelmente uma literatura complexa são necessárias. Decidimos traduzir um artigo introdutório sobre pipelines de aprendizado de máquina, cobrindo considerações arquitetônicas e mais aplicadas. Vamos discutir se as pesquisas nesta direção são relevantes.
Você já escreveu um pipeline de aprendizado de máquina que demorou muito para ser executado? Ou pior: você atingiu o estágio em que precisa salvar partes intermediárias do pipeline em disco para poder estudar os estágios do pipeline um por um, contando com pontos de verificação? Ou pior, você já tentou refatorar esse código de aprendizado de máquina nojento antes de colocá-lo em produção - e descobriu que demorou meses? Sim, qualquer pessoa que trabalhou em pipelines de aprendizado de máquina por muito tempo teve que lidar com isso. Então, por que não construir um bom pipeline que nos dê flexibilidade suficiente e a capacidade de refatorar facilmente o código para envio posterior à produção?
Primeiro, vamos definir um pipeline de aprendizado de máquina e discutir a ideia de usar pontos de interrupção entre os estágios do pipeline. Em seguida, veremos como você pode implementar esses pontos de interrupção para não levar um tiro no pé ao mover o pipeline para a produção. Também discutiremos o streaming de dados e as compensações associadas ao encapsulamento de programação orientada a objetos (OOP) que você deve percorrer em pipelines ao especificar hiperparâmetros.
O QUE SÃO TRANSPORTADORES?
TransportadorÉ uma sequência de etapas na transformação de dados. Ele é criado de acordo com o antigo padrão de design de tubo e filtro (lembre-se, por exemplo, dos comandos bash unix com tubos "|" ou operadores de redirecionamento ">"). No entanto, os pipelines são objetos no código. Portanto, você pode ter uma classe para cada filtro (ou seja, para cada estágio no pipeline), bem como outra classe para combinar todos esses estágios em um pipeline concluído. Alguns pipelines podem combinar outros pipelines em série ou em paralelo, ter muitas entradas ou saídas, etc. É conveniente pensar em pipelines de aprendizado de máquina como:
- Canal e filtros . Os estágios dos dados do processo do pipeline e os estágios gerenciam seu estado interno, que pode ser aprendido a partir dos dados.
- . ; , . , – .
- (DAG). , . : , , , , (,
fit_transform), , ( RNN). , , .
Métodos de
transporte Os transportadores (ou estágios de oleoduto) devem ter os dois métodos a seguir:
- " Adequado " para treinamento em dados e aquisição de um estado (por exemplo, tal estado é o peso de uma rede neural)
- “ Transformar ” (ou “prever”) para realmente processar os dados e gerar uma previsão.
- Nota: Se um estágio de pipeline não exigir um desses métodos, o estágio pode herdar de NonFittableMixin ou NonTransformableMixin, que fornecerá uma implementação de um desses métodos por padrão para que não faça nada.
Os métodos a seguir também podem ser definidos opcionalmente em estágios de pipeline :
- “fit_transform” , , , .
- “ Configuração ” que chamará o método de “configuração” em cada uma dessas etapas do pipeline. Por exemplo, se um estágio de pipeline contém uma rede neural TensorFlow, PyTorch ou Keras, esses estágios podem criar seus próprios gráficos neurais e se registrar para trabalhar com a GPU no método de "configuração" antes do ajuste. Não é recomendado criar gráficos diretamente nos construtores de palco antes de ajustar; Há várias razões para isso. Por exemplo, antes de começar, as etapas podem ser copiadas várias vezes com diferentes hiperparâmetros como parte do algoritmo de Aprendizado de Máquina Automático, que procura os melhores hiperparâmetros para você.
- “ Teardown ”, este método é funcionalmente o oposto de “setup”: ele destrói recursos.
Os métodos a seguir são fornecidos por padrão, fornecendo controle de hiperparâmetro:
- “get_hyperparams” . () , “
__”. - “set_hyperparams” , .
- “get_hyperparams_space” , , . , “
get_hyperparams” , , . , , ,RandInt(1, 3), 1 3 .call .rvs(), “set_hyperparams”, . - “set_hyperparams_space” , “
get_hyperparams_space”.
Readequação de pipeline, minilotagem e aprendizagem online
Para algoritmos que usam minilotagem, como redes neurais profundas de aprendizagem (DNN), ou algoritmos que aprendem online, como aprendizagem por reforço (RL), para pipelines ou seus estágios ideais o encadeamento de várias chamadas é adequado para que elas sigam exatamente uma após a outra e, em tempo real, sejam ajustadas ao tamanho de minilotes. Esse recurso é suportado em alguns pipelines e em alguns estágios dos pipelines, mas em algum estágio o ajuste obtido pode ser redefinido devido ao fato de que o método de “ajuste” será chamado novamente. Tudo depende de como você programou o estágio do pipeline. Idealmente, um estágio de pipeline só deve ser liberado depois de chamar o método de "desmontagem" e, em seguida, chamar o "
setup”Até o próximo ajuste, e os dados não foram liberados entre os ajustes ou durante a conversão.
USANDO PONTOS DE VERIFICAÇÃO EM TRANSPORTADORES
É uma boa prática usar pontos de interrupção em pipelines até que você precise usar este código para outros fins e alterar os dados. Se você não usar as abstrações corretas em seu código, pode estar atirando no próprio pé.
Prós e contras do uso de pontos de verificação em pipelines:
- Os pontos de interrupção podem acelerar o fluxo de trabalho se as etapas de programação e depuração estiverem no meio ou no final do pipeline. Isso elimina a necessidade de recalcular os primeiros estágios do pipeline todas as vezes.
- ( , ), , . , , – . , , , , , .
- Talvez você tenha recursos computacionais limitados e a única opção viável para você seja executar uma etapa de cada vez no hardware disponível. Você pode usar um ponto de interrupção e, em seguida, adicionar mais algumas etapas depois dele, e os dados serão usados de onde você parou se quiser reexecutar toda a estrutura.
Desvantagens de usar pontos de interrupção em pipelines:
- Isso usa discos, então se você fizer errado, seu código pode ficar lento. Para acelerar as coisas, você pode pelo menos usar o disco RAM ou montar a pasta de cache em sua RAM.
- Isso pode ocupar muito espaço em disco. Ou muito espaço na RAM ao usar um diretório montado na RAM.
- O estado armazenado no disco é mais difícil de gerenciar: seu programa tem a complexidade adicional necessária para fazer o código rodar mais rápido. Observe que, de uma perspectiva de programação funcional, suas funções e código não estarão mais limpos, pois você precisa gerenciar os efeitos colaterais associados ao uso do disco. Os efeitos colaterais associados ao gerenciamento do estado do disco (seu cache) podem ser o terreno fértil para todos os tipos de bugs estranhos
Sabe-se que alguns dos bugs mais difíceis na programação surgem de problemas de invalidação de cache.
Em Ciência da Computação, existem apenas duas coisas realmente complicadas: invalidação de cache e nomenclatura de entidades. - Phil Carlton
Conselhos sobre como gerenciar adequadamente o estado e o cache em pipelines.
Sabe-se que frameworks de programação e padrões de projeto podem ser um fator limitante - pela simples razão de que governam certas regras. Esperançosamente, isso é feito para manter suas tarefas de gerenciamento de código o mais simples possível, para que você evite erros e seu código não fique bagunçado. Aqui estão meus cinco centavos sobre o design no contexto de pipelines e gerenciamento de estado:
AS ETAPAS DO TRANSPORTADOR NÃO DEVEM CONTROLAR AS CONFIGURAÇÕES DOS PONTOS DE TESTE NOS DADOS EMITIDOS POR
Para gerenciar isso, uma biblioteca especial de pipelining deve ser usada para fazer tudo isso por você.
Por quê?
Por que os estágios do pipeline não deveriam controlar o posicionamento dos pontos de verificação nos dados que eles produzem? Pelas mesmas boas razões pelas quais você usa uma biblioteca ou estrutura ao trabalhar e não reproduz a funcionalidade correspondente:
- Você terá uma chave seletora simples que tornará mais fácil ativar ou desativar totalmente os pontos de interrupção antes de implantar na produção.
- , , , : , , . , .
- / (I/O) . , . : , . ?
- , , – . , , .
- , , , , , , . , . .
- , , (, , ) . , ( , ) . , , , , , , . , . , .
Isso é legal. Com a abstração certa, agora você pode programar pipelines de aprendizado de máquina para acelerar drasticamente a fase de ajuste de hiperparâmetros; para fazer isso, você precisa armazenar em cache o resultado intermediário de cada teste, ignorando os estágios do pipeline repetidamente, quando os hiperparâmetros dos estágios do pipeline intermediários permanecem inalterados. Além disso, quando você estiver pronto para liberar o código para produção, poderá desabilitar imediatamente o cache inteiramente, em vez de refatorar o código por um mês inteiro.
Não bata nesta parede.
STREAMING DE DADOS EM TRANSPORTADORES DE APRENDIZAGEM DE MÁQUINA
A teoria do processamento paralelo afirma que os pipelines são uma ferramenta de streaming de dados que permite paralelizar os estágios do pipeline. Exemplo de lavanderiailustra bem este problema e sua solução. Por exemplo, um segundo estágio no pipeline pode começar a processar informações parciais do primeiro estágio do pipeline, enquanto o primeiro estágio continua a computar novos dados. Além disso, para que o segundo estágio do transportador funcione, não é necessário que o primeiro estágio complete completamente sua etapa de processamento de todos os dados. Vamos chamar esses pipelines especiais de streaming (veja aqui e aqui ).
Não me entenda mal, trabalhar com canais do scikit-learn é muito agradável. Mas eles não são classificados para streaming. Não apenas o scikit-learn, mas a maioria das bibliotecas existentes em pipeline não aproveitam os recursos de streaming quando poderiam. Existem problemas de multithreading em todo o ecossistema Python. Na maioria das bibliotecas em pipeline, cada estágio é completamente bloqueado e requer transformação de todos os dados de uma vez. Existem apenas algumas bibliotecas de streaming disponíveis.
Ativar o streaming pode ser tão simples quanto usar uma classe em
StreamingPipelinevez dePipelinepara ligar as fases uma a uma. Ao mesmo tempo, são indicados o tamanho do minilote e o tamanho da fila (para evitar o consumo excessivo de RAM, isso garante um trabalho mais estável na produção). Idealmente, tal estrutura também exigiria filas multithread com semáforos, conforme descrito no problema do provedor e do consumidor : para organizar a transferência de informações de um estágio do pipeline para outro.
Em nossa empresa, o Neuraxle já está fazendo uma coisa melhor do que o scikit-learn: trata-se de pipelines sequenciais que podem ser usados com a classe MiniBatchSequentialPipeline.... Até agora, essa coisa não é multi-threaded (mas está nos planos). No mínimo, ele já passa os dados para o pipeline na forma de minilotes durante o processo de adaptação ou transformação, antes de coletar os resultados, o que permite trabalhar com grandes pipelines como no scikit-learn , mas desta vez usando minilotes, bem como inúmeras outras possibilidades, incluindo: espaços de hiperparâmetros, métodos de instalação, aprendizado de máquina automático e assim por diante.
Nossa solução Python de streaming paralelo
- O método de adaptação e / ou transformação pode ser chamado várias vezes seguidas para melhorar o ajuste com novos minilotes.
- , -. , , .
- , . , setup. , , . , TensorFlow, , , , C++, Python, GPU. joblib . .
- , . , – , , , , .
- . , , ; , . , , , , ( Joiner). , . , , , .
Além disso, queremos garantir que qualquer objeto em Python possa ser compartilhado entre threads de forma que seja serializável e recarregável. Nesse caso, o código pode ser enviado dinamicamente para processamento em qualquer trabalhador (pode ser outro computador ou processo), mesmo que o próprio código necessário não esteja neste trabalhador. Isso é feito usando uma cadeia de serializadores específicos para cada classe que incorpora o estágio do pipeline. Por padrão, cada uma dessas etapas possui um serializador que permite processar o código Python regular e, para um código mais complexo, usar a GPU e importar o código em outras linguagens. Os modelos são simplesmente serializados usando seus protetorese, em seguida, recarregado no trabalhador. Se o trabalhador for local, os objetos podem ser serializados em um disco localizado na RAM ou em um diretório montado na RAM.
COMPROMISSOS PARA INCAPSULAÇÃO
Há mais uma coisa irritante inerente à maioria das bibliotecas para aprendizado de máquina em pipeline. É sobre como os hiperparâmetros são tratados. Veja o scikit-learn, por exemplo. Espaços de hiperparâmetros (também conhecidos como distribuições estatísticas de valores de hiperparâmetros ) geralmente precisam ser especificados fora do pipeline, com sublinhados como separadores entre os estágios do (s) pipeline (s). Enquanto a pesquisa aleatória e a pesquisa em gradepermitem que você explore grades de hiperparâmetros ou espaços de probabilidade de hiperparâmetros conforme definido nas distribuições scipy , o scikit-learn em si não fornece espaços de hiperparâmetros padrão para cada classificador e transformador. A responsabilidade por executar essas funções pode ser atribuída a cada um dos objetos no pipeline. Assim, o objeto será autossuficiente e conterá seus próprios hiperparâmetros. Isso não viola o princípio de responsabilidade única, o princípio de abrir / fechar e os princípios de programação orientada a objetos SOLID.
COMPATIBILIDADE E INTEGRAÇÃO Ao
programar pipelines de aprendizado de máquina, é útil ter em mente que eles devem ser compatíveis com muitas outras ferramentas, em particular o scikit-learn., TensorFlow , Keras , PyTorch e muitas outras bibliotecas de máquina e de aprendizado profundo.
Por exemplo, escrevemos um método
.tosklearn()que nos permite transformar estágios de pipeline ou um pipeline inteiro em BaseEstimatorum objeto básico da biblioteca scikit-learn. Quanto a outras bibliotecas de aprendizado de máquina, a tarefa se resume a escrever uma nova classe que herda da nossa BaseStepe substituir em código específico as operações de ajuste e transformação, bem como, possivelmente, configuração e demolição. Você também precisa definir um protetor que salvará e carregará seu modelo. Aqui está a documentação da classe BaseStepe exemplos para ela.
CONCLUSÃO
Para resumir, observamos que o código de pipelines de aprendizado de máquina, pronto para ir para a produção, deve atender a muitos critérios de qualidade, que são bastante alcançáveis se você aderir aos padrões de design corretos e estruturar bem o código. Observe o seguinte:
- No código de aprendizado de máquina, faz sentido usar pipelines e definir cada estágio do pipeline como uma instância de uma classe.
- Toda a estrutura pode ser otimizada com pontos de interrupção para ajudar a encontrar os melhores hiperparâmetros e executar repetidamente o código nos mesmos dados (mas possivelmente com hiperparâmetros diferentes ou código-fonte modificado).
- , RAM. , .
- , –
BaseStep, , .