Scikit-learn: Verwenden Sie die Python-Protokollierung, um Informationen zum Konvergenzfortschritt und -Level für lang laufende Aufgaben zu melden

Erstellt am 12. Feb. 2011  ·  31Kommentare  ·  Quelle: scikit-learn/scikit-learn

Dies ist ein Vorschlag, das Protokollierungsmodul von Python zu verwenden, anstatt die Flags stdout und verbose in der Modell-API zu verwenden.

Die Verwendung des Protokollierungsmoduls würde es dem Benutzer erleichtern, die Ausführlichkeit des Scikits mithilfe einer einzigen und gut dokumentierten Konfigurationsschnittstelle und Protokollierungs-API zu kontrollieren.

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

New Feature

Hilfreichster Kommentar

Eine fünfte Option wäre, Verbose-Flags zu entfernen, die Protokollierung überall zu verwenden und Benutzern die Möglichkeit zu geben, die Ausführlichkeit über die Protokollierungs-API anzupassen. Dafür wurde Logging schließlich entwickelt.

Ich würde das Entfernen von ausführlichen Informationen unterstützen, da ich die Konfiguration pro Schätzer finde
frustrierend, und die numerischen Werte von ausführlichen willkürlichen, schlecht
dokumentiert,

Ich denke, es wäre sehr schön, verbose loszuwerden und Protokollierungsebenen zu verwenden. Der einzige Nachteil, den ich sehe, ist, dass die Protokollierung dadurch etwas weniger auffindbar wird.

Alle 31 Kommentare

Die Arbeiten dazu haben in https://github.com/GaelVaroquaux/scikit-learn/tree/progress_logger begonnen

Was noch zu tun bleibt, ist wahrscheinlich ziemlich mechanische Arbeit.

Auch im neuen Modul Gradient Boosting gibt es Arbeit.

Logging ist meiner Erfahrung nach überhaupt nicht so einfach zu verwenden, also -1 dazu.

Arbeitet jemand daran?

Wie wäre es, wenn wir einen Logger hinzufügen, der standardmäßig auf STDOUT druckt? Das sollte ziemlich einfach sein, oder?

