Scikit-learn: Utilice el registro de Python para informar sobre el progreso de la convergencia, la información de nivel para tareas de larga duración.

Creado en 12 feb. 2011  ·  31Comentarios  ·  Fuente: scikit-learn/scikit-learn

Esta es una propuesta para usar el módulo de registro de Python en lugar de usar marcas stdout y verbose en la API de modelos.

El uso del módulo de registro facilitaría al usuario el control de la verbosidad del scikit mediante una interfaz de configuración y una API de registro únicas y bien documentadas.

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

New Feature

Comentario más útil

Una quinta opción sería eliminar los indicadores detallados, usar el registro en todas partes y permitir que los usuarios ajusten el nivel de detalle a través de la API de registro. Después de todo, para esto se diseñó la tala.

Apoyaría la eliminación de verbose, ya que encuentro la configuración por estimador
frustrante, y los valores numéricos de verboso arbitrario, pobremente
documentado

Creo que deshacerse de verbose y usar niveles de registro sería muy bueno. El único inconveniente que veo es que haría que el registro sea un poco menos detectable.

Todos 31 comentarios

Se ha comenzado a trabajar en esto en https://github.com/GaelVaroquaux/scikit-learn/tree/progress_logger

Lo que queda por hacer es probablemente un trabajo bastante mecánico.

También hay trabajo en el nuevo módulo Gradient Boosting.

El registro en realidad no es tan fácil de usar, en mi experiencia, así que -1 en esto.

¿Alguien está trabajando en esto?

¿Qué tal si agregamos un registrador que por defecto imprime en STDOUT? Eso debería ser bastante simple, ¿verdad?

