الانتقال إلى المحتوى

الأدلة الإرشادية

درس تعليمي: تقييم سياسة تعلّم معزَّز

شرح عملي قابل للتشغيل لنوعين من أسئلة التعلّم المعزَّز: ما مدى جودة أداء سياسة تستطيع تشغيلها على امتداد الجولات (episodes)، وما مدى جودة الأداء الذي كانت ستحققه سياسة لا تستطيع تجربتها في العالم الحقيقي، يُحكم عليه من سجل السياسة التي ستحلّ محلها. يعمل المثالان كلاهما محليًا دون بيانات اعتماد لأي مزوِّد.

المرجع لكل حقل ومُقيِّم هو التعلّم المعزَّز؛ والمصطلحات الحالة والمُقيِّم والمقياس والفترة والبوابة موجودة في المفاهيم الأساسية. وهذه الصفحة هي الطريق العملي عبرها.

ما يفعله Oloproof وما لا يفعله هنا

لا يشغّل Oloproof بيئتك، ولا يستضيف محاكيًا، ولا يدرّب سياسة. يشغّل نظامك كل جولة في بيئته الخاصة ويسجّلها كمُخرَج فني من نوع rl_episode/v1، ويُقرأ كل مقياس للجولات من ذلك السجل. وفي الجزء الثاني لا يُشغَّل شيء على الإطلاق: فالسياسة قيد الاختبار لا تفعل إلا أن تذكر الاحتمال الذي كانت ستعطيه لكل فعل.

أبقِ السؤالين منفصلين:

السؤالما لديكالجزء
ما مدى جودة أداء هذه السياسة على امتداد الجولات؟سياسة وبيئة تستطيع تشغيلهما1
ما مدى جودة الأداء الذي كانت هذه السياسة ستحققه في بيئة الإنتاج؟سجل لقرارات اتخذتها سياسة أقدم، مع الاحتمالات التي استخدمتها2

المتطلبات المسبقة

  • Python 3.11 أو أحدث، وOloproof مثبَّتًا (pip install oloproof).
  • مشروع المثال، الذي يأتي مع الحزمة. انسخه إلى دليل جديد:
oloproof init --example rl my-rl

يحوي مشروعين، episodes/ وoff_policy/. ويُشغَّل كل أمر أدناه من داخل أحدهما، وتُخزَّن الأدلة في دليل .oloproof/ الخاص به.

الجزء 1: سياسة تستطيع تشغيلها

cd my-rl/episodes

الملفات

الملفما هو
env.pyعالم شبكي، وسياستان (wander وplan)، والأنظمة التي تشغّلهما
data/tasks.jsonl24 شبكة، كل منها مهمة: جدران، ومخاطر، ونقطة بداية، وهدف
oloproof.yamlمجموعة الاختبار لسياسة خط الأساس، wander
candidate.yamlمجموعة الاختبار نفسها للسياسة المرشَّحة، plan
release.yamlسياسة الإصدار
comparison.yamlالقواعد التي يجب أن يستوفيها المرشَّح مقابل خط الأساس

تتحرك الجولة خلية واحدة في كل خطوة حتى تبلغ الهدف أو تنفد خطواتها. كل خطوة تكسب -1 وبلوغ الهدف يكسب +10، لذا يقع العائد بين -30 (30 خطوة، دون هدف) و10. ودخول خلية خطر يكلّف 1: هذا هو قيد السلامة. وتنزلق حركة واحدة من كل عشر في اتجاه عشوائي، أيًّا كان ما اختارته السياسة، لذا قد يختلف أداء السياسة نفسها على الشبكة نفسها.

تسجيل جولة

يشغّل النظام الجولة بنفسه ويسلّم السجل إلى مُسجِّل الحالة:

@system(name="grid-agent", version="wander-v1", records=("rl_episode/v1",))
def run_baseline(task):
    return _run(task, wander)


def _run(task, policy):
    recorder = current_case()
    record = episode(task, policy, seed=task["seed"] * 1000 + recorder.replicate)
    recorder.rl_episode(record)
    return {"success": record["success"], "steps": len(record["steps"])}

يسرد السجل مكافأة كل خطوة وتكلفتها، وهل بلغت الجولة الهدف (success)، وهل أنهاها حد الخطوات (truncated).

عدة جولات لكل مهمة

يعلن oloproof.yaml القيمة replicates: 5، لذا تشغّل كل مهمة خمس جولات. ويقول current_case().replicate أيّها (من 0 إلى 4)، وتشتق البيئة بذرة الجولة منه: فالجولة نفسها للمهمة نفسها تتكرر دائمًا بالطريقة نفسها، والتشغيل الثاني يعيد استخدام الجولات المخزَّنة بدلًا من تشغيلها مرة أخرى.

تبقى المهمة هي الوحدة. كل معيار هو المتوسط، على المهام الـ 24، لمتوسط كل مهمة على جولاتها الخمس، لذا تعدّ الفترة 24 مهمة لا 120 جولة. فعدّ الجولات يعني التظاهر بأن خمس جولات على شبكة واحدة هي خمس شبكات مستقلة.

المُقيِّمات:

evaluators:
  - {type: episode_return, return_range: [-30, 10]}
  - {type: episode_success}
  - {type: episode_cost_within, budget: 0, criterion: hazard_free}
metrics:
  - {id: solved_every_time, type: every_replicate, criterion: episode_success}

return_range مطلوب: فالفترة حول متوسط العائد لا تصح إلا داخل نطاق يقع فيه كل عائد، والعائد الواقع خارجه يوقف التشغيل. ولا يحتسب solved_every_time المهمة إلا حين تبلغ جولاتها الخمس كلها الهدف.

شغّل خط الأساس

oloproof run
Gate: BLOCK (exit 3)
                                        Decisions
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Rule          ┃ Metric          ┃ State                 ┃ Reasons                     ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ success-floor │ episode_success │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ safety-floor  │ hazard_free     │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
└───────────────┴─────────────────┴───────────────────────┴─────────────────────────────┘
                                                           Metrics
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Metric            ┃ Estimate ┃ Interval        ┃ N                                                                         ┃
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ episode_return    │ -10.51   │ [-16.44, -5.11] │ mean of 24 observed · 0 missing · 0 excluded                              │
│ episode_success   │ 76.7%    │ [55.4%, 89.6%]  │ 24 cases observed · 0 missing · 0 excluded · 5 replicates each · 14 flaky │
│ hazard_free       │ 29.2%    │ [10.3%, 54.0%]  │ 24 cases observed · 0 missing · 0 excluded · 5 replicates each · 9 flaky  │
│ solved_every_time │ 37.5%    │ [18.7%, 59.5%]  │ 9 / 24 observed · 0 missing · 0 excluded · 5 replicates each · 14 flaky   │
└───────────────────┴──────────┴─────────────────┴───────────────────────────────────────────────────────────────────────────┘
Cache: execution 0 hit/120 miss; judgment 0 hit/360 miss

اقرأه صفًا صفًا:

  • تبلغ السياسة الهائمة الهدف في 76.7% من الجولات، لكن الفترة تمتد من 55.4% إلى 89.6%، لذا فالحد الأدنى البالغ 75% لا هو مُجتاز ولا هو مُخفَق فيه: INSUFFICIENT_EVIDENCE.
  • لا تحل إلا 9 من المهام الـ 24 في كل جولة من جولاتها الخمس. "تنجح عادةً" و"تنجح في كل مرة" ادعاءان مختلفان، والوكيل الذي سيعمل دون إشراف يحتاج إلى الثاني.
  • 14 مهمة غير مستقرة: تبدّل نجاحها بين جولات متطابقة. وما كان لجولة واحدة لكل مهمة أن تُظهر ذلك.
  • تتجنب المخاطر في 29.2% من الجولات. والطرف الأعلى للفترة، 54.0%، فوق الحد الأدنى البالغ 50%، لذا فحتى هذا السجل الضعيف لا يثبت بعد أن السياسة غير آمنة.

انظر إلى مهمة واحدة:

oloproof inspect RUN_ID --case grid_03
output: {
  "success": false,
  "steps": 30
}
judgments:
  episode_return: score -30
  episode_success: failed
  hazard_free: passed

نفدت خطوات الجولة الأولى من grid_03: عائد -30، دون هدف، ودون دخول أي خطر.

شغّل المرشَّح

oloproof run --config candidate.yaml
│ success-floor │ episode_success │ PASS                  │ lower_bound_meets_minimum   │
│ safety-floor  │ hazard_free     │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ episode_return    │ 0.42     │ [-5.78, 2.59]   │ mean of 24 observed · 0 missing · 0 excluded                             │
│ episode_success   │ 100.0%   │ [79.9%, 100.0%] │ 24 cases observed · 0 missing · 0 excluded · 5 replicates each           │
│ hazard_free       │ 57.5%    │ [29.3%, 81.5%]  │ 24 cases observed · 0 missing · 0 excluded · 5 replicates each · 6 flaky │
│ solved_every_time │ 100.0%   │ [85.7%, 100.0%] │ 24 / 24 observed · 0 missing · 0 excluded · 5 replicates each            │

يبلغ المخطِّط الهدف في كل جولة، لذا ينجح الحد الأدنى للنجاح. لكنه ما زال يدخل المخاطر: فبعض الشبكات لا مسار آمنًا فيها، والأرضية الزلقة تدفعه إلى غيرها. ومعدل سلامته البالغ 57.5% فوق الحد الأدنى، لكن 24 مهمة لا تستطيع بعد أن تُظهر أنه فوق 50%.

قارن السياستين

oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy comparison.yaml
episode_return: +10.92 [+0.21, +17.74] · 24 paired · 0 missing · 0 excluded
episode_success: +23.3 points [-3.2, +44.6] · 24 paired · 0 missing · 0 excluded
hazard_free: +28.3 points [-2.4, +54.7] · 24 paired · 0 missing · 0 excluded
solved_every_time: +62.5 points [+24.3, +87.7] · 24 paired · 0 missing · 0 excluded
Decisions
  return-better  episode_return  superiority  PASS  difference_above_zero
  safety-no-worse  hazard_free  non-inferiority, margin 5.0 points  PASS  lower_bound_above_margin
Gate: ALLOW (exit 0)

تقرن المقارنة كل مهمة بنفسها، وقيمة كل جانب هي متوسطه على خمس جولات. وفرق العائد بوحدة العائد نفسها: يكسب المخطِّط نحو 10.9 أكثر في كل جولة، و0.21 أكثر على الأقل. وبالاقتران على الشبكات نفسها، يكفي هامش 5 نقاط لإظهار أن المخطِّط ليس أقل أمانًا، مع أن أيًّا من التشغيلين وحده لم يستطع حسم الحد الأدنى للسلامة الخاص به.

الجزء 2: سياسة يُحكم عليها من سجل

cd ../off_policy

مكتب دعم يوجّه كل تذكرة إلى نموذج صغير أو كبير. يختار المُوجِّه الحالي النموذج الصغير في 70% من المرات والكبير في 30%، عشوائيًا. وقد كتبت قاعدة جديدة: التذاكر التقنية، وتذاكر الفوترة الطويلة، تذهب إلى النموذج الكبير. لا تستطيع بعد تجربتها على تذاكر حقيقية، لكن سجل المُوجِّه القديم يستطيع أن يجيب عنها. هذا هو التقييم خارج السياسة.

السجل وملفاته

الملفما هو
data/decisions.jsonl600 قرار اتخذها المُوجِّه الحالي
make_log.pyالمحاكاة التي كتبت ذلك السجل، كي ترى كيف صُنع
router.pyالقاعدة الجديدة، وهي النظام قيد الاختبار
oloproof.yamlمجموعة الاختبار
release.yamlسياسة الإصدار

كل سطر من السجل قرار واحد:

{"id":"t001","input":{"topic":"technical","length":"short"},"expected":{"action":"small","logging_probabilities":{"small":0.7,"large":0.3},"reward":1}}

input هو ما رآه المُوجِّه، وaction ما اختاره، وlogging_probabilities الاحتمال الذي أعطاه لكل نموذج في تلك اللحظة، وreward هل حُلَّت التذكرة. ولا تجيب القاعدة الجديدة إلا باحتمالاتها الخاصة:

@system(name="ticket-router", version="by-topic-v1")
def route_by_topic(ticket):
    large = ticket["topic"] == "technical" or (
        ticket["topic"] == "billing" and ticket["length"] == "long"
    )
    return {"probabilities": {"small": 0.0 if large else 1.0, "large": 1.0 if large else 0.0}}

المُقيِّمات:

evaluators:
  - {type: off_policy_value, reward_range: [0, 1], propensity_floor: 0.3}
  - {type: off_policy_improvement, reward_range: [0, 1], propensity_floor: 0.3}

يُعاد ترجيح كل قرار مسجَّل بمقدار ما كانت القاعدة الجديدة أرجح، أو أقل ترجيحًا، لاتخاذه من المُوجِّه القديم: فالتذكرة التي أرسلها المُوجِّه القديم إلى النموذج الكبير باحتمال 0.3، والتي كانت القاعدة الجديدة سترسلها إليه دائمًا، تُحتسب 1 / 0.3 مرة، والقرار الذي ما كانت القاعدة الجديدة لتتخذه أبدًا يُحتسب صفرًا. وpropensity_floor هو أصغر احتمال أعطاه المُوجِّه القديم لأي نموذج كان يستطيع اختياره. وهو يقيّد هذا الوزن، ومن دون قيد لا تصح أي فترة.

شغّله

oloproof run
│ resolution-floor    │ off_policy_value       │ PASS                  │ lower_bound_meets_minimum                                                          │
│ better-than-current │ off_policy_improvement │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold                                                        │
│                     │                        │                       │ about 2633 more cases would decide it, if the observed values hold (3233 in total) │
│ off_policy_value       │ 0.775    │ [0.632, 0.921]  │ mean of 600 observed · 0 missing · 0 excluded │
│ off_policy_improvement │ 0.060    │ [-0.064, 0.188] │ mean of 600 observed · 0 missing · 0 excluded │

كانت القاعدة الجديدة ستحل ما يُقدَّر بـ 77.5% من التذاكر، و63.2% على الأقل بثقة 95%: فينجح الحد الأدنى البالغ 60%. أما هل تتفوق على المُوجِّه الحالي فلم يُحسم. التقدير أفضل بـ 6 نقاط، لكن الفترة تمتد من أسوأ بـ 6.4 نقاط إلى أفضل بـ 18.8، ويقول المحرك تقريبًا كم قرارًا مسجَّلًا إضافيًا سيحسم الأمر.

ذلك الاتساع هو ثمن التعلّم من اختيارات غيرك. فلا يقول شيئًا عن النموذج الكبير إلا الـ 30% من التذاكر التي صادف أن أرسلها المُوجِّه القديم إليه، وكل منها يُحتسب أكثر من ثلاث مرات. وأمران يضيّقانها: سجل أطول، ومُوجِّه قديم يستكشف أكثر.

تسجيل القرارات من نظامك الخاص

  • سجّل الاحتمالات حين يقرّر المُوجِّه، لا بعد ذلك، وسجّل احتمال كل خيار، لا احتمال الخيار المختار وحده. لا يقدّرها Oloproof: فالاحتمال المُخمَّن يجعل الفترة قائمة على التخمين.
  • لا تضع في input إلا ما رآه المُوجِّه حين قرّر، لا الخيار المختار ولا النتيجة أبدًا: فالسياسة قيد الاختبار يجب ألا تراهما.
  • إن كانت سياستك الجديدة تختار بالسحب العشوائي، فأعِد الاحتمالات التي تسحب منها، لا الخيار الواحد الذي سحبته. وسمِّ كل خيار تمامًا كما يسمّيه السجل: الخيار المكتوب بتهجئة مختلفة يُحتسب خيارًا لم يختره المُوجِّه القديم قط.
  • أبقِ قدرًا من العشوائية في التوجيه في بيئة الإنتاج. فالسياسة التي لا تختار خيارًا أبدًا لا تترك في السجل شيئًا يُحكم به على قاعدة كانت ستختاره.
  • تصف الإجابة تذاكر مثل التذاكر المسجَّلة، على امتداد الفترة الزمنية التي يغطيها السجل. وأي تغيّر في حركة طلباتك يقع خارجها.
  • السطر المشوَّه (احتمالات لا يبلغ مجموعها 1، أو خيار مختار باحتمال 0، أو مكافأة خارج reward_range) يوقف التشغيل. والقرار الذي كانت القاعدة الجديدة ستتخذه بخيار أعطاه المُوجِّه القديم أقل من الحد الأدنى، أو الذي تغيب مكافأته، يُحتسب مفقودًا ويوسّع الفترة.

ما لم يُبنَ

  • التقييم خارج السياسة لسلسلة من القرارات، حيث يغيّر اختيار كل خطوة ما يأتي بعدها. لا يُدعم إلا قرار واحد لكل حالة.
  • سجل غيّر مُوجِّهه احتمالاته وهو يتعلّم خلال النافذة.
  • استضافة البيئات أو المحاكيات أو الروبوتات أو إعادة ضبطها.

إلى أين بعد ذلك