Это предложение использовать модуль ведения журнала python вместо использования флагов stdout и verbose в API моделей.
Использование модуля журналирования упростило бы пользователю управление подробностью scikit с помощью единого и хорошо документированного интерфейса конфигурации и API журналирования.
Работа над этим началась в 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
и я хотел бы помочь реализовать здесь улучшения, если будет достигнут некоторый консенсус и план.
может быть полезно вспомнить упомянутые здесь идеи:
verbose
if verbose:
logger.debug(message)
else:
logger.info(message)
logger
зависимости от verbose
if verbose:
logger.selLevel("DEBUG")
DEBUG
, в зависимости от подробности if verbose:
verbose_handler = logging.StreamHandler()
verbose_handler.setLevel("DEBUG")
logger.addHandler(verbose_handler)
Мой взгляд на эти варианты:
Вариант 1 или вариант 4, вероятно, будет лучшим.
logging
logging
. Если sklearn использует logging
, то пользователи могут настроить многословие с помощью самого logging
, например, import logging; logging.getLogger("sklearn").setLevel("DEBUG")
.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 или что-то в этом роде?
Печать не является потокобезопасной. Они просто печатают один и тот же файловый дескриптор. Вероятно, работая в течение более длительного времени, вы увидите, что сообщения иногда чередуются, а строки путаются.
Самый полезный комментарий
Я думаю, что было бы неплохо избавиться от
verbose
и использовать уровни ведения журнала. Единственный недостаток, который я вижу, заключается в том, что это сделает ведение журнала менее заметным.