الوحدة 10 — مشروع: التنبّؤ بالطلب مع فواصل
الوحدات التسع السابقة بنَت الأدوات. هذه الوحدة تجمعها في مشروع نهائيّ كامل: تسليم توقّع 28 يومًا للطلب على 250 صيدليّة، مع فواصل قابلة للتحويل إلى قرارات مخزون، وخطّة إعادة تدريب، وتبليغ صريح لعدم اليقين. النموذج سيُنشر في نظام حقيقيّ، ليس تمرينًا.
قراءة جدول المقارنة النهائيّ
بعد تحقّق متدرّج بخمس طيّات (وحدة 9)، الجدول الذي نُقدّمه إلى قسم القرار:
| النموذج | MAE ± σ (28 يومًا) | MASE | زمن التدريب | زمن الاستدلال |
|---|---|---|---|---|
| naïf saisonnier | 145 ± 18 | 1.00 | فوريّ | فوريّ |
| SARIMA(1,1,1)(0,1,1) | 122 ± 15 | 0.84 | 3 د | 0.2 ث |
| Prophet + fêtes | 115 ± 12 | 0.79 | 8 د | 0.5 ث |
| LightGBM + calendrier | 101 ± 9 | 0.70 | 45 ث | 0.05 ث |
| LSTM (64, L=60) | 98 ± 14 | 0.68 | 22 د (GPU) | 1.2 ث |
LightGBM هو الفائز لسلسلتنا. المبرّرات:
- الدقّة قريبة من LSTM (فارق 3٪) مع استقرار أفضل (انحراف 9 بدلًا من 14).
- زمن التدريب أقصر بـ30 مرّة، فقابل لإعادة التدريب اليوميّ بلا تكلفة.
- الاستدلال أسرع بـ24 مرّة، فيقبل الاستعلامات التفاعليّة من واجهة قسم الشراء.
- الصيانة أخفّ بكثير: لا تكوي نات PyTorch، لا بطاقات رسوميّة، لا حاجة إلى ضبط دقيق للوسائط الفائقة.
هذا الخيار مبنيّ على أدلّة، لا على ذوق. غياب هذا الجدول في مشروع تنبّؤ هو غياب المنهجيّة أصلًا.
فواصل بالكمّيّات: الحلّ العمليّ
النقاط وحدها لا تكفي لقسم الشراء. نحتاج «توقّع 200 مع فاصل [155, 260]». الطريقة المفضّلة في 2026: انحدار الكمّيّات مع LightGBM، أي تدريب نموذجين إضافيّين يُنتِجان الحدّ السفليّ والعلويّ:
import lightgbm as lgb
vars_base = dict(
n_estimators=500,
learning_rate=0.05,
num_leaves=31,
min_child_samples=20,
random_state=42,
)
# نموذج الوسط: L2 (متوسّط شرطيّ).
m_moyen = lgb.LGBMRegressor(**vars_base, objective="regression_l2")
m_moyen.fit(X_tr, y_tr)
# نموذج الكمّيّة 0.05 (الحدّ السفليّ).
m_bas = lgb.LGBMRegressor(**vars_base, objective="quantile", alpha=0.05)
m_bas.fit(X_tr, y_tr)
# نموذج الكمّيّة 0.95 (الحدّ العلويّ).
m_haut = lgb.LGBMRegressor(**vars_base, objective="quantile", alpha=0.95)
m_haut.fit(X_tr, y_tr)
# التنبّؤ يعطي ثلاث قيم لكلّ نقطة.
pred_moy = m_moyen.predict(X_te)
pred_bas = m_bas.predict(X_te)
pred_haut = m_haut.predict(X_te)
# فاصل 90٪ (95٪ - 5٪).
largeur_moy = (pred_haut - pred_bas).mean()
print(f"عرض متوسّط للفاصل 90٪ = {largeur_moy:.1f}")
نتحقّق من التغطية الفعليّة (وحدة 9): يجب أن يقع 90٪ من القيم الحقيقيّة داخل الفاصل. إن كانت التغطية 78٪، فالفاصل ثقة زائدة: نُوسّع الكمّيّات (0.03 و0.97). إن كانت 96٪، فالفاصل واسع بلا داعٍ: نُضيّق. ثلاث دورات معايرة عادةً تكفي.
خطّة إعادة التدريب
نموذج مُنشَر يُصبح قديمًا. اتّجاهات المبيعات تتحوّل، تُطرح منتجات جديدة، تتغيّر السياسات. خطّة إعادة تدريب لا تقلّ أهمّيّة عن النموذج الأوّل:
- تكرار إعادة التدريب: أسبوعيّ لمبيعاتنا. أدنى من ذلك يُخاطر بانحراف. أعلى من ذلك (يوميّ) يُضيف تعقيدًا بلا مكافأة عادة.
- إطلاق مُحفَّز: مراقبة MAE المتدحرج على النافذة الأخيرة (7 أيّام). إذا تجاوز عتبة (مثلًا 1.5 ضعف MAE المرجعيّ)، أطلق إعادة تدريب استثنائيّة.
- إعادة معايرة الفواصل: كلّ 30 يومًا. تغطية الفاصل قد تنحرف بلا انحراف واضح للنقطة.
- مراجعة يدويّة ربع سنويّة: يستعرض المهندس النتائج على أفق الربع، ويقرّر إن كانت المتغيّرات ما زالت ذات صلة أو يجب إضافة جديدة.
# مثال إعادة تدريب مبرمج (pseudo-code).
def entrainement_hebdomadaire():
df = charger_donnees_a_jour()
X, y = preparer_matrice(df)
# نُدرّب النماذج الثلاثة (وسط، كمّيّتان).
m_moyen.fit(X, y)
m_bas.fit(X, y)
m_haut.fit(X, y)
# نحفظ الأوزان ونُبلّغ المقاييس.
sauvegarder([m_moyen, m_bas, m_haut])
envoyer_rapport_metriques()
# جدولة عبر cron: chaque lundi 03:00 UTC.
تبليغ عدم اليقين إلى الأعمال
قسم الشراء لا يُريد نموذجًا بل قرارًا مساعدًا. تحويل التنبّؤ إلى قرار يستلزم عرضًا واضحًا لعدم اليقين. ثلاث قواعد للعرض:
- أظهر الفاصل، لا النقطة وحدها. جدول العرض الأدنى: [توقّع منخفض، توقّع متوسّط، توقّع مرتفع] لكلّ يوم.
- ترجم النسبة إلى وحدات. لا تقل «فاصل 90٪ بعرض 15٪»؛ قل «الطلب متوقّع بين 340 و490 وحدة». الأعداد المطلقة تُقرأ فورًا.
- صنّف الأيّام حسب الثقة. عمود إضافيّ «ثقة عالية / متوسّطة / منخفضة» يُبنى من عرض الفاصل النسبيّ. تسمح للمشتري بتخصيص اهتمامه.
مثال جدول التسليم للأسبوع القادم:
| التاريخ | التوقّع | الفاصل 90٪ | الثقة |
|---|---|---|---|
| 2027-09-01 (أحد) | 175 | [148, 210] | عالية |
| 2027-09-02 (اثنين) | 380 | [325, 445] | متوسّطة |
| 2027-09-07 (سبت) | 190 | [130, 265] | منخفضة (عيد) |