التعلم المعزز2015متوسط9 دقيقة قراءة

إعادة التجربة بالأولوية

Prioritized Experience Replay

Schaul, T. · Quan, J. · Antonoglou, I. · Silver, D. — ICLR

المشكلة

في شبكة Q العميقة يُخزِّن تجاربه السابقة في ذاكرة إعادة التشغيل ويسحب منها عيّنات عشوائية بتوزيع منتظم. لكن ليست كل التجارب متساوية الفائدة: نجاح نادر مدفون بين آلاف حالات الفشل يحصل على فرصة إعادة التشغيل ذاتها التي يحصل عليها انتقال تكراري تعلّمه الوكيل بالفعل. هذا يُهدر الحوسبة ويُبطئ التعلّم، لا سيّما حين تكون المكافآت نادرة الظهور.

الإسهام

إطار عمل يُعيد تشغيل الانتقالات المهمة بوتيرة أعلى، مستخدماً مقدار خطأ الفرق الزمني كمقياس لمدى «مفاجأة» الانتقال بالنسبة لدالة القيمة الحالية. الترتيب العشوائي يوازن بين الجشع والعشوائية للحفاظ على التنوع، وأوزان أخذ العيّنات بالأهمية تُصحِّح الانحياز الناتج عن العيّنات غير المنتظمة. عند تطبيقه على شبكة Q العميقة في ألعاب Atari، ضاعَف هذا الأسلوب سرعة التعلّم وحسَّن النتيجة النهائية في 41 من أصل 49 لعبة.

الأثر

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

تخيّل طاهية تستعد لمسابقة طهي. كل مساء تجلس لتراجع أطباق اليوم. في الوضع المعتاد تتذوّق كل طبق بالتساوي — السوفليه الممتازة والريزوتو المحترقة تنالان القدر نفسه من الانتباه.

لكن لو فكّرت بذكاء، ستبدأ بالريزوتو المحترقة — الطبق الذي فاجأها — لأن هنا بالذات يكمن الدرس الأهم. طبعاً لا تتجاهل السوفليه تماماً، بل تمرّ عليها بين الحين والآخر للتأكّد، لكنها تركّز وقتها المحدود حيث تستطيع أن تتحسّن فعلاً. هذا بالضبط ما تفعله بالأولوية.

المشكلة: العيّنات المنتظمة تُهدر الجهد

في شبكة Q العميقة الأصلية، يحتفظ الوكيل بكل انتقال (s,a,r,s)(s, a, r, s') داخل ، ثم يسحب منها دفعات تدريبية بشكل عشوائي تماماً. الفائدة من ذلك أنه يكسر الارتباط الزمني بين العيّنات المتتالية — وهذا يُحسِّن استقرار — لكن الثمن أنه يتعامل مع كل انتقال وكأن قيمته التعليمية واحدة.

تخيّل روبوتاً يتعلّم المشي: مقابل كل خطوة ناجحة تُبقيه واقفاً، هناك مئات السقطات. تلك اللحظات النادرة التي نجح فيها فعلاً تحمل أغنى إشارة تعلّم، لكنها تُسحب بالاحتمال ذاته الذي تُسحب به السقطات المكرّرة التي لم يعد فيها جديد. في تجربة Blind Cliffwalk الواردة في الورقة، يحتاج الوكيل إلى عدد أُسّي من العيّنات (2n2^n) ليعثر على المسار الوحيد الذي يحمل — أما مع إعادة التشغيل بالأولوية فيصبح التعلّم ممكناً عملياً.

افتح في المختبر
لاحظ كيف يوزّع السحب المنتظم الاهتمام بالتساوي على جميع الانتقالات، بينما يركّز السحب بالأولوية على الانتقالات التي يكون خطؤها كبيراً (أي المُفاجِئة). اضغط «خطوة» لسحب عيّنات.
تستيقظ التجربة عند وصولك…

الفكرة المحورية: خطأ الفرق الزمني يقيس المفاجأة

السؤال الجوهري: كيف نحدّد أيّ الانتقالات تستحق اهتماماً أكبر؟ لو كان لدينا مقياس مثالي لقسنا التحسّن الفعلي في أداء الوكيل لو أعاد دراسة انتقال بعينه. هذا المقياس المثالي مستحيل حسابه مباشرةً، لكن يوجد بديل عملي قوي وهو : الفارق بين ما توقّعه الوكيل وما حدث فعلاً.

فكّر فيه كمقياس للمفاجأة. حين يتوقّع الوكيل أن حالة ما تساوي 5 نقاط ثم يكتشف أنها تساوي 15، يكون خطأ الفرق الزمني كبيراً — أي أن هذا الانتقال يحمل درساً ثميناً. أما حين يتطابق التوقع مع الواقع فيقترب الخطأ من الصفر — ببساطة لأن الوكيل يعرف هذا الدرس مسبقاً.

صيغة خطأ الفرق الزمني لأي انتقال هي:

δ=R+γmaxaQ(s,a)Q(s,a)\delta = R + \gamma \max_{a'} Q(s', a') - Q(s, a)
خطأ الفرق الزمني — إشارة «المفاجأة»R = المكافأة الفورية · γ max Q(s',a') = أفضل قيمة مستقبلية مخصومة · Q(s,a) = ما توقّعه الوكيل · |δ| = مقدار خطأ التوقع — كلما كبرت القيمة زاد ما يمكن تعلّمه

أبسط فكرة تتبادر إلى الذهن هي الاختيار الجشع: أعِد دائماً تشغيل الانتقال الذي يملك أكبر |δ|. هذا يعمل بشكل ممتاز على المسائل الصغيرة — لكنه يقع في ثلاث مشكلات جدّية على أرض الواقع:

  • أولويات قديمة: خطأ الفرق الزمني لا يُحدَّث لانتقالٍ ما إلا عند إعادة تشغيله، فقد يبقى انتقال بخطأ أوّلي صغير مُهمَلاً للأبد، رغم أن تغيّرت وأصبح ذلك الانتقال مفاجئاً فعلاً.
  • حساسية تجاه الضوضاء: مكافأة مشوّشة أو خطأ ناتج عن قد يرفع |δ| فجأةً، فينشغل الوكيل بانتقالات لا قيمة تعليمية حقيقية فيها.
  • ضياع التنوّع: الوكيل يظلّ يعيد تشغيل الانتقالات ذاتها ذات الخطأ المرتفع، فيحدث عليها ويتجاهل بقيّة التوزيع.
افتح في المختبر
حرّك المنزلق لتغيير المكافأة الفعلية لانتقالٍ ما، ولاحظ كيف يتغيّر خطأ الفرق الزمني (المفاجأة). كلما كبر |δ| زاد ما يمكن للوكيل أن يتعلّمه من هذا الانتقال.
تستيقظ التجربة عند وصولك…

الترتيب العشوائي: التوازن بين الجشع والتنوع

الحل هو أن نجعل احتمال يزداد مع ارتفاع الأولوية، لكن دون أن يصل إلى الصفر لأي انتقال — حتى الانتقال الأقل أولوية يظل لديه فرصة أن يُسحب. احتمال سحب الانتقال ii يُحسب كالتالي:

P(i)=piαkpkαP(i) = \frac{p_i^{\alpha}}{\sum_k p_k^{\alpha}}
الترتيب العشوائي — توزيع السحبp_i = أولوية الانتقال i · α = يتحكم في مقدار الترتيب بالأولوية · α = 0 يعطي عيّنات منتظمة · α = 1 يعطي ترتيباً تناسبياً كاملاً

تطرح الورقة طريقتين لتعريف الأولوية pip_i:

الطريقة التناسبية: pi=δi+ϵp_i = |\delta_i| + \epsilon، حيث ϵ\epsilon ثابت صغير يضمن ألا يصل الاحتمال إلى الصفر. هنا يتناسب احتمال السحب طردياً مع حجم خطأ الفرق الزمني.

الطريقة الترتيبية: pi=1/rank(i)p_i = 1/\text{rank}(i)، حيث تُرتَّب الانتقالات حسب δi|\delta_i|. هذه الطريقة تنتج توزيع ، وميزتها أنها أكثر متانة أمام القيم الشاذة لأنها تعتمد على ترتيب الانتقالات فقط لا على القيم الفعلية للأخطاء.

لتوضيح الفرق: تخيّل قائمة أغانٍ. الأسلوب التناسبي يشبه تشغيل كل أغنية بما يتناسب مع عدد مرات استماعها — فتُهيمن الأغنية الرائجة. أما الأسلوب الترتيبي فيشبه القاعدة: «شغّل الأولى أكثر، ثم الثانية أقل، ثم الثالثة…» — لا يهمّ كم مرة سُمعت كل أغنية، المهم ترتيبها بين الأغاني.

افتح في المختبر
غيّر قيمة α وقارن كيف يوزّع كل من الأسلوب التناسبي والأسلوب الترتيبي احتمالات السحب على انتقالات ذات أخطاء فرق زمني متفاوتة.
تستيقظ التجربة عند وصولك…

تصحيح الانحياز: أوزان أخذ العيّنات بالأهمية

حين نسحب العيّنات بشكل غير منتظم، نُدخل في : لم نعد نحسب التدرّج المتوقع بالنسبة للتوزيع الحقيقي للبيانات. إذا تركنا هذا الانحياز دون علاج، ستتقارب دالة القيمة نحو حلٍّ خاطئ. والحل هو — نُعيد ترجيح كل تحديث بحيث نلغي التشويه الذي أحدثه السحب بالأولوية.

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

wi=(1N1P(i))βw_i = \left(\frac{1}{N} \cdot \frac{1}{P(i)}\right)^{\beta}
وزن أخذ العيّنات بالأهمية — تصحيح الانحيازN = حجم ذاكرة إعادة التشغيل · P(i) = احتمال سحب الانتقال i · β = يتحكم في مقدار التصحيح · β = 1 يعطي تصحيحاً كاملاً · β < 1 يعطي تصحيحاً جزئياً

هناك حيلة ذكية تستغل طبيعة التدريب نفسه: في المراحل المبكّرة تتغير ودالة القيمة بسرعة على أي حال، فانحياز بسيط لا يُحدث ضرراً يُذكر. لذلك يبدأ المؤلفون بقيمة منخفضة لـβ (تصحيح جزئي) ويرفعونها تدريجياً حتى تصل إلى 1 مع نهاية التدريب. النتيجة: في البداية نحصل على ترتيب قوي بالأولوية مع تصحيح ضئيل يسرّع التعلّم، وعند اقتراب يكتمل التصحيح لضمان حلٍّ صحيح.

الأوزان تُقسم أيضاً على 1/maxiwi1/\max_i w_i بحيث لا تُكبّر أي تحديث — فقط تُصغّره. هذا يمنع عدم الاستقرار الذي قد تسبّبه خطوات تدرّج ضخمة. والجميل أن هذه التسوية تتآزر مع تدريج β: كلما اقترب β من 1 كبر مقام التسوية، فينخفض حجم الخطوة الفعلي تلقائياً — وكأنه تخفيض ضمني لـ.

افتح في المختبر
حرّك منزلق تقدّم التدريب ولاحظ كيف تتغيّر أوزان أخذ العيّنات بالأهمية مع تدريج β. في البداية: ترتيب قوي بالأولوية مع تصحيح ضئيل. في النهاية: تصحيح كامل للانحياز.
تستيقظ التجربة عند وصولك…

تجميع القطع معاً

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

في كل دورة تعلّم، تسير العملية كالتالي:

  • تُخزَّن الانتقالات الجديدة بـأقصى أولوية حتى تُشاهَد مرة واحدة على الأقل
  • تُسحب دفعة تدريبية وفق توزيع الأولوية P(i)P(i)
  • يُحسب خطأ الفرق الزمني وأوزان أخذ العيّنات بالأهمية لكل انتقال مسحوب
  • تُحدَّث أوزان الشبكة باستخدام أخطاء الفرق الزمني المرجَّحة
  • تُحدَّث أولويات الانتقالات المسحوبة بناءً على أخطائها الجديدة
افتح في المختبر
انقر على العقد الورقية لتغيير الأولويات، ولاحظ كيف تنتقل التحديثات صعوداً عبر شجرة المجموع. اضغط «سحب» لترى كيف يعمل السحب التناسبي على أرض الواقع.
تستيقظ التجربة عند وصولك…

الفكرة في شيفرة برمجية

إعادة التشغيل بالأولوية — منطق السحب والتحديث الأساسيpython

مبسَّط لإظهار الفكرة — ليس التنفيذ الحقيقي.

import numpy as np

class PrioritizedReplayBuffer:
    """ترتيب تناسبي مبسّط بالأولوية مع أخذ العيّنات بالأهمية."""

    def __init__(self, capacity, alpha=0.6, beta_start=0.4):
        self.capacity = capacity
        self.alpha = alpha          # مقدار الترتيب بالأولوية: 0=منتظم، 1=كامل
        self.beta = beta_start      # تصحيح IS: يُدرَّج إلى 1 أثناء التدريب
        self.priorities = np.zeros(capacity, dtype=np.float32)
        self.buffer = [None] * capacity
        self.pos = 0                # موضع الكتابة الدائري
        self.size = 0

    def add(self, transition, td_error=None):
        """خزّن بأقصى أولوية لضمان إعادة تشغيل الانتقالات الجديدة مرة واحدة على الأقل."""
        priority = self.priorities[:self.size].max() if self.size > 0 else 1.0
        if td_error is not None:
            priority = (abs(td_error) + 1e-6) ** self.alpha
        self.buffer[self.pos] = transition
        self.priorities[self.pos] = priority
        self.pos = (self.pos + 1) % self.capacity
        self.size = min(self.size + 1, self.capacity)

    def sample(self, batch_size):
        """اسحب عيّنات بما يتناسب مع الأولوية، وأعِد أوزان IS."""
        probs = self.priorities[:self.size] ** self.alpha
        probs /= probs.sum()                            # P(i)

        indices = np.random.choice(self.size, batch_size, p=probs, replace=False)

        # أوزان أخذ العيّنات بالأهمية: w_i = (N * P(i))^(-β) / max(w)
        weights = (self.size * probs[indices]) ** (-self.beta)
        weights /= weights.max()                        # تسوية

        transitions = [self.buffer[i] for i in indices]
        return transitions, indices, weights

    def update_priorities(self, indices, td_errors):
        """حدّث الأولويات بأخطاء الفرق الزمني الجديدة بعد خطوة التعلّم."""
        for idx, td_err in zip(indices, td_errors):
            self.priorities[idx] = (abs(td_err) + 1e-6) ** self.alpha

    def anneal_beta(self, progress):
        """درِّج β خطياً نحو 1.0 مع تقدّم التدريب."""
        self.beta = 0.4 + progress * (1.0 - 0.4)

# الاستخدام: بعد حساب أخطاء الفرق الزمني في حلقة تحديث DQN،
# اضرب كل تدرّج بوزن IS:
#   loss = (weights * td_errors ** 2).mean()

النتائج على معيار Atari

اختبر المؤلفون إعادة التشغيل بالأولوية على 49–57 لعبة Atari بمعاملات فائقة موحّدة عبر جميع الألعاب — بدون أي ضبط مخصّص لكل لعبة. وجاءت النتائج كالتالي:

  • إضافة إعادة التشغيل بالأولوية إلى حسّنت النتائج في 41 من 49 لعبة، ورفعت الأداء المعياري الوسيط من 48% إلى 106%.
  • عند دمجها مع Double DQN ارتفع الوسيط من 111% إلى 128%، والمتوسط من 418% إلى 551%.
  • سرعة التعلّم تضاعفت تقريباً: الوكيل الذي يستخدم الأولوية وصل إلى الأداء النهائي للنموذج الأساسي في نحو 40–47% فقط من زمن التدريب.
  • الألعاب ذات (مثل Frostbite وBattlezone وZaxxon) حقّقت أكبر مكاسب، لأن الترتيب بالأولوية يُبرز بسرعة تلك الانتقالات النادرة التي تحمل معلومات قيّمة.
افتح في المختبر
التحسّن في النتيجة المعيارية بعد إضافة إعادة التشغيل بالأولوية إلى Double DQN عبر ألعاب Atari المختلفة. لاحظ أن أغلب الألعاب حقّقت مكاسب واضحة.
تستيقظ التجربة عند وصولك…

لماذا أهمّ هذا العمل

  1. 2013

    شبكة Q العميقة (الأصلية)

    قدّمت آلية إعادة تشغيل التجارب لتحقيق استقرار التدريب في التعلّم المعزّز العميق، مع سحب منتظم من ذاكرة تتسع لمليون انتقال.

  2. 2015

    إعادة التجربة بالأولوية

    استبدلت السحب المنتظم بسحب قائم على الأولوية باستخدام خطأ الفرق الزمني. ضاعفت سرعة التعلّم وحققت أفضل أداء على ألعاب Atari.

  3. 2016

    Double DQN + إعادة التشغيل بالأولوية

    الجمع بين إعادة التشغيل بالأولوية و Double Q-learning أنتج مكاسب متكاملة — مما أثبت أن التحسينات مستقلة عن بعضها.

  4. 2017

    Rainbow DQN

    جمعت ستة تحسينات لشبكة Q العميقة — منها إعادة التشغيل بالأولوية — في وكيل واحد تفوّق بفارق كبير على كل مكوّن بمفرده.

المرجعSchaul, Quan, Antonoglou, Silver. Prioritized Experience Replay. ICLR, 2016.

مصطلحات هذه الورقة