Scikit-learn: Используйте ведение журнала Python для отчета о прогрессе конвергенции на уровне информации для длительных задач.

Созданный на 12 февр. 2011  ·  31Комментарии  ·  Источник: scikit-learn/scikit-learn

Это предложение использовать модуль ведения журнала python вместо использования флагов stdout и verbose в API моделей.

Использование модуля журналирования упростило бы пользователю управление подробностью scikit с помощью единого и хорошо документированного интерфейса конфигурации и API журналирования.

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

New Feature

Самый полезный комментарий

Пятый вариант - удалить подробные флаги, везде использовать ведение журнала и позволить пользователям настраивать подробность через API ведения журнала. В конце концов, именно для этого и была разработана регистрация.

Я бы поддержал удаление подробностей, так как нахожу конфигурацию для каждого оценщика
расстраивает, а числовые значения многословно произвольно, плохо
задокументировано,

Я думаю, что было бы неплохо избавиться от verbose и использовать уровни ведения журнала. Единственный недостаток, который я вижу, заключается в том, что это сделает ведение журнала менее заметным.

Все 31 Комментарий

Работа над этим началась в https://github.com/GaelVaroquaux/scikit-learn/tree/progress_logger

Что еще предстоит сделать, это, скорее всего, довольно механическая работа.

Также есть работа в новом модуле Gradient Boosting.

По моему опыту, ведение журнала на самом деле не так просто использовать, поэтому -1 по этому поводу.

Кто-нибудь над этим работает?

Как насчет того, чтобы добавить регистратор, который по умолчанию печатает в STDOUT? Это должно быть довольно просто, правда?

Эта проблема открыта с 2011 года, поэтому мне интересно, будет ли она исправлена. Я столкнулся с этой проблемой с RFECV (https://github.com/scikit-learn/scikit-learn/blob/a24c8b464d094d2c468a16ea9f8bf8d42d949f84/sklearn/feature_selection/rfe.py#L273). Я хотел распечатать прогресс, но при подробной печати по умолчанию печатается слишком много сообщений. Я не хотел, чтобы обезьяна исправляла sys.stdout чтобы эта работа работала, и переопределение регистратора было бы простым и чистым решением.

Есть и другие, выпущенные в sklearn, такие как # 8105 и # 10973, которые выиграют от реального входа в sklearn. В целом, я думаю, что ведение журнала было бы отличным дополнением к sklearn.

добро пожаловать поработать над этим. возможно, система обратного вызова лучше, чем
протоколирование

Сейчас я немного занят, но я поддерживаю настраиваемое ведение журнала в sklean в любой форме (хотя я предпочитаю стандартное ведение журнала на Python).

Было ли обсуждение того, что будет означать verbose=True , когда scikit-learn начнет использовать ведение журнала? Мы немного разбираемся с этим в dask-ml: https://github.com/dask/dask-ml/pull/528.

Учитывая, что библиотеки не должны выполнять настройку журналирования, пользователь должен настроить свое «приложение» (которое может быть просто сценарием или интерактивным сеансом) для соответствующего журналирования. Это не всегда легко сделать правильно.

Мое предложение в https://github.com/dask/dask-ml/pull/528 для verbose=True означает «временно настроить ведение журнала для меня». Вы можете использовать диспетчер контекста для настройки ведения журнала , и scikit-learn должен убедиться, что сообщения уровня INFO выводятся на стандартный вывод в соответствии с текущим поведением.

Временно означает также, что настройка обработчика специфична для этого
оценщик или оценщик типа?

Мое предложение в dask / dask-ml # 528 для verbose = True означает «временно настроить ведение журнала для меня».

Кажется, это хороший баланс. Использование модуля регистрации не так уж и удобно. Другой «взлом» - использовать по умолчанию info , но когда пользователь устанавливает verbose=True журналы могут быть повышены до warning . Это сработает, потому что предупреждения отображаются по умолчанию.

Я думаю об изменении уровня конкретных сообщений, когда пользователь просит больше
многословность - это полная противоположность тому, как модуль ведения журнала предназначен для
Работа. Но локальный обработчик может измениться с предупреждения на информацию для отладки.
уровень в потоке по мере увеличения подробностей

Комментарий @jnothman совпадает с моими мыслями. scikit-learn всегда будет выдавать сообщение, а ключевое слово verbose управляет уровнем регистратора и обработчиками.

Но локальный обработчик может измениться с предупреждения на информацию для отладки.
уровень в потоке по мере увеличения подробностей

Хорошо, давай с этим. В настоящее время уровни ведения журнала https://docs.python.org/3/library/logging.html#logging -levels По умолчанию мы можем использовать INFO , который по умолчанию не генерирует. Когда verbose=1 , у нас есть информация об изменении обработчика -> предупреждение и отладка -> информация. Когда мы устанавливаем verbose>=2 , у нас все еще есть информация -> предупреждение, но также есть отладка -> предупреждение, и оценщик может интерпретировать verbose>=2 как означающий «выдавать больше отладочных сообщений по мере увеличения подробностей». Это значение может быть разным для разных оценщиков.

Что вы думаете?

Привет, меня очень интересует этот вопрос. У меня есть некоторый опыт работы с logging и я хотел бы помочь реализовать здесь улучшения, если будет достигнут некоторый консенсус и план.

может быть полезно вспомнить упомянутые здесь идеи:

  1. использовать шаблон обратного вызова
  2. изменить уровень сообщения в зависимости от verbose
    if verbose:
        logger.debug(message)
    else:
        logger.info(message)
  1. изменить уровень logger зависимости от verbose
    if verbose:
        logger.selLevel("DEBUG")
  1. добавить обработчик с уровнем DEBUG , в зависимости от подробности
    if verbose:
        verbose_handler = logging.StreamHandler()
        verbose_handler.setLevel("DEBUG")
        logger.addHandler(verbose_handler)

Мой взгляд на эти варианты:

Вариант 1 или вариант 4, вероятно, будет лучшим.

  • Вариант 1 (обратные вызовы) хорош тем, что он максимально агностичен (люди могут регистрировать вещи, как хотят). Но он может быть менее гибким с точки зрения обмена сообщениями / фиксации состояния. (Разве обратные вызовы не вызываются только один раз или один раз за какую-то итерацию внутреннего цикла?)
  • Вариант 2, как обсуждалось здесь, я считаю неправильным использованием библиотеки logging
  • Вариант 3 работает, но, я думаю, частично лишает смысла использование библиотеки logging . Если sklearn использует logging , то пользователи могут настроить многословие с помощью самого logging , например, import logging; logging.getLogger("sklearn").setLevel("DEBUG") .
  • Вариант 4, наверное, самый канонический. Документы предлагают _не_ создавать обработчики в коде библиотеки, отличные от NullHandler s, но я думаю, что здесь есть смысл, учитывая, что sklearn имеет флаги verbose . В этом случае печать журнала - это «особенность» библиотеки.

Пятый вариант должен был бы удалить verbose флаги, используйте logging во всем мире, и позволяет пользователям настроить подробность через logging API. В конце концов, это то, для чего был разработан logging .

@grisaitis спасибо! См. Также более свежие обсуждения по теме в https://github.com/scikit-learn/scikit-learn/issues/17439 и https://github.com/scikit-learn/scikit-learn/pull/16925#issuecomment -638956487. (относительно обратных вызовов). Мы будем очень благодарны за вашу помощь, основная проблема в том, что мы еще не решили, какой подход будет лучше :)

Я бы поддержал удаление подробностей, так как нахожу конфигурацию для каждого оценщика
расстраивает, а числовые значения многословно произвольно, плохо
задокументированы и т. д. Конфигурация для каждого класса будет обрабатываться с помощью
несколько имен регистраторов scikit-learn.

Пятый вариант - удалить подробные флаги, везде использовать ведение журнала и позволить пользователям настраивать подробность через API ведения журнала. В конце концов, именно для этого и была разработана регистрация.

Я бы поддержал удаление подробностей, так как нахожу конфигурацию для каждого оценщика
расстраивает, а числовые значения многословно произвольно, плохо
задокументировано,

Я думаю, что было бы неплохо избавиться от verbose и использовать уровни ведения журнала. Единственный недостаток, который я вижу, заключается в том, что это сделает ведение журнала менее заметным.

Кроме того, ведение журнала обеспечивает то, что вы можете прикреплять дополнительную информацию к каждому сообщению журнала, а не только к строкам. Так что целый набор полезных вещей. Поэтому, если вы хотите сообщить о потерях во время обучения, вы можете сделать это и сохранить числовое значение. Более того, вы можете сохранить числовое значение как число и использовать его для форматирования удобной для пользователя строки, например: logger.debug("Current loss: %(loss)s", {'loss': loss}) . Я считаю, что это очень полезно в целом, и я был бы рад, если бы sklearn также раскрыл это.

Я думаю, что иметь логгеры уровня модуля или оценки - это немного излишне, и мы должны начать с чего-то простого, что позволит нам расширить его позже.
Кроме того, все, что мы делаем, должно позволять пользователям достаточно легко воспроизводить текущее поведение.

Как взаимодействуют ведение журнала и joblib? Уровень ведения журнала не сохраняется (как и ожидалось):

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

Честно говоря, я не совсем понимаю, как это работает.

оба stdout и stderr не отображаются в jupyter, кстати.

Моя мечта: иметь возможность получить приблизительное представление о текущем поведении с помощью одной строки, но также иметь возможность легко использовать вместо этого индикаторы выполнения или сходимость графика.

re verbose: вероятно, будет проще отказаться от подробного, но отказ от подробного описания и отсутствие ведения журнала на уровне оценщика немного усложнит регистрацию одного оценщика, но не другого. Я думаю, что это нормально, если пользователь фильтрует сообщения.

Привет всем, спасибо за дружеские ответы и информацию. Я прочитал другие выпуски и у меня есть некоторые мысли.

joblib будет сложно. У меня есть кое-какие идеи.

@amueller , это очень странно. я воспроизвел твой пример. вещи действительно работают с concurrent.futures.ProcessPoolExecutor , который, я думаю, joblib использует ...

похоже, что joblib уничтожает состояние в logging . любые joblib эксперты имеют представление о том, что может происходить?

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)

который выводит

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

Я думаю, вам следует настроить процессы порождения joblib для отправки сообщения журнала в основной журнал в основном процессе. Тогда можно будет управлять логированием только в основном процессе. Что-то вроде этого или этого . Итак, есть приемники и источники очереди журналирования, и вы можете связать их вместе. Мы используем это в нашем кластере, чтобы отправлять все журналы со всех рабочих на всех машинах в центральное место, чтобы показать их на компьютере пользователя.

@mitar Я согласен, я думаю, что это лучший вариант. (не обязательно сетевые очереди, но очереди межпроцессного взаимодействия)

Я на самом деле кодирую пример, используя logging QueueHandler / QueueListener прямо сейчас, чтобы протестировать с joblib и concurrent.futures . буду следить здесь.

также люблю ваше другое предложение:

Кроме того, ведение журнала обеспечивает то, что вы можете прикреплять дополнительную информацию к каждому сообщению журнала, а не только к строкам. Так что целый набор полезных вещей. Поэтому, если вы хотите сообщить о потерях во время обучения, вы можете сделать это и сохранить числовое значение. Более того, вы можете сохранить числовое значение как число и использовать его для форматирования удобной для пользователя строки, например: logger.debug("Current loss: %(loss)s", {'loss': loss}) . Я считаю, что это очень полезно в целом, и я был бы рад, если бы sklearn также раскрыл это.

Я реализовал визуализацию моделирования гауссовой смеси с помощью параметра extra и специального класса Handler. отлично работает для передачи состояния и позволяет пользователю решать, как его обрабатывать.

также по поводу идиосинкразии joblib , которые я заметил выше ... я собираюсь принять это как есть и за пределами области применения. Дизайн на основе очередей в любом случае был бы наиболее гибким.

единственное ограничение использования QueueHandler, о котором я могу думать, заключается в том, что любое состояние extra ( logger.debug("message", extra={...} ) состоит в том, что extra dict должен быть сериализуемым для очереди. так что никаких массивов numpy. : / не могу думать ни о каких других проблемах

Я на самом деле кодирую пример, используя QueueHandler / QueueListener прямо сейчас,

Да, вы всегда должны использовать обработчик очереди, потому что вы никогда не знаете, когда отправка через сокет блокируется, и вы не хотите замедлять модель из-за блокировки регистрации.

Кроме того, вам даже не нужно использовать extra . Я думаю, что logger.debug("message %(foo)s", {'foo': 1, 'bar': 2}) просто работает.

Да, вы всегда должны использовать обработчик очереди, потому что вы никогда не знаете, когда отправка через сокет блокируется, и вы не хотите замедлять модель из-за блокировки регистрации.

для случая joblib , если бы мы реализовали QueueHandler / QueueListener , какое состояние мы должны были бы передать пулу процессов? только queue , верно?

Кроме того, вам даже не нужно использовать extra . Я думаю, что logger.debug("message %(foo)s", {'foo': 1, 'bar': 2}) просто работает.

спасибо да. Я считаю полезным также регистрировать состояние без преобразования его в текст. например, включение массива numpy в extra и выполнение визуализации данных в реальном времени (в некотором роде визуальное ведение журнала) с помощью специального обработчика ведения журнала в блокноте jupyter. это было бы СУПЕР-классно со sklearn, и похоже, что @rth проделал аналогичную работу с обратными вызовами.

в случае с joblib, если бы мы реализовали QueueHandler / QueueListener, какое состояние мы должны были бы передать пулу процессов? просто очередь, да?

Я так думаю. Я не использовал это для границ процессов, но похоже, что у них есть документированная поддержка многопроцессорности, поэтому она также должна работать с joblib. Я использую QueueHandler / QueueListener внутри того же процесса. Чтобы отделить ведение журнала записи от транспорта журнала. Как и QueueHandler -> QueueListener -> Отправить в центральную службу ведения журнала. Но из документации похоже, что он может работать через многопроцессорную очередь.

Я считаю полезным также регистрировать состояние без преобразования его в текст

да. Я говорю о том, что вам не нужно использовать extra , а просто передавать dict напрямую, а затем вы используете только несколько элементов из этого dict для форматирования сообщения (обратите внимание, что то, что используется в строках формата, решено пользователями библиотеки sklearn, а не библиотекой sklearn, всегда можно настроить то, что вы хотите, при форматировании чего-то, чего вы не ожидали, поэтому то, что именно преобразуется в текст, на самом деле не находится под контролем sklearn). Все значения в extra также могут использоваться для форматирования сообщений. Поэтому я не уверен, насколько полезен этот extra . Но я также не говорю, что мы не должны его использовать. Гораздо более ясно, что было полезной нагрузкой для строки слева, а что - лишним. Так что мы можем использовать и то, и другое. Я просто хотел убедиться, что эта альтернатива известна.

@grisaitis FYI, если вы упоминаете имя в

Извини, Андреас! 😬 Это обидно ... Я просто пытался иметь хорошо документированные коммиты, лол. Избегу в будущем.

В этом репо я выяснил, как ведение журнала может работать с joblib с помощью комбинации QueueHandler / QueueListener. Кажется, хорошо работает.

В качестве первого шага я реализую ведение журнала с этим подходом в той части sklearn, где используется joblib . Может быть, одна из моделей ансамбля. Открою новый пиар.

для случая joblib, если мы реализовали QueueHandler / QueueListener,

Да, похоже, что необходимо запустить / остановить поток мониторинга (здесь QueueListener ) как при использовании модуля ведения журнала, так и обратных вызовов в случае многопроцессорной обработки (приблизительный пример обратных вызовов с многопроцессорностью в https: // github.com/scikit-learn/scikit-learn/pull/16925#issuecomment-656184396)

Итак, я полагаю, что единственная причина, по которой то, что я сделал выше, «сработало» заключалась в том, что он был напечатан в stdout, который был общим ресурсом, а print является потокобезопасным в python3 или что-то в этом роде?

Итак, я полагаю, что единственная причина, по которой то, что я сделал выше, «сработало», заключалась в том, что он был напечатан на stdout, который был общим ресурсом, а print является потокобезопасным в python3 или что-то в этом роде?

Печать не является потокобезопасной. Они просто печатают один и тот же файловый дескриптор. Вероятно, работая в течение более длительного времени, вы увидите, что сообщения иногда чередуются, а строки путаются.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги