المراقبة والانزياح
درس 5 من 6
5المراقبة والانزياح
الصورة الكبيرةالخدمة قد تعمل بلا أي خطأ، كل طلب يعود بـ200، والنموذج يسوء كل أسبوع. هذا الانجراف (Drift): البيانات التي تصل تبتعد تدريجيًّا عمّا تدرّب عليه النموذج. المراقبة تبدأ بثلاثة أشياء تسجّلها دائمًا، وتنتهي بمقياس يخبرك أن البيانات تغيّرت قبل أن يخبرك المستخدمون.
ماذا تشاهد في الرسم: بعد تحديث التطبيق صارت الرسائل أطول بكثير مما تدرّب عليه النموذج، فانزاح المنحنى إلى اليمين. الخدمة ما زالت تعيد 200، وPSI وحده كشف أن البيانات تغيّرت.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
النموذج قد يعمل بلا أي خطأ تقني ويعطي أجوبة أسوأ كل شهر، لأن العالم تغيّر: الناس يسألون أسئلة جديدة، والأسعار تغيرت. مثل خريطة قديمة لمدينة بُنيت فيها طرق جديدة. المراقبة أن تلاحظ ذلك قبل أن يشتكي المستخدمون.
تسجّل دائمًا المدخلات والمخرجات (مع حماية الخصوصية)، والزمن والأخطاء، وإصدار النموذج. الانزياح drift نوعان: في توزيع المدخلات data drift وفي العلاقة بالهدف concept drift. تقيسه بمقارنة التوزيعات مع فترة مرجعية وتنبّه عند تجاوز حد.
قبل أن تبدأ
🌐من دفتر إلى خدمة
خدمتك تعيد نسخة النموذج في كل رد، وهي أول ما تسجّله.
⏱️الزمن والتكلفة
تعرف p50 وp95، وستراهما في لوحة المراقبة.
الفكرة الجوهرية
خدمة بلا أخطاء قد تسوء كل أسبوع. تبدأ هنا بما تسجّله مع كل طلب، ثم حذف البيانات الشخصية، ثم قياس الانجراف، ثم لوحة المراقبة والعيّنة الموسومة.
1ما تسجّله مع كل طلب
كل طلب يكتب سطر سجل بصيغة JSON. فيه دائمًا ثلاثة حقول: معرّف الطلب، ونسخة النموذج، والزمن.
انظر إلى الرسم: بهذه الحقول تحضر عن سؤالين: ماذا حدث لهذا الطلب؟ وهل تغيّر شيء هذا الأسبوع؟ ومعها طول المدخل والنتيجة.
2احذف البيانات الشخصية قبل الكتابة
السجلات يقرؤها فرق كثيرة وتُنسخ وتُحفظ شهورًا. لذلك تُحذف البيانات الشخصية قبل الكتابة لا بعدها، وتكون مدة الحفظ محدودة ومن يقرأ محدودًا.
import json
import re
EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+")
PHONE = re.compile(r"(?:\+?966|0)?5\d{8}\b")
NATIONAL_ID = re.compile(r"\b[12]\d{9}\b")
def redact(text):
text = EMAIL.sub("[email]", text)
text = NATIONAL_ID.sub("[id]", text)
return PHONE.sub("[phone]", text)
def log_request(request_id, text, label, latency_ms, version):
print(json.dumps({
"request_id": request_id,
"model_version": version,
"latency_ms": latency_ms,
"input_chars": len(text),
"input_preview": redact(text)[:60],
"label": label,
}, ensure_ascii=False))
log_request("r-001", "رقمي 0551234567 وبريدي sara@example.com والطلب لم يصل", "negative", 184, "v3")
log_request("r-002", "هويتي 1098765432 أريد تحديث العنوان", "neutral", 201, "v3")
رقم الجوال والبريد ورقم الهوية استُبدلت قبل أن يُكتب أي شيء. السجلات تُقرأ من فرق كثيرة، وتُنسخ لأنظمة أخرى، وتبقى أشهرًا، فالبيانات الشخصية فيها تسرّب ينتظر حدوثه. هذه الأنماط الثلاثة بداية لا نهاية: أضف ما يخصّ مستخدميك (أرقام الحسابات البنكية مثلًا)، واختبرها على عيّنة حقيقية.
3الانجراف ومقياس PSI
الانجراف تغيّر في توزيع المدخلات عمّا تدرّب عليه النموذج، فتنخفض دقته بصمت وكل الطلبات تردّ 200.
انظر إلى الرسم: PSI رقم واحد تقرؤه بحدّين.
طول الرسائل وقت التدريب، ثم في الأسبوع الأول من الإنتاج، ثم في الأسبوع الثامن بعد أن أضاف تحديث للتطبيق نموذجًا طويلًا.
import numpy as np
rng = np.random.default_rng(0)
train_len = rng.lognormal(np.log(60), 0.5, 5000) # message length at training time
week_1 = rng.lognormal(np.log(62), 0.5, 2000) # live traffic, first week
week_8 = rng.lognormal(np.log(120), 0.6, 2000) # after a new app version added a long form
def psi(expected, actual, bins=10):
edges = np.percentile(expected, np.linspace(0, 100, bins + 1))
edges[0], edges[-1] = -np.inf, np.inf
e = np.histogram(expected, edges)[0] / len(expected) + 1e-6
a = np.histogram(actual, edges)[0] / len(actual) + 1e-6
return float(np.sum((a - e) * np.log(a / e)))
for name, live in [("week 1", week_1), ("week 8", week_8)]:
score = psi(train_len, live)
verdict = "stable" if score < 0.1 else "watch" if score < 0.25 else "drifted: investigate"
print(f"{name}: PSI = {score:.2f} ({verdict})")
PSI (مؤشّر استقرار المجتمع) يقسم القيم إلى عشر شرائح بحسب بيانات التدريب، ويقيس كم تغيّرت نسبة كل شريحة. الأسبوع الأول 0.01: مستقر. الأسبوع الثامن 1.40: تغيّر جذري، والنموذج يرى رسائل أطول بكثير مما تدرّب عليه. القاعدة الشائعة: تحت 0.1 مستقر، وبين 0.1 و0.25 راقب، وفوق 0.25 تحقّق. ثم خذ عيّنة وقِس الدقة الفعلية قبل أي قرار.
4اللوحة والعيّنة الموسومة
أخطر انجراف لا يظهر في المدخلات: نفس الكلمات بمعنى جديد. هنا تحتاج عيّنة صغيرة دورية، خمسين رسالة في الأسبوع مثلًا، يسمها شخص.
انظر إلى الرسم: مقياس الانجراف إنذار مبكّر، والدقة على العيّنة هي الحقيقة. لا تعيد التدريب قبل أن تؤكّدها.
from prometheus_client import Counter, Histogram
REQUESTS = Counter("predict_requests_total", "Prediction requests", ["model_version", "status"])
LATENCY = Histogram("predict_latency_seconds", "Prediction latency", ["model_version"],
buckets=[0.05, 0.1, 0.2, 0.3, 0.5, 1, 2, 5])
INPUT_CHARS = Histogram("predict_input_chars", "Input length", buckets=[20, 50, 100, 200, 500, 1000, 2000])
def observe(version, status, seconds, chars):
REQUESTS.labels(version, status).inc()
LATENCY.labels(version).observe(seconds)
INPUT_CHARS.observe(chars)
ثلاثة مقاييس تكفي للوحة أولى: عدد الطلبات بحسب النسخة والحالة (معدّل الأخطاء)، والزمن كمدرّج تكراري (منه تُحسب p50 وp95)، وطول المدخل (إنذار مبكر للانجراف). Prometheus يجمعها وGrafana يرسمها.
كيف يعمل، خطوة بخطوة: كيف يحسب PSI الانجراف
تقسم قيم وقت التدريب إلى عشر خانات متساوية العدد بحسب المئينات.
تحسب حصة كل خانة في بيانات التدريب وفي بيانات الإنتاج الحالية.
تجمع لكل خانة الفرق بين الحصتين مضروبًا في لوغاريتم نسبتهما.
تحت 0.1 مستقر، ومن 0.1 إلى 0.25 راقب، وفوق 0.25 تحقّق بعيّنة مسمّاة قبل أي قرار.
مثال محلول
كل طلب يكتب سطر سجل منظّمًا (JSON) فيه على الأقل: معرّف الطلب، ونسخة النموذج، والزمن. ومعها ما يساعد التشخيص: طول المدخل، والنتيجة، ومعاينة قصيرة للمدخل بعد حذف البيانات الشخصية منها.
جرّب بلا كود
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| لا أخطاء في الخدمة يعني أن النموذج بخير. | كل طلب قد يعود بـ200 والنموذج يسوء كل أسبوع لأن المدخلات ابتعدت عمّا تدرّب عليه. |
| PSI مرتفع يعني أن تعيد التدريب فورًا. | الانجراف إنذار لا حكم. قد يبقى النموذج جيدًا، أو يكون السبب خللًا في جمع البيانات. قِس الدقة على عيّنة مسمّاة أولًا. |
| تسجيل المدخل كاملًا ضروري للتشخيص. | الطول والمعاينة المنقّحة يكفيان غالبًا. العيّنات الكاملة تُحفظ بموافقة وفي مكان بصلاحية محدّدة. |
جرّب بنفسك
ما الثلاثة التي تسجّلها مع كل طلب دائمًا؟
المعرّف يربط الشكوى بالطلب، والنسخة تخبرك أي نموذج أجاب، والزمن يكشف البطء. لا شيء منها بيانات شخصية.
PSI لميزة مهمّة صار 0.4. ما الخطوة التالية؟
الانجراف إنذار لا حكم. قد يكون النموذج ما زال جيدًا على التوزيع الجديد، أو قد يكون السبب خطأ في جمع البيانات لا تغيّرًا حقيقيًّا.
بطاقات مراجعة سريعة
تحقق من فهمك
PSI لطول المدخلات 0.05 (مستقر)، لكن شكاوى المستخدمين تضاعفت. ما الاحتمال؟
انجراف لا يظهر في هذا المقياس: المعنى تغيّر (منتج جديد، موضوع جديد)، أو ميزة أخرى غير الطول تغيّرت، أو المشكلة ليست في النموذج أصلًا (واجهة، أو زمن). خذ عيّنة من الطلبات المشكو منها وسمّها، وقِس الدقة عليها. PSI على ميزة واحدة يرى جزءًا صغيرًا من الصورة.
لماذا تحمل مقاييس الطلبات والزمن في اللوحة نسخة النموذج تسمية؟
لأن نسختين قد تعملان معًا أثناء الإطلاق. التسمية تسمح بمقارنتهما جنبًا إلى جنب، فتعرف فورًا إن رفعت النسخة الجديدة الأخطاء أو الزمن.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي2
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.