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.
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:
verbose
if verbose:
logger.debug(message)
else:
logger.info(message)
logger
, dependendo de verbose
if verbose:
logger.selLevel("DEBUG")
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.
logging
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")
.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 quelogger.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.
Comentários muito úteis
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.