Este problema ha estado abierto desde 2011, por lo que me pregunto si se solucionará. Me encontré con este problema con RFECV (https://github.com/scikit-learn/scikit-learn/blob/a24c8b464d094d2c468a16ea9f8bf8d42d949f84/sklearn/feature_selection/rfe.py#L273). Quería imprimir el progreso, pero la impresión detallada predeterminada imprime demasiados mensajes. No quería parchear sys.stdout para que esto funcione y anular el registrador sería la solución simple y limpia.

Hay otros emitidos en sklearn como # 8105 y # 10973 que se beneficiarían del registro real en sklearn. En general, creo que el registro sería una gran adición a sklearn.

eres bienvenido a trabajar en ello. tal vez un sistema de devolución de llamada sea mejor que
Inicio sesión

Estoy un poco ocupado en este momento, pero apoyo el registro personalizable en sklean en cualquier forma (aunque prefiero el registro estándar de Python).

¿Ha habido alguna discusión sobre lo que significará verbose=True cuando scikit-learn comience a usar el registro? Estamos lidiando con esto un poco en dask-ml: https://github.com/dask/dask-ml/pull/528.

Dado que se supone que las bibliotecas no deben realizar la configuración de registro, depende del usuario configurar su "aplicación" (que puede ser solo un script o una sesión interactiva) para registrar las cosas de manera apropiada. Esto no siempre es fácil de hacer correctamente.

Mi propuesta en https://github.com/dask/dask-ml/pull/528 es que verbose=True signifique "configurar temporalmente el registro para mí". Puede usar un administrador de contexto para configurar el registro , y scikit-learn querría asegurarse de que los mensajes de INFO level se impriman en stdout para que coincidan con el comportamiento actual.

También significa temporalmente que la configuración del controlador es específica para ese
estimador o tipo de estimador?

Mi propuesta en dask / dask-ml # 528 es que verbose = True signifique "configurar temporalmente el registro para mí".

Esto parece un buen equilibrio. El uso del módulo de registro no es demasiado fácil de usar. Otro "truco" sería usar info por defecto, pero cuando un usuario establece verbose=True los registros pueden elevarse a warning . Esto funcionaría porque las advertencias se muestran de forma predeterminada.

Creo que cambiar el nivel de mensajes específicos cuando el usuario pide más
la verbosidad es exactamente lo contrario de cómo el módulo de registro está destinado a
trabaja. Pero el controlador local podría cambiar de advertencia a información para depurar
nivel en la corriente a medida que aumenta la verbosidad

El comentario de @jnothman coincide con mis pensamientos. scikit-learn siempre emitiría el mensaje, y la palabra clave detallada controla el nivel del registrador y los controladores.

Pero el controlador local podría cambiar de advertencia a información para depurar
nivel en la corriente a medida que aumenta la verbosidad

Bien, vamos con esto. Actualmente, los niveles de registro son https://docs.python.org/3/library/logging.html#logging -levels Por defecto, podemos usar INFO , que no emite por defecto. Cuando verbose=1 , tenemos el controlador cambiar información -> advertencia y depurar -> información. Cuando configuramos verbose>=2 , todavía tenemos información -> advertencia pero también depuración -> advertencia, y el estimador puede interpretar que verbose>=2 significa "emitir más mensajes de depuración a medida que aumenta la información". Este significado puede ser diferente entre diferentes estimadores.

¿Qué piensas?

Hola, estoy muy interesado en este tema. Tengo algo de experiencia con logging y me encantaría ayudar a implementar una mejora aquí si se llega a algún consenso y plan.

podría ser útil para recapitular las ideas mencionadas aquí:

  1. usar un patrón de devolución de llamada
  2. cambiar el nivel del mensaje, dependiendo de verbose
    if verbose:
        logger.debug(message)
    else:
        logger.info(message)
  1. cambiar el nivel de logger , dependiendo de verbose
    if verbose:
        logger.selLevel("DEBUG")
  1. agregue un manejador con nivel DEBUG , dependiendo del detallado
    if verbose:
        verbose_handler = logging.StreamHandler()
        verbose_handler.setLevel("DEBUG")
        logger.addHandler(verbose_handler)

Mi opinión sobre estas opciones:

La opción 1 o la opción 4 probablemente sería la mejor.

  • La opción 1 (devoluciones de llamada) es buena porque es más agnóstica (las personas pueden registrar las cosas como quieran). Pero podría ser menos flexible desde una perspectiva de captura de mensajes / estado. (¿No se llaman devoluciones de llamada solo una vez o una vez por iteración de bucle interno?)
  • Opción 2, como se discutió aquí, creo que está haciendo un mal uso de la biblioteca logging
  • La opción 3 funciona, pero creo que anula parte del propósito de usar la biblioteca logging . Si sklearn usa logging , entonces los usuarios pueden ajustar la verbosidad a través de logging , por ejemplo, import logging; logging.getLogger("sklearn").setLevel("DEBUG") .
  • La opción 4 es probablemente la más canónica. Los documentos sugieren _no_ crear controladores en el código de la biblioteca que no sea NullHandler s, pero creo que aquí tiene sentido, dado que sklearn tiene verbose indicadores. En este caso, la impresión de registros es una "característica" de la biblioteca.

Una quinta opción sería eliminar las banderas verbose , usar logging todas partes y permitir que los usuarios ajusten la verbosidad a través de la API logging . Después de todo, esto es para lo que se diseñó logging .

@grisaitis gracias! Consulte también los debates relacionados más recientes en https://github.com/scikit-learn/scikit-learn/issues/17439 y https://github.com/scikit-learn/scikit-learn/pull/16925#issuecomment -638956487 (con respecto a las devoluciones de llamada). Su ayuda sería muy apreciada, el problema principal es que aún no hemos decidido qué enfoque sería el mejor :)

Apoyaría la eliminación de verbose, ya que encuentro la configuración por estimador
frustrante, y los valores numéricos de verboso arbitrario, pobremente
documentado, etc. La configuración por clase se manejaría teniendo
múltiples nombres de registradores de scikit-learn.

Una quinta opción sería eliminar los indicadores detallados, usar el registro en todas partes y permitir que los usuarios ajusten el nivel de detalle a través de la API de registro. Después de todo, para esto se diseñó la tala.

Apoyaría la eliminación de verbose, ya que encuentro la configuración por estimador
frustrante, y los valores numéricos de verboso arbitrario, pobremente
documentado

Creo que deshacerse de verbose y usar niveles de registro sería muy bueno. El único inconveniente que veo es que haría que el registro sea un poco menos detectable.

Además, una cosa que proporciona el registro es que puede adjuntar información adicional a cada mensaje de registro, no solo cadenas. Todo un dict de cosas útiles. Entonces, si desea informar la pérdida durante el aprendizaje, puede hacerlo y almacenar el valor numérico. Aún más, puede almacenar el valor numérico como número y usarlo para formatear una cadena fácil de usar, como: logger.debug("Current loss: %(loss)s", {'loss': loss}) . Lo encuentro muy útil en general y me encantaría que sklearn también lo exponga.

Creo que tener registradores de nivel de módulo o estimador es un poco exagerado por ahora y deberíamos comenzar con algo simple que nos permita extenderlo más adelante.
Además, cualquier cosa que hagamos debería permitir a los usuarios reproducir de forma razonablemente sencilla el comportamiento actual.

¿Cómo interactúan el registro y la biblioteca de trabajos? El nivel de registro no se conserva (como se esperaba, supongo):

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, no estoy del todo seguro de cómo funciona esto.

tanto stdout como stderr no aparecen en jupyter por cierto.

Mi sueño: poder obtener una aproximación del comportamiento actual con una sola línea, pero también poder usar fácilmente barras de progreso o graficar la convergencia en su lugar.

Re verbose: probablemente sea más limpio desaprobar el verbose, pero desaprobar el verbose y no tener un registro a nivel de estimador hará que sea un poco más complicado registrar un estimador pero no otro. Sin embargo, creo que está bien que el usuario filtre los mensajes.

Hola a todos, gracias por las amables respuestas e información. Leí los otros números y tengo algunas ideas.

joblib será complicado. aunque tengo algunas ideas.

@amueller eso es muy raro. Reproduje tu ejemplo. cosas funcionan con concurrent.futures.ProcessPoolExecutor , que creo que joblib usos ...

parece que joblib está destruyendo el estado en logging . ¿Algún experto en joblib tiene ideas de lo que podría estar pasando?

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)

que salidas

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

Creo que debería configurar los procesos que genera joblib para enviar un mensaje de registro al registrador principal en el proceso principal. Entonces, se puede controlar el registro en el proceso principal únicamente. Algo como esto o esto . Por lo tanto, hay fuentes y receptores de cola de registro y puede unirlos. Usamos esto en nuestro clúster, para enviar todos los registros de todos los trabajadores en todas las máquinas a la ubicación central, para mostrarlos en la computadora del usuario.

@mitar estoy de acuerdo, creo que esa podría ser la mejor apuesta. (no necesariamente colas basadas en la red, sino colas de comunicación entre procesos)

En realidad, estoy codificando un ejemplo usando logging QueueHandler / QueueListener ahora mismo, para probar con joblib y concurrent.futures . Seguiremos aquí.

también me encanta tu otra sugerencia:

Además, una cosa que proporciona el registro es que puede adjuntar información adicional a cada mensaje de registro, no solo cadenas. Todo un dict de cosas útiles. Entonces, si desea informar la pérdida durante el aprendizaje, puede hacerlo y almacenar el valor numérico. Aún más, puede almacenar el valor numérico como número y usarlo para formatear una cadena fácil de usar, como: logger.debug("Current loss: %(loss)s", {'loss': loss}) . Lo encuentro muy útil en general y me encantaría que sklearn también lo exponga.

Implementé una visualización del modelado de mezcla gaussiana usando el parámetro extra y una clase Handler personalizada. funciona muy bien para pasar el estado y dejar que el usuario decida cómo manejar el estado.

También se refieren a las idiosincrasias de joblib que noté anteriormente ... voy a aceptar eso como está y fuera de alcance. un diseño basado en colas sería más flexible de todos modos.

la única limitación de usar un QueueHandler, que se me ocurre, es que cualquier extra estado ( logger.debug("message", extra={...} ) es que el extra dict debe ser serializable para la cola. así que no hay matrices numpy. : No puedo pensar en otros problemas.

De hecho, estoy codificando un ejemplo usando QueueHandler / QueueListener en este momento,

Sí, siempre debe usar el controlador de cola, porque nunca se sabe cuándo se bloquea el envío a través del socket y no desea ralentizar el modelo debido al bloqueo de registro.

Además, ni siquiera tiene que usar extra . Creo que logger.debug("message %(foo)s", {'foo': 1, 'bar': 2}) simplemente funciona.

Sí, siempre debe usar el controlador de cola, porque nunca se sabe cuándo se bloquea el envío a través del socket y no desea ralentizar el modelo debido al bloqueo de registro.

para el caso joblib , si implementamos QueueHandler / QueueListener , ¿qué estado tendríamos que pasar al grupo de procesos? solo los queue , ¿verdad?

Además, ni siquiera tiene que usar extra . Creo que logger.debug("message %(foo)s", {'foo': 1, 'bar': 2}) simplemente funciona.

gracias si. También me resulta útil registrar el estado sin convertirlo en texto. por ejemplo, incluir una matriz numpy en extra y realizar visualización de datos en tiempo real (registro visual de alguna manera) con un controlador de registro personalizado en un cuaderno jupyter. esto sería SUPER agradable con sklearn, y parece que @rth ha estado haciendo un trabajo similar con devoluciones de llamada.

para el caso de la biblioteca de trabajos, si implementamos QueueHandler / QueueListener, ¿qué estado tendríamos que pasar al grupo de procesos? solo la cola, ¿verdad?

Creo que sí. No he usado esto sobre los límites del proceso, pero parece que tienen un soporte documentado para multiprocesamiento, por lo que también debería funcionar con joblib. Estoy usando QueueHandler / QueueListener dentro del mismo proceso. Para desacoplar las escrituras de registro del transporte de registro. También lo es QueueHandler -> QueueListener -> Enviar al servicio de registro central. Pero a partir de la documentación, parece que puede funcionar a través de una cola de multiprocesamiento.

También me resulta útil registrar el estado sin convertirlo en texto

Si. Lo que estoy diciendo es que no tiene que usar extra , sino simplemente pasar dict directamente, y luego usa solo algunos elementos de ese dict para formatear el mensaje (tenga en cuenta que lo que se usa en las cadenas de formato se decide por los usuarios de la biblioteca sklearn, no por la biblioteca sklearn, siempre se puede configurar lo que desee al formatear algo que no esperaba, por lo que lo que se convierte exactamente en texto no está realmente bajo el control de sklearn). Todos los valores en extra también se pueden usar para formatear mensajes. Entonces no estoy seguro de cuán útil es extra . Pero tampoco estoy diciendo que no debamos usarlo. Es mucho más explícito cuál fue la carga útil de la cadena de la izquierda y qué es adicional. Entonces también podemos usar ambos. Solo quería asegurarme de que se conozca esta alternativa.

@grisaitis Para su información, si menciona un nombre en una confirmación, cada vez que hace algo con la confirmación (como rebasarlo, fusionarlo o presionarlo), la persona recibe un correo electrónico, por lo que generalmente no se recomienda;)

¡Lo siento, Andreas! 😬 Eso es vergonzoso ... Solo estaba tratando de tener confirmaciones bien documentadas jajaja. Evitará en el futuro.

En ese repositorio descubrí cómo el registro puede funcionar con joblib con un combo QueueHandler / QueueListener. Parece funcionar muy bien.

Como primer paso, implementaré el registro con ese enfoque en una parte de sklearn donde se usa joblib . Quizás uno de los modelos de conjunto. Abrirá un nuevo PR.

para el caso de la biblioteca de trabajos, si implementamos QueueHandler / QueueListener,

Sí, parece que sería necesario iniciar / detener un hilo de monitoreo (aquí QueueListener ) tanto si se usa el módulo de registro como las devoluciones de llamada en el caso de multiprocesamiento (ejemplo aproximado de devoluciones de llamada con multiprocesamiento en https: // github.com/scikit-learn/scikit-learn/pull/16925#issuecomment-656184396)

Entonces, me imagino que la única razón por la que lo que hice arriba "funcionó" fue que se imprimió en stdout, que era el recurso compartido y print es seguro para subprocesos en python3 o algo así.

Entonces, me imagino que la única razón por la que lo que hice arriba "funcionó" fue que se imprimió en la salida estándar, que era el recurso compartido, y la impresión es segura para subprocesos en python3 o algo así.

La impresión no es segura para subprocesos. Simplemente imprimen en el mismo descriptor de archivo. Probablemente al ejecutar durante más tiempo, verá que los mensajes a veces se entrelazan y las líneas se confunden.

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