Scikit-learn: Use o registro de python para relatar o progresso da convergência e informações de nível para tarefas de longa execução

Criado em 12 fev. 2011  ·  31Comentários  ·  Fonte: scikit-learn/scikit-learn

Esta é uma proposta para usar o módulo de registro de python em vez de usar sinalizadores stdout e verbose na API de modelos.

Usar o módulo de registro tornaria mais fácil para o usuário controlar o detalhamento do scikit usando uma interface de configuração única e bem documentada e API de registro.

http://docs.python.org/library/logging.html

New Feature

Comentários muito úteis

Uma quinta opção seria remover sinalizadores detalhados, usar o registro em todos os lugares e permitir que os usuários ajustem o detalhamento por meio da API de registro. Afinal, é para isso que o log foi projetado.

Eu apoiaria a remoção detalhada, pois acho a configuração por estimador
frustrante, e os valores numéricos de verboso arbitrário, mal
documentado,

Acho que se livrar de verbose e usar os níveis de registro seria muito bom. A única desvantagem que vejo é que isso tornaria o registro um pouco menos detectável.

Todos 31 comentários

O trabalho já começou em https://github.com/GaelVaroquaux/scikit-learn/tree/progress_logger

O que resta a ser feito é provavelmente um trabalho bastante mecânico.

Também há trabalho no novo módulo Gradient Boosting.

Log, na verdade, não é tão fácil de usar, na minha experiência, então -1 nisso.

Alguém está trabalhando nisso?

Que tal adicionarmos um registrador que, por padrão, imprime em STDOUT? Isso deve ser bastante simples, certo?

Este problema está aberto desde 2011, então eu me pergunto se isso será corrigido. Encontrei este problema com RFECV (https://github.com/scikit-learn/scikit-learn/blob/a24c8b464d094d2c468a16ea9f8bf8d42d949f84/sklearn/feature_selection/rfe.py#L273). Eu queria imprimir o andamento, mas a impressão detalhada padrão imprime muitas mensagens. Eu não queria fazer o monkey patch sys.stdout para fazer isso funcionar e substituir o logger seria uma solução simples e limpa.

Existem outras edições no sklearn, como # 8105 e # 10973, que se beneficiariam de um registro real no sklearn. No geral, acho que o log seria um ótimo complemento para o sklearn.

você é bem-vindo para trabalhar nisso. talvez um sistema de callback seja melhor do que
exploração madeireira

Estou um pouco ocupado agora, mas eu suporte o registro personalizável no sklean em qualquer forma (embora eu prefira o registro Python padrão).

Houve alguma discussão sobre o que verbose=True significará quando o scikit-learn começar a usar o registro? Estamos lidando um pouco com isso em dask-ml: https://github.com/dask/dask-ml/pull/528.

Dado que as bibliotecas não devem fazer a configuração do registro, cabe ao usuário configurar seu "aplicativo" (que pode ser apenas um script ou sessão interativa) para registrar as coisas de forma apropriada. Isso nem sempre é fácil de fazer corretamente.

Minha proposta em https://github.com/dask/dask-ml/pull/528 é de verbose=True para significar "configurar temporariamente o registro para mim". Você pode usar um gerenciador de contexto para configurar o log , e o scikit-learn deseja garantir que as mensagens de nível INFO sejam impressas em stdout para corresponder ao comportamento atual.

Isso também significa temporariamente que a configuração do manipulador é específica para aquele
estimador ou tipo de estimador?

Minha proposta em dask / dask-ml # 528 é verbose = True para significar "configurar temporariamente o registro para mim".

Parece um bom equilíbrio. Usar o módulo de registro não é muito amigável. Outro "hack" seria usar info por padrão, mas quando um usuário define verbose=True os logs podem ser elevados a warning . Isso funcionaria porque os avisos são exibidos por padrão.

Acho que mudar o nível de mensagens específicas quando o usuário pede mais
verbosidade é exatamente o oposto de como o módulo de registro se destina a
trabalhar. Mas o manipulador local pode mudar de aviso para informação para depuração
nível no fluxo conforme aumenta o detalhamento

O comentário de @jnothman corresponde aos meus pensamentos. O scikit-learn sempre emitirá a mensagem e a palavra-chave verbose controla o nível do logger e os manipuladores.

Mas o manipulador local pode mudar de aviso para informação para depuração
nível no fluxo conforme aumenta o detalhamento

Ok, vamos com isso. Atualmente, os níveis de registro são https://docs.python.org/3/library/logging.html#logging -levels Por padrão, podemos usar INFO , que não emite por padrão. Quando verbose=1 , temos o manipulador para alterar informações -> aviso e depurar -> informações. Quando definimos verbose>=2 , ainda temos info -> aviso, mas também temos depuração -> aviso, e o estimador pode interpretar verbose>=2 como significando "emitir mais mensagens de depuração à medida que aumenta o detalhamento". Este significado pode ser diferente entre diferentes estimadores.

O que você acha?

Olá, estou muito interessado neste assunto. Tenho alguma experiência com logging e adoraria ajudar a implementar uma melhoria aqui se algum consenso e um plano forem alcançados.

pode ser útil recapitular as ideias mencionadas aqui:

  1. use um padrão de retorno de chamada
  2. mude o nível da mensagem, dependendo de verbose
    if verbose:
        logger.debug(message)
    else:
        logger.info(message)
  1. mude o nível de logger , dependendo de verbose
    if verbose:
        logger.selLevel("DEBUG")
  1. adicione um manipulador com nível DEBUG , dependendo do detalhamento
    if verbose:
        verbose_handler = logging.StreamHandler()
        verbose_handler.setLevel("DEBUG")
        logger.addHandler(verbose_handler)

Minha opinião sobre essas opções:

A opção 1 ou a opção 4 provavelmente seria a melhor.

  • A opção 1 (callbacks) é boa porque é mais agnóstica (as pessoas podem registrar as coisas como quiserem). Mas pode ser menos flexível do ponto de vista de captura de mensagens / estado. (Os callbacks não são chamados apenas uma vez ou uma vez por alguma iteração de loop interno?)
  • Opção 2, conforme discutido aqui, acho que está fazendo mau uso da biblioteca logging
  • A opção 3 funciona, mas acho que anula parte do propósito de usar a biblioteca logging . Se sklearn usar logging , então os usuários podem ajustar a verbosidade através do próprio logging , por exemplo import logging; logging.getLogger("sklearn").setLevel("DEBUG") .
  • A opção 4 é provavelmente mais canônica. Os documentos sugerem _não_ criar manipuladores em código de biblioteca diferente de NullHandler s, mas acho que aqui faz sentido, visto que sklearn tem sinalizadores verbose . Nesse caso, a impressão do log é um "recurso" da biblioteca.

Uma quinta opção seria remover verbose sinalizadores, usar logging todos os lugares e permitir que os usuários ajustem a verbosidade por meio da API logging . Afinal, é para isso que logging foi projetado.

@grisaitis obrigado! Veja também discussões relacionadas mais recentes em https://github.com/scikit-learn/scikit-learn/issues/17439 e https://github.com/scikit-learn/scikit-learn/pull/16925#issuecomment -638956487 (sobre chamadas de retorno). Sua ajuda seria muito apreciada, o principal problema é que ainda não decidimos qual abordagem seria a melhor :)

Eu apoiaria a remoção detalhada, pois acho a configuração por estimador
frustrante, e os valores numéricos de verboso arbitrário, mal
documentado, etc. A configuração por classe seria tratada por ter
vários nomes de registradores scikit-learn.

Uma quinta opção seria remover sinalizadores detalhados, usar o registro em todos os lugares e permitir que os usuários ajustem o detalhamento por meio da API de registro. Afinal, é para isso que o log foi projetado.

Eu apoiaria a remoção detalhada, pois acho a configuração por estimador
frustrante, e os valores numéricos de verboso arbitrário, mal
documentado,

Acho que se livrar de verbose e usar os níveis de registro seria muito bom. A única desvantagem que vejo é que isso tornaria o registro um pouco menos detectável.

Além disso, uma coisa que o log fornece é que você pode anexar informações extras a cada mensagem de log, não apenas strings. Então, todo o ditado de coisas úteis. Portanto, se você deseja relatar a perda durante o aprendizado, pode fazer isso e armazenar o valor numérico. Ainda mais, você pode armazenar o valor numérico como um número e usá-lo para formatar uma string amigável, como: logger.debug("Current loss: %(loss)s", {'loss': loss}) . Acho isso muito útil em geral e adoraria se o sklearn expusesse isso também.

Acho que ter registradores de nível de módulo ou estimador é um pouco exagero por enquanto e devemos começar com algo simples que nos permita estendê-lo mais tarde.
Além disso, tudo o que fizermos deve permitir aos usuários reproduzir o comportamento atual de maneira razoavelmente fácil.

Como o registro e o joblib interagem? O nível de registro não é preservado (como esperado, eu acho):

import logging
logger = logging.getLogger('sklearn')
logger.setLevel(2)

def get_level():
    another_logger = logging.getLogger('sklearn')
    return another_logger.level

results = Parallel(n_jobs=2)(
    delayed(get_level)() for _ in range(2)
)
results

`` `
[0, 0]

But that's probably not needed, since this works:
```python
import logging
import sys
logger = logging.getLogger('sklearn')
logger.setLevel(1)

handler = logging.StreamHandler(sys.stdout)
logger.addHandler(handler)


def log_some():
    another_logger = logging.getLogger('sklearn')
    another_logger.critical("log something")

results = Parallel(n_jobs=2)(
    delayed(log_some)() for _ in range(2)
)

Honestamente, não tenho certeza de como isso funciona.

ambos stdout e stderr não aparecem no jupyter btw.

Meu sonho: ser capaz de obter uma aproximação do comportamento atual com uma única linha, mas também ser capaz de usar facilmente as barras de progresso ou a convergência do gráfico.

Re verbose: provavelmente é mais limpo descontinuar o verbose, mas a descontinuação do verbose e não ter o registro no nível do estimador tornará um pouco mais complicado registrar um estimador, mas não outro. No entanto, acho que não há problema em que o usuário filtre as mensagens.

Olá a todos, obrigado pelas respostas amigáveis ​​e informações. Li as outras edições e tenho algumas ideias.

joblib será complicado. eu tenho algumas idéias embora.

@amueller isso é muito estranho. eu reproduzi seu exemplo. coisas fazer o trabalho com o concurrent.futures.ProcessPoolExecutor , que eu acho que joblib usos ...

parece que joblib está detonando o estado em logging . algum joblib especialista tem ideias do que pode estar acontecendo?

import concurrent.futures
import logging
import os

logger = logging.getLogger("demo🙂")
logger.setLevel("DEBUG")

handler = logging.StreamHandler()
handler.setFormatter(
    logging.Formatter("%(process)d (%(processName)s) %(levelname)s:%(name)s:%(message)s")
)
logger.addHandler(handler)

def get_logger_info(_=None):
    another_logger = logging.getLogger("demo🙂")
    print(os.getpid(), "another_logger:", another_logger, another_logger.handlers)
    another_logger.warning(f"hello from {os.getpid()}")
    return another_logger

if __name__ == "__main__":
    print(get_logger_info())

    print()
    print("concurrent.futures demo...")
    with concurrent.futures.ProcessPoolExecutor(2) as executor:
        results = executor.map(get_logger_info, range(2))
        print(list(results))

    print()
    print("joblib demo (<strong i="17">@amueller</strong>'s example #2)...")
    from joblib import Parallel, delayed
    results = Parallel(n_jobs=2)(delayed(get_logger_info)() for _ in range(2))
    print(results)

quais saídas

19817 another_logger: <Logger demo🙂 (DEBUG)> [<StreamHandler <stderr> (NOTSET)>]
19817 (MainProcess) WARNING:demo🙂:hello from 19817
<Logger demo🙂 (DEBUG)>

concurrent.futures demo...
19819 another_logger: <Logger demo🙂 (DEBUG)> [<StreamHandler <stderr> (NOTSET)>]
19819 (SpawnProcess-1) WARNING:demo🙂:hello from 19819
19819 another_logger: <Logger demo🙂 (DEBUG)> [<StreamHandler <stderr> (NOTSET)>]
19819 (SpawnProcess-1) WARNING:demo🙂:hello from 19819
[<Logger demo🙂 (DEBUG)>, <Logger demo🙂 (DEBUG)>]

joblib demo (<strong i="21">@amueller</strong>'s example #2)...
19823 another_logger: <Logger demo🙂 (WARNING)> []
hello from 19823
19823 another_logger: <Logger demo🙂 (WARNING)> []
hello from 19823
[<Logger demo🙂 (DEBUG)>, <Logger demo🙂 (DEBUG)>]

Eu acho que você deve configurar os processos de spawns do joblib para enviar mensagem de log para o logger principal no processo principal. Então, pode-se controlar o registro apenas no processo principal. Algo assim ou assim . Portanto, existem fontes e coletores de filas de registro e você pode ligá-los. Usamos isso em nosso cluster, para enviar todo o registro de todos os trabalhadores em todas as máquinas para um local central, para mostrá-lo no computador do usuário.

@mitar eu concordo, acho que pode ser a melhor aposta. (não necessariamente filas baseadas em rede, mas filas de comunicação entre processos)

na verdade, estou codificando um exemplo usando logging QueueHandler / QueueListener agora, para testar com joblib e concurrent.futures . seguirá aqui.

também adoro sua outra sugestão:

Além disso, uma coisa que o log fornece é que você pode anexar informações extras a cada mensagem de log, não apenas strings. Então, todo o ditado de coisas úteis. Portanto, se você deseja relatar a perda durante o aprendizado, pode fazer isso e armazenar o valor numérico. Ainda mais, você pode armazenar o valor numérico como um número e usá-lo para formatar uma string amigável, como: logger.debug("Current loss: %(loss)s", {'loss': loss}) . Acho isso muito útil em geral e adoraria se o sklearn expusesse isso também.

Implementei uma visualização da modelagem de mistura gaussiana usando o parâmetro extra e uma classe Handler personalizada. funciona muito bem para passar o estado e permitir que o usuário decida como lidar com o estado.

também re joblib idiossincrasias que notei acima ... vou aceitar isso como está e fora do escopo. um design baseado em fila seria mais flexível de qualquer maneira.

a única limitação de usar um QueueHandler, que eu consigo pensar, é que qualquer extra estado ( logger.debug("message", extra={...} ) é que extra dict deve ser serializável para a fila. portanto, sem matrizes entorpecidas. : / não consigo pensar em nenhum outro problema, no entanto

na verdade, estou codificando um exemplo usando QueueHandler / QueueListener agora,

Sim, você deve sempre usar o gerenciador de fila, porque você nunca sabe quando o envio sobre o soquete bloqueia e você não quer desacelerar o modelo por causa do bloqueio de registro.

Além disso, você nem precisa usar extra . Acho que logger.debug("message %(foo)s", {'foo': 1, 'bar': 2}) simplesmente funciona.

Sim, você deve sempre usar o gerenciador de fila, porque você nunca sabe quando o envio sobre o soquete bloqueia e você não quer desacelerar o modelo por causa do bloqueio de registro.

para o caso joblib , se implementássemos QueueHandler / QueueListener , que estado teríamos que passar para o pool de processos? apenas queue , certo?

Além disso, você nem precisa usar extra . Acho que logger.debug("message %(foo)s", {'foo': 1, 'bar': 2}) simplesmente funciona.

obrigado sim. Acho útil também registrar o estado sem convertê-lo em texto. por exemplo, incluindo um array numpy em extra , e fazendo visualização de dados em tempo real (log visual de certa forma) com um manipulador de log personalizado em um notebook jupyter. isso seria SUPER legal com sklearn, e parece que @rth tem feito um trabalho semelhante com callbacks.

para o caso do joblib, se implementássemos QueueHandler / QueueListener, que estado teríamos que passar para o pool de processos? só a fila, certo?

Eu penso que sim. Eu não usei isso sobre limites de processo, mas parece que eles têm um suporte documentado para multiprocessamento, então ele deve funcionar com joblib também. Estou usando o QueueHandler / QueueListener dentro do mesmo processo. Para desacoplar as gravações de registro do transporte de registro. O mesmo ocorre com o QueueHandler -> QueueListener -> Enviar para o serviço de registro central. Mas, pela documentação, parece que ele pode funcionar por meio de fila de multiprocessamento.

Acho útil também registrar o estado sem convertê-lo em texto

sim. O que estou dizendo é que você não precisa usar extra , mas apenas passar o dict diretamente, e então você usará apenas alguns itens desse dict para a formatação da mensagem (observe que o que é usado no formato das strings é decidido pelos usuários da biblioteca sklearn, não pela biblioteca sklearn, pode-se sempre configurar se deseja formatar algo que você não esperava, então o que exatamente é convertido em texto não está realmente sob controle do sklearn). Todos os valores em extra também podem ser usados ​​para a formatação da mensagem. Portanto, não tenho certeza de quão útil é esse extra . Mas também não estou dizendo que não devemos usá-lo. É muito mais explícito qual era a carga útil da string à esquerda e o que é extra. Portanto, também podemos usar os dois. Eu só queria ter certeza de que essa alternativa fosse conhecida.

@grisaitis FYI se você mencionar um nome em um commit, toda vez que você fizer algo com o commit (como rebasing ou mesclá-lo ou empurrá-lo), a pessoa recebe um e-mail, então geralmente é desencorajado;)

Desculpe por isso Andreas! 😬 Isso é embaraçoso ... Eu só estava tentando ter commits bem documentados lol. Irá evitar no futuro.

Nesse repo, descobri como o log pode funcionar com joblib com uma combinação QueueHandler / QueueListener. Parece funcionar bem.

Como primeira etapa, implementarei o log com essa abordagem em uma parte do sklearn onde joblib é usado. Talvez um dos modelos de conjunto. Vai abrir um novo PR.

para o caso do joblib, se implementamos QueueHandler / QueueListener,

Sim, parece que seria necessário iniciar / interromper um thread de monitoramento (aqui QueueListener ) tanto se usar o módulo de registro quanto callbacks no caso de multiprocessamento (exemplo aproximado de callbacks com multiprocessamento em https: // github.com/scikit-learn/scikit-learn/pull/16925#issuecomment-656184396)

Então eu acho que a única razão pela qual o que fiz acima "funcionou" foi imprimir em stdout, que era o recurso compartilhado, e print é threadsafe em python3 ou algo parecido?

Então eu acho que a única razão pela qual o que eu fiz acima "funcionou" foi imprimir em stdout, que era o recurso compartilhado, e imprimir é threadsafe em python3 ou algo parecido?

A impressão não é segura para thread. Eles apenas imprimem no mesmo descritor de arquivo. Provavelmente executando por mais tempo, você verá que as mensagens às vezes se intercalam e as linhas se misturam.

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

Questões relacionadas

MechCoder picture MechCoder  ·  165Comentários

jhennrich picture jhennrich  ·  61Comentários

yedtoss picture yedtoss  ·  68Comentários

thomasjpfan picture thomasjpfan  ·  60Comentários

tdomhan picture tdomhan  ·  58Comentários