Dieses Problem ist seit 2011 offen und daher frage ich mich, ob das behoben wird. Ich bin auf dieses Problem mit RFECV gestoßen (https://github.com/scikit-learn/scikit-learn/blob/a24c8b464d094d2c468a16ea9f8bf8d42d949f84/sklearn/feature_selection/rfe.py#L273). Ich wollte den Fortschritt drucken, aber der standardmäßige ausführliche Druck druckt zu viele Nachrichten. Ich wollte nicht sys.stdout patchen, damit dies funktioniert, und das Überschreiben des Loggers wäre die einfache und saubere Lösung.

Es gibt andere in sklearn ausgegebene Versionen wie #8105 und #10973, die von einer echten Anmeldung in sklearn profitieren würden. Insgesamt denke ich, dass Logging eine großartige Ergänzung zu sklearn wäre.

Sie können gerne daran arbeiten. vielleicht ist ein Rückrufsystem besser als
Protokollierung

Ich bin gerade etwas beschäftigt, aber ich unterstütze die anpassbare Protokollierung in sklean in jeder Form (obwohl ich die Standard-Python-Protokollierung bevorzuge).

Gab es eine Diskussion darüber, was verbose=True bedeuten wird, wenn scikit-learn mit der Protokollierung beginnt? Wir beschäftigen uns ein bisschen damit in dask-ml: https://github.com/dask/dask-ml/pull/528.

Da Bibliotheken keine Protokollierungskonfiguration durchführen sollen, liegt es am Benutzer, seine "Anwendung" (die nur ein Skript oder eine interaktive Sitzung sein kann) zu konfigurieren, um die Dinge entsprechend zu protokollieren. Dies ist nicht immer einfach, richtig zu machen.

Mein Vorschlag in https://github.com/dask/dask-ml/pull/528 lautet, dass verbose=True "Protokollierung vorübergehend für mich konfigurieren" bedeutet. Sie können einen Kontextmanager verwenden, um die Protokollierung zu konfigurieren , und scikit-learn möchte sicherstellen, dass Nachrichten der Ebene INFO auf stdout ausgegeben werden, um dem aktuellen Verhalten zu entsprechen.

Bedeutet vorübergehend auch, dass die Einrichtung des Handlers spezifisch dafür ist
Schätzer oder Schätzertyp?

Mein Vorschlag in dask/dask-ml#528 lautet, dass verbose=True "Protokollierung vorübergehend für mich konfigurieren" bedeutet.

Dies scheint eine gute Balance zu sein. Die Verwendung des Logging-Moduls ist nicht so benutzerfreundlich. Ein anderer "Hack" wäre, standardmäßig info , aber wenn ein Benutzer verbose=True festlegt, können die Protokolle auf warning erhöht werden. Dies würde funktionieren, da Warnungen standardmäßig angezeigt werden.

Ich denke, die Ebene bestimmter Nachrichten zu ändern, wenn der Benutzer nach mehr fragt
Ausführlichkeit ist genau das Gegenteil von der Bedeutung des Logging-Moduls
Arbeit. Aber der lokale Handler könnte von Warnung zu Info zu Debug wechseln
Level im Stream, wenn die Ausführlichkeit zunimmt

Der Kommentar von @jnothman stimmt mit meinen Gedanken überein. scikit-learn würde die Nachricht immer ausgeben, und das verbose-Schlüsselwort steuert die Logger-Ebene und die Handler.

Aber der lokale Handler könnte von Warnung zu Info zu Debug wechseln
Level im Stream, wenn die Ausführlichkeit zunimmt

Okay, gehen wir damit. Derzeit sind die Protokollierungsstufen https://docs.python.org/3/library/logging.html#logging -levels Standardmäßig können wir INFO , die standardmäßig nicht ausgegeben werden. Wenn verbose=1 , haben wir den Handler Change Info -> Warning und Debug -> Info. Wenn wir verbose>=2 , haben wir immer noch Info -> Warnung, aber auch Debug -> Warnung, und der Schätzer kann das verbose>=2 so interpretieren, dass "mehr Debug-Meldungen ausgeben, wenn die Ausführlichkeit zunimmt". Diese Bedeutung kann zwischen verschiedenen Schätzern unterschiedlich sein.

Was denken Sie?

Hallo, ich interessiere mich sehr für dieses Thema. Ich habe einige Erfahrung mit logging und würde gerne helfen, hier eine Verbesserung zu implementieren, wenn ein Konsens und ein Plan erreicht werden.

könnte hilfreich sein, um die hier erwähnten Ideen zusammenzufassen:

  1. Verwenden Sie ein Rückrufmuster
  2. Ändern Sie die Ebene der Nachricht, abhängig von verbose
    if verbose:
        logger.debug(message)
    else:
        logger.info(message)
  1. Ändern Sie die Stufe von logger , abhängig von verbose
    if verbose:
        logger.selLevel("DEBUG")
  1. füge einen Handler mit Level DEBUG , je nach Ausführlichkeit
    if verbose:
        verbose_handler = logging.StreamHandler()
        verbose_handler.setLevel("DEBUG")
        logger.addHandler(verbose_handler)

Meine Meinung zu diesen Optionen:

Option 1 oder Option 4 wäre wahrscheinlich am besten.

  • Option 1 (Rückrufe) ist insofern gut, als sie am agnostischsten ist (die Leute können Dinge protokollieren, wie sie wollen). Aus Sicht der Messaging-/Zustandserfassung ist es jedoch möglicherweise weniger flexibel. (Werden Callbacks nicht nur einmal oder einmal pro einer internen Schleifeniteration aufgerufen?)
  • Option 2, wie hier besprochen, ist meiner Meinung nach der Missbrauch der logging Bibliothek
  • Option 3 funktioniert, aber ich denke, dass ein Teil des Zwecks der Verwendung der logging Bibliothek zunichte gemacht wird. Wenn sklearn logging , können Benutzer die Ausführlichkeit über logging selbst anpassen, zB import logging; logging.getLogger("sklearn").setLevel("DEBUG") .
  • Option 4 ist wahrscheinlich am kanonischsten. Die Dokumentation schlägt vor, _keine_ andere Handler im Bibliothekscode als NullHandler s zu erstellen, aber ich denke, hier ist es sinnvoll, da sklearn verbose Flags hat. In diesem Fall ist der Protokolldruck ein "Feature" der Bibliothek.

Eine fünfte Option wäre, verbose Flags zu entfernen, logging überall zu verwenden und Benutzern die Möglichkeit zu geben, die Ausführlichkeit über die logging API anzupassen. Dafür wurde logging schließlich entwickelt.

@grisaitis danke! Siehe auch neuere verwandte Diskussionen in https://github.com/scikit-learn/scikit-learn/issues/17439 und https://github.com/scikit-learn/scikit-learn/pull/16925#issuecomment -638956487 (in Bezug auf Rückrufe). Ihre Hilfe wäre sehr dankbar, das Hauptproblem ist, dass wir uns noch nicht entschieden haben, welcher Ansatz am besten wäre :)

Ich würde das Entfernen von ausführlichen Informationen unterstützen, da ich die Konfiguration pro Schätzer finde
frustrierend, und die numerischen Werte von ausführlichen willkürlichen, schlecht
dokumentiert usw. Die Konfiguration pro Klasse würde gehandhabt, indem
mehrere scikit-learn-Loggernamen.

Eine fünfte Option wäre, Verbose-Flags zu entfernen, die Protokollierung überall zu verwenden und Benutzern die Möglichkeit zu geben, die Ausführlichkeit über die Protokollierungs-API anzupassen. Dafür wurde Logging schließlich entwickelt.

Ich würde das Entfernen von ausführlichen Informationen unterstützen, da ich die Konfiguration pro Schätzer finde
frustrierend, und die numerischen Werte von ausführlichen willkürlichen, schlecht
dokumentiert,

Ich denke, es wäre sehr schön, verbose loszuwerden und Protokollierungsebenen zu verwenden. Der einzige Nachteil, den ich sehe, ist, dass die Protokollierung dadurch etwas weniger auffindbar wird.

Die Protokollierung bietet außerdem die Möglichkeit, jeder Protokollnachricht zusätzliche Informationen anzuhängen, nicht nur Zeichenfolgen. Also eine ganze Menge nützlicher Sachen. Wenn Sie also einen Verlust während des Lernens melden möchten, können Sie dies tun und den numerischen Wert speichern. Darüber hinaus können Sie den numerischen Wert sowohl als Zahl speichern als auch verwenden, um eine benutzerfreundliche Zeichenfolge zu formatieren, wie zum Beispiel: logger.debug("Current loss: %(loss)s", {'loss': loss}) . Ich finde das im Allgemeinen sehr nützlich und würde mich freuen, wenn sklearn das auch enthüllt.

Ich denke, Logger auf Modul- oder Estimator-Ebene zu haben, ist im Moment etwas übertrieben und wir sollten mit etwas Einfachem beginnen, das es uns ermöglicht, es später zu erweitern.
Außerdem sollte alles, was wir tun, es den Benutzern ermöglichen, das aktuelle Verhalten einigermaßen einfach zu reproduzieren.

Wie interagieren Logging und Joblib? Die Protokollierungsebene wird nicht beibehalten (wie erwartet, denke ich):

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

Ehrlich gesagt bin ich mir jedoch nicht ganz sicher, wie das funktioniert.

sowohl stdout als auch stderr werden übrigens nicht in jupyter angezeigt.

Mein Traum: mit einer einzigen Zeile eine Annäherung an das aktuelle Verhalten zu bekommen, aber auch einfach Fortschrittsbalken verwenden oder stattdessen Konvergenz darstellen zu können.

re verbose: Es ist wahrscheinlich sauberer, verbose als veraltet zu bezeichnen, aber wenn man verbose veraltet und keine Protokollierung auf Schätzerebene hat, wird es etwas schwieriger, einen Schätzer zu protokollieren, aber keinen anderen. Ich denke, es ist in Ordnung, wenn der Benutzer die Nachrichten filtert.

Hallo zusammen, danke für die freundlichen Antworten und Infos. Ich habe die anderen Ausgaben gelesen und habe einige Gedanken.

joblib wird schwierig. ich habe aber ein paar ideen.

@amueller das ist sehr komisch. ich habe dein Beispiel reproduziert. Dinge tun , die Arbeit mit concurrent.futures.ProcessPoolExecutor , was ich denke , joblib Anwendungen ...

Es scheint, als würde joblib den Staat in logging Atombomben angreifen. irgendwelche joblib Experten haben Ideen, was los sein könnte?

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)

welche Ausgänge

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

Ich denke, Sie sollten Prozesse, die joblib spawns, so konfigurieren, dass sie Protokollierungsnachrichten an den Hauptlogger im Hauptprozess senden. Dann kann man die Protokollierung nur im Hauptprozess steuern. So oder so ähnlich . Es gibt also Protokollierungswarteschlangensenken und -quellen, und Sie können sie miteinander verknüpfen. Wir verwenden dies in unserem Cluster, um alle Protokolle von allen Arbeitern auf allen Maschinen an einen zentralen Ort zu senden, um sie auf dem Computer des Benutzers anzuzeigen.

@mitar Ich stimme zu, ich denke, das könnte die beste

Ich programmiere gerade ein Beispiel mit logging QueueHandler / QueueListener von logging , um mit joblib und concurrent.futures zu testen. werde hier nachgehen.

liebe auch deinen anderen vorschlag:

Die Protokollierung bietet außerdem die Möglichkeit, jeder Protokollnachricht zusätzliche Informationen anzuhängen, nicht nur Zeichenfolgen. Also eine ganze Menge nützlicher Sachen. Wenn Sie also einen Verlust während des Lernens melden möchten, können Sie dies tun und den numerischen Wert speichern. Darüber hinaus können Sie den numerischen Wert sowohl als Zahl speichern als auch verwenden, um eine benutzerfreundliche Zeichenfolge zu formatieren, wie zum Beispiel: logger.debug("Current loss: %(loss)s", {'loss': loss}) . Ich finde das im Allgemeinen sehr nützlich und würde mich freuen, wenn sklearn das auch enthüllt.

Ich habe eine Visualisierung der Gaußschen Mischungsmodellierung mit dem Parameter extra und einer benutzerdefinierten Handler-Klasse implementiert. funktioniert super gut, um den Status herumzugeben und den Benutzer entscheiden zu lassen, wie er mit dem Status umgeht.

auch zu den Eigenheiten von joblib , die ich oben bemerkt habe ... ich werde das so akzeptieren, wie es ist und außerhalb des Geltungsbereichs liegt. ein warteschlangenbasiertes Design wäre ohnehin am flexibelsten.

Die einzige Einschränkung bei der Verwendung eines QueueHandlers, die mir einfällt, besteht darin, dass jeder extra Zustand ( logger.debug("message", extra={...} ) darin besteht, dass das extra Dikt für die Warteschlange serialisierbar sein muss. also keine numpy Arrays. :/ fallen mir keine anderen Probleme ein

Ich kodiere gerade ein Beispiel mit QueueHandler / QueueListener,

Ja, Sie sollten immer Queue-Handler verwenden, da Sie nie wissen, wann das Senden über den Socket blockiert und Sie das Modell nicht aufgrund von Logging-Blockierungen verlangsamen möchten.

Außerdem müssen Sie nicht einmal extra . Ich denke, logger.debug("message %(foo)s", {'foo': 1, 'bar': 2}) funktioniert einfach.

Ja, Sie sollten immer Queue-Handler verwenden, da Sie nie wissen, wann das Senden über den Socket blockiert und Sie das Modell nicht aufgrund von Logging-Blockierungen verlangsamen möchten.

für den Fall joblib , wenn wir QueueHandler / QueueListener implementieren, welchen Status müssten wir dann an den Prozesspool übergeben? nur die queue , oder?

Außerdem müssen Sie nicht einmal extra . Ich denke, logger.debug("message %(foo)s", {'foo': 1, 'bar': 2}) funktioniert einfach.

danke ja. Ich finde es auch nützlich, den Status zu protokollieren, ohne ihn in Text umzuwandeln. zB Einfügen eines numpy-Arrays in extra und Durchführen einer Echtzeit-Datenvisualisierung (in gewisser Weise visuelles Logging) mit einem benutzerdefinierten Logging-Handler in einem Jupyter-Notebook. das wäre SUPER nett mit sklearn und sieht so aus, als hätte @rth ähnliche Arbeit mit Rückrufen gemacht.

Für den Joblib-Fall, wenn wir QueueHandler / QueueListener implementieren, welchen Status müssten wir dann an den Prozesspool übergeben? nur die Warteschlange, oder?

Ich glaube schon. Ich habe dies nicht über Prozessgrenzen hinweg verwendet, aber es scheint, dass sie eine dokumentierte Unterstützung für Multiprocessing haben, daher sollte es auch mit Joblib funktionieren. Ich verwende QueueHandler / QueueListener innerhalb desselben Prozesses. Logging-Schreibvorgänge vom Logging-Transport entkoppeln. Ebenso QueueHandler -> QueueListener -> An zentralen Protokollierungsdienst senden. Aber aus der Dokumentation sieht es so aus, als ob es durch die Multiprocessing-Warteschlange funktionieren kann.

Ich finde es auch nützlich, den Status zu protokollieren, ohne ihn in Text umzuwandeln

Jawohl. Was ich sagen möchte ist, dass Sie nicht extra , sondern einfach dict direkt übergeben und dann nur wenige Elemente aus diesem dict für die Nachrichtenformatierung verwenden (beachten Sie, dass entschieden wird, was in Formatstrings verwendet wird.) von Benutzern der sklearn-Bibliothek, nicht von der sklearn-Bibliothek, kann man immer konfigurieren, dass man etwas formatieren möchte, was man nicht erwartet hat. Alle Werte in extra können auch für die Nachrichtenformatierung verwendet werden. Ich bin mir also nicht sicher, wie nützlich das extra ist. Aber ich sage auch nicht, dass wir es nicht verwenden sollten. Es ist viel expliziter, was die Nutzlast für den String auf der linken Seite war und was zusätzlich ist. Wir können also auch beides verwenden. Ich wollte nur sicherstellen, dass diese Alternative bekannt ist.

@grisaitis FYI Wenn Sie einen Namen in einem Commit erwähnen, erhält die Person jedes Mal, wenn Sie etwas mit dem Commit tun (z.

Tut mir leid, Andreas! 😬 Das ist peinlich... Ich habe nur versucht, gut dokumentierte Commits zu haben lol. Werde es in Zukunft vermeiden.

In diesem Repo habe ich herausgefunden, wie die Protokollierung mit joblib mit einer QueueHandler / QueueListener-Kombination funktionieren kann. Scheint gut zu funktionieren.

Als ersten Schritt werde ich die Protokollierung mit diesem Ansatz in einem Teil von sklearn implementieren, in dem joblib verwendet wird. Vielleicht eines der Ensemble-Modelle. Werde eine neue PR eröffnen.

für den Joblib-Fall, wenn wir QueueHandler / QueueListener implementiert haben,

Ja, es hört sich so an, als ob es notwendig wäre, einen Monitoring-Thread (hier QueueListener ) sowohl bei Verwendung des Logging-Moduls als auch bei Callbacks im Fall von Multiprocessing zu starten/stoppen (ungefähres Beispiel für Callbacks mit Multiprocessing in https:// github.com/scikit-learn/scikit-learn/pull/16925#issuecomment-656184396)

Ich denke also, der einzige Grund, warum das, was ich oben gemacht habe, "funktioniert" war, war, dass es auf stdout gedruckt wurde, was die freigegebene Ressource war und print in Python3 oder so ähnlich threadsicher ist?

Ich denke also, der einzige Grund, warum das, was ich oben getan habe, "funktioniert" war, war, dass es auf stdout gedruckt wurde, was die freigegebene Ressource war und print in Python3 threadsicher ist oder so ähnlich?

Der Druck ist nicht threadsicher. Sie drucken einfach auf denselben Dateideskriptor. Wenn Sie wahrscheinlich längere Zeit laufen, werden Sie feststellen, dass sich Nachrichten manchmal verschachteln und Zeilen durcheinander geraten.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen