هذا اقتراح لاستخدام وحدة تسجيل الدخول في Python بدلاً من استخدام علامات stdout و verbose في واجهة برمجة التطبيقات للنماذج.
إن استخدام وحدة التسجيل سيجعل من السهل على المستخدم التحكم في إسهاب scikit باستخدام واجهة تكوين واحدة وموثقة جيدًا وواجهة برمجة تطبيقات للتسجيل.
بدأ العمل على هذا في https://github.com/GaelVaroquaux/scikit-learn/tree/progress_logger
ما يتبقى هو على الأرجح عمل ميكانيكي إلى حد ما.
هناك أيضًا عمل في وحدة تعزيز التدرج الجديدة.
التسجيل في الواقع ليس بهذه السهولة في الاستخدام على الإطلاق ، في تجربتي ، لذا -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
على stdout لتطابق السلوك الحالي.
هل يعني مؤقتًا أيضًا أن إعداد المعالج خاص بذلك
مقدر أو نوع مقدر؟
اقتراحي في dask / dask-ml # 528 هو للإطالة = صحيح ليعني "تكوين التسجيل مؤقتًا بالنسبة لي".
هذا يبدو وكأنه توازن جيد. استخدام وحدة التسجيل ليس سهل الاستخدام للغاية. قد يكون "الاختراق" الآخر هو استخدام info
افتراضيًا ، ولكن عندما يقوم المستخدم بتعيين verbose=True
، يمكن رفع السجلات إلى warning
. قد يعمل هذا لأنه يتم عرض التحذيرات بشكل افتراضي.
أعتقد أن تغيير مستوى رسائل معينة عندما يطلب المستخدم المزيد
الإسهاب هو بالضبط عكس ما تم تصميمه لوحدة التسجيل
الشغل. لكن المعالج المحلي يمكن أن يتغير من تحذير إلى معلومات لتصحيح الأخطاء
المستوى على الدفق مع زيادة مطولة
تعليق @ jnothman يطابق أفكاري. يقوم scikit-learn دائمًا بإرسال الرسالة ، وتتحكم الكلمة الأساسية المطولة في مستوى المسجل والمعالجات.
لكن المعالج المحلي يمكن أن يتغير من تحذير إلى معلومات لتصحيح الأخطاء
المستوى على الدفق مع زيادة مطولة
حسنًا ، دعنا نذهب مع هذا. مستويات التسجيل حاليًا هي 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
flags. في هذه الحالة ، تعد طباعة السجل "ميزة" للمكتبة.يتمثل الخيار الخامس في إزالة العلامات 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.
يتمثل الخيار الخامس في إزالة العلامات المطولة ، واستخدام التسجيل في كل مكان ، والسماح للمستخدمين بضبط الإسهاب عبر واجهة برمجة تطبيقات التسجيل. هذا هو ما تم تصميم التسجيل من أجله ، بعد كل شيء.
سأدعم الإزالة المطولة ، حيث أجد التكوين لكل مقدر
محبط ، والقيم الرقمية مطول تعسفي ، ضعيف
موثقة
أعتقد أن التخلص من 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 راجع للشغل.
حلمي: أن أكون قادرًا على الحصول على تقدير تقريبي للسلوك الحالي بسطر واحد ، ولكن أيضًا القدرة على استخدام أشرطة التقدم بسهولة أو تقارب الحبكة بدلاً من ذلك.
إعادة الإسهاب: ربما يكون من الأنظف الإهمال المطول ، لكن الإهمال المطول وعدم وجود تسجيل على مستوى المقدر سيجعل من الأصعب قليلاً تسجيل مقدر واحد دون الآخر. أعتقد أنه من الجيد أن يقوم المستخدم بتصفية الرسائل.
مرحبًا جميعًا ، شكرًا على الردود والمعلومات الودية. قرأت القضايا الأخرى ولدي بعض الأفكار.
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)>]
أعتقد أنه يجب عليك تكوين عمليات نشر كتاب الوظائف لإرسال رسالة تسجيل إلى المسجل الرئيسي في العملية الرئيسية. ثم يمكن للمرء التحكم في التسجيل في العملية الرئيسية فقط. شيء من هذا القبيل أو هذا . لذلك توجد أحواض ومصادر قائمة انتظار لتسجيل الدخول ويمكنك ربطها معًا. نستخدم هذا في مجموعتنا ، لإرسال جميع عمليات التسجيل من جميع العاملين على جميع الأجهزة إلى الموقع المركزي ، لعرضها على كمبيوتر المستخدم.
mitar أوافق ، أعتقد أن هذا قد يكون أفضل رهان. (ليس بالضرورة قوائم الانتظار المستندة إلى الشبكة ، ولكن قوائم انتظار الاتصال بين العمليات)
أقوم بالفعل بترميز أحد الأمثلة باستخدام logging
's QueueHandler
/ QueueListener
الآن ، للاختبار باستخدام joblib
و concurrent.futures
. ستتابع هنا.
أحب أيضًا اقتراحك الآخر:
أيضًا ، هناك شيء واحد يوفره التسجيل وهو أنه يمكنك إرفاق معلومات إضافية بكل رسالة تسجيل ، وليس فقط السلاسل. حتى ديكت كامل من الأشياء المفيدة. لذلك إذا كنت تريد الإبلاغ عن الخسارة أثناء التعلم ، فيمكنك القيام بذلك وتخزين القيمة العددية. علاوة على ذلك ، يمكنك تخزين القيمة الرقمية كرقم واستخدامها لتنسيق سلسلة سهلة الاستخدام ، مثل:
logger.debug("Current loss: %(loss)s", {'loss': loss})
. أجد ذلك مفيدًا جدًا بشكل عام وسأحب إذا كشف موقع sklearn عن ذلك أيضًا.
لقد قمت بتنفيذ تصور لنمذجة خليط gaussian باستخدام المعلمة extra
وفئة Handler المخصصة. يعمل بشكل رائع للغاية لتمرير الحالة ، والسماح للمستخدم بتحديد كيفية التعامل مع الحالة.
أيضًا خصوصيات joblib
التي لاحظتها أعلاه ... سأقبل ذلك كما هو وخارج النطاق. سيكون التصميم القائم على قائمة الانتظار أكثر مرونة على أي حال.
والقيد الوحيد من استخدام QueueHandler، أستطيع أن أفكر، هو أن أي extra
دولة ( logger.debug("message", extra={...}
) هو أن extra
ديكت يجب أن يكون للتسلسل لقائمة الانتظار. لذلك لا توجد مصفوفات معقدة. : / لا أستطيع التفكير في أي قضايا أخرى بالرغم من ذلك
أقوم بالفعل بترميز مثال باستخدام 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})
يعمل فقط.
شكرا نعم. أجد أنه من المفيد أيضًا تسجيل الحالة دون تحويلها إلى نص. على سبيل المثال ، تضمين مصفوفة عددية في extra
، والقيام بتصور البيانات في الوقت الفعلي (تسجيل مرئي بطريقة ما) باستخدام معالج تسجيل مخصص في دفتر ملاحظات jupyter. سيكون هذا رائعًا جدًا مع sklearn ، ويبدو أن rth يقوم بعمل مماثل مع عمليات الاسترجاعات.
بالنسبة لحالة كتاب العمل ، إذا قمنا بتطبيق QueueHandler / QueueListener ، فما الحالة التي يجب علينا تمريرها إلى مجموعة العمليات؟ فقط قائمة الانتظار ، أليس كذلك؟
أنا اعتقد ذلك. لم أستخدم هذا فوق حدود العملية ، ولكن يبدو أن لديهم دعمًا موثقًا للمعالجة المتعددة ، لذا يجب أن يعمل مع Joblib أيضًا. أنا أستخدم QueueHandler / QueueListener داخل نفس العملية. لفصل قطع الأشجار يكتب من قطع الأشجار النقل. هكذا هو QueueHandler -> QueueListener -> أرسل إلى خدمة التسجيل المركزية. ولكن من الوثائق يبدو أنه يمكن أن يعمل من خلال قائمة انتظار متعددة المعالجات.
أجد أنه من المفيد أيضًا تسجيل الحالة دون تحويلها إلى نص
نعم فعلا. ما أقوله هو أنك لست مضطرًا إلى استخدام extra
، ولكن فقط قم بتمرير الأمر مباشرة ، وبعد ذلك يمكنك استخدام عناصر قليلة فقط من هذا الشكل لتنسيق الرسالة (لاحظ أن ما يتم استخدامه في سلاسل التنسيق يتم تحديده بواسطة مستخدمي مكتبة sklearn ، وليس من خلال مكتبة sklearn ، يمكن للمرء دائمًا التهيئة التي تريدها في تنسيق شيء لم تكن تتوقعه ، لذا فإن ما يتم تحويله بالضبط إلى نص لا يخضع لسيطرة sklearn). يمكن أيضًا استخدام جميع القيم الموجودة في extra
لتنسيق الرسائل أيضًا. لذلك لست متأكدًا من مدى فائدة extra
. لكنني أيضًا لا أقول إننا لا ينبغي أن نستخدمها. من الواضح أكثر بكثير ما هي الحمولة للسلسلة الموجودة على اليسار ، وما هو إضافي. لذا يمكننا أيضًا استخدام كليهما. أردت فقط التأكد من معرفة هذا البديل.
grisaitis FYI إذا ذكرت اسمًا في الالتزام ، في كل مرة تفعل فيها أي شيء مع الالتزام (مثل إعادة تعيينه أو دمجه أو دفعه) ، يتلقى الشخص بريدًا إلكترونيًا ، لذلك لا يُنصح به عمومًا ؛)
آسف لذلك أندرياس! 😬 هذا محرج ... كنت أحاول فقط الحصول على التزامات جيدة التوثيق لول. سيتجنب في المستقبل.
في هذا الريبو ، اكتشفت كيف يمكن أن يعمل التسجيل مع joblib
مع مجموعة التحرير والسرد QueueHandler / QueueListener. يبدو أنه يعمل بشكل جيد.
كخطوة أولى ، سأقوم بتنفيذ التسجيل باستخدام هذا الأسلوب في جزء من sklearn حيث يتم استخدام joblib
. ربما أحد نماذج المجموعة. سيتم فتح علاقات عامة جديدة.
في حالة إعلان الوظائف ، إذا قمنا بتطبيق QueueHandler / QueueListener ،
نعم ، يبدو أنه سيكون من الضروري بدء / إيقاف سلسلة رسائل مراقبة (هنا QueueListener
) في حالة استخدام وحدة التسجيل وعمليات الاسترجاعات في حالة المعالجة المتعددة (مثال تقريبي لعمليات الاسترجاعات ذات المعالجة المتعددة في https: // github.com/scikit-learn/scikit-learn/pull/16925#issuecomment-656184396)
لذا أعتقد أن السبب الوحيد الذي جعل ما فعلته أعلاه "نجح" هو أنه تم طباعته على stdout وهو المورد المشترك و print
هو threadafe في python3 أو شيء من هذا القبيل؟
لذلك أعتقد أن السبب الوحيد الذي جعل ما فعلته أعلاه "نجح" هو أنه تم طباعته على stdout الذي كان المورد المشترك والطباعة هي Threadafe في python3 أو شيء من هذا القبيل؟
الطباعة ليست آمنة الخيط. هم فقط يطبعون إلى نفس واصف الملف. من المحتمل أن تعمل لفترة أطول ، فسترى أن الرسائل تتداخل أحيانًا وتختلط الأسطر.
التعليق الأكثر فائدة
أعتقد أن التخلص من
verbose
واستخدام مستويات التسجيل سيكون أمرًا رائعًا للغاية. الجانب السلبي الوحيد الذي أراه هو أنه سيجعل التسجيل أقل قابلية للاكتشاف.