أساسيات OpenAI Gym : الجزء الأول

بعد الحديث كثيرًا عن المفاهيم النظرية للتعلم المعزز (RL) في المقالين الأولين من سلسلة التعلم المعزز في هذا المقال ، ستتعلم أساسيات OpenAI Gym ، وهي مكتبة تستخدم لتوفير واجهة برمجة تطبيقات موحدة لوكيل التعلم المعزز والعديد من بيئات التعلم المعزز. 

مكونات الوكيل 

هناك عدة كيانات في رؤية التعلم المعزز للعالم:

الوكيل: الشيء أو الشخص الذي يقوم بدور فعال. في الممارسة العملية ، الوكيل هو جزء من التعليمات البرمجية التي تنفذ بعض -السياسات. بشكل أساسي ، تحدد هذه السياسة الإجراء المطلوب في كل خطوة زمنية ، مع مراعاة ملاحظاتنا.

البيئة: نموذج ما للعالم خارج عن الوكيل ويتحمل مسئولية توفير الملاحظات وإعطاء المكافآت. تغير البيئة حالتها بناءً على تصرفات الوكيل.

دعنا نستكشف كيف يمكن تنفيذ كليهما في Python في موقف بسيط. سنحدد البيئة التي ستمنح الوكيل مكافآت عشوائية لعدد محدود من الخطوات ، بغض النظر عن تصرفات الوكيل. هذا السيناريو ليس مفيدًا جدًا ، لكنه سيسمح لنا بالتركيز على طرق محددة في كل من فئات البيئة والوكلاء. لنبدأ بالبيئة:

البيئة

class Environment:
    def __init__(self):
        self.steps_left = 10

في الكود السابق ، سمحنا للبيئة بتهيئة حالتها الداخلية. في حالتنا ، الحالة هي مجرد عداد يحد من عدد الخطوات الزمنية التي يُسمح للوكيل باتخاذها للتفاعل مع البيئة.

    def get_observation(self) -> List[float]:
        return [0.0, 0.0, 0.0]

من المفترض أن تعيد get_observation () ملاحظة البيئة الحالية إلى الوكيل. وعادة ما يتم تنفيذه كإحدى وظائف الحالة الداخلية للبيئة. 

إذا كنت مهتمًا بما هو المقصود بـ-> List [float] ، فهذا مثال على التعليقات التوضيحية  (Type aliases) في Python ، والتي تم تقديمها في Python 3.5. يمكنك معرفة المزيد من خلال هذا الرابط  في مثالنا ، يكون متجه الملاحظة دائمًا صفراً ، حيث أن البيئة في الأساس ليس لها حالة داخلية.

    def get_actions(self) -> List[int]:
        return [0, 1]

تسمح get_actions () للوكيل بالاستعلام عن مجموعة الإجراءات التي يمكنه تنفيذها. عادةً ، لا تتغير مجموعة الإجراءات التي يمكن للوكيل تنفيذها بمرور الوقت ، ولكن يمكن أن تصبح بعض الإجراءات مستحيلة في حالات مختلفة (على سبيل المثال ، ليست كل حركة ممكنة في أي موضع من لعبة tic-tac-toe). في مثالنا المبسط ، لا يوجد سوى إجراءين يمكن للوكيل تنفيذهما ، ويتم ترميزهما باستخدام الأعداد الصحيحة 0 و 1.

    def is_done(self) -> bool:
        return self.steps_left == 0

تشير الوظيفة المساعدة السابقة إلى نهاية الحلقة إلى الوكيل. كما رأيت في المقال الثاني ، يتم تقسيم سلسلة تفاعلات الوكيل -البيئة إلى سلسلة من الخطوات تسمى الحلقات. يمكن أن تكون الحلقات محدودة ، كما هو الحال في لعبة الشطرنج ، أو لانهائية ، مثل مهمة Voyager 2 (مسبار فضائي شهير تم إطلاقه منذ أكثر من 40 عامًا وسافر خارج نظامنا الشمسي). لتغطية كلا السيناريوهين ، توفر لنا البيئة طريقة لاكتشاف متى تنتهي الحلقة ولا توجد طريقة للتواصل معها بعد الآن.

    def action(self, action: int) -> float:
        if self.is_done():
            raise Exception("Game is over")
        self.steps_left -= 1
        return random.random()

 الوظيفة المساعدة  action() هي القطعة المركزية في وظائف البيئة. يقوم بأمرين – يتعامل مع عمل الوكيل ويعيد مكافأة هذا الإجراء. في مثالنا ، المكافأة عشوائية ويتم تجاهل عملها. بالإضافة إلى ذلك ، نقوم بتحديث عدد الخطوات ونرفض متابعة الحلقات التي انتهت.

الوكيل

الآن عند النظر إلى جزء الوكيل ، فإنه أبسط بكثير ويتضمن طريقتين فقط: المُنشئ والطريقة التي تؤدي خطوة واحدة في البيئة:

class Agent:
    def __init__(self):
        self.total_reward = 0.0

في المنشئ ، نقوم بتهيئة العداد الذي سيحتفظ بالمكافأة الإجمالية المتراكمة بواسطة الوكيل أثناء الحلقة.

    def step(self, env: Environment):
        current_obs = env.get_observation()
        actions = env.get_actions()
        reward = env.action(random.choice(actions))
        self.total_reward += reward

تقبل وظيفة الخطوة البيئة كوسيطة وتسمح للوكيل بتنفيذ الإجراءات التالية:

  • راقب البيئة
  • اتخذ قرارًا بشأن الإجراء المطلوب اتخاذه بناءً على الملاحظات
  • أرسل الإجراء إلى  البيئة
  • احصل على المكافأة للخطوة الحالية

في مثالنا ، الوكيل يتجاهل الملاحظات التي تم الحصول عليها أثناء عملية صنع القرار بشأن الإجراء الذي يجب اتخاذه. بدلاً من ذلك ، يتم تحديد كل إجراء بشكل عشوائي. القطعة الأخيرة من الكود، الذي يشغل حلقة واحدة:

if __name__ == "__main__":
    env = Environment()
    agent = Agent()
    while not env.is_done():
        agent.step(env)
    print("Total reward got: %.4f" % agent.total_reward)

يوضح بساطة الكود السابق المفاهيم الأساسية المهمة التي تأتي من نموذج RL. يمكن أن تكون البيئة نموذجًا فيزيائيًا معقدًا للغاية ، ويمكن للوكيل بسهولة أن يكون شبكة عصبية كبيرة (NN) تنفذ أحدث خوارزمية للتعلم المعزز، لكن النمط الأساسي سيبقى كما هو – في كل خطوة ، سيأخذ الوكيل بعض الملاحظات من البيئة ، ويقوم بإجراء حساباته ، ويحدد الإجراء المطلوب اتخاذه. ستكون نتيجة هذا الإجراء مكافأة وملاحظة جديدة.

قد تسأل ، إذا كان النمط هو نفسه ، فلماذا نحتاج إلى كتابته من الصفر؟ ماذا لو تم تنفيذه بالفعل من قبل شخص ما ويمكن استخدامه كمكتبة؟ بالطبع ، مثل إطارات العمل هذه موجوة. الآن ، دعنا ندخل في تفاصيل واجهة OpenAI Gym API ، والتي توفر لنا الكثير من البيئات ، من البيئات السهلة إلى الصعبة.

واجهة برمجة تطبيقات OpenAI Gym

تم تطوير مكتبة Python المسماة Gym من قبل  OpenAI . الهدف الرئيسي من Gym هو توفير مجموعة غنية من البيئات لتجارب RL باستخدام واجهة موحدة. لذلك ، ليس من المستغرب أن تكون الطبقة المركزية في المكتبة هي بيئة تسمى Env. تعرض هذه الفئة العديد من الأساليب والمجالات التي توفر المعلومات المطلوبة حول إمكانياتها. على مستوى عالٍ ، توفر كل بيئة هذه الأجزاء من المعلومات والوظائف:

  • مجموعة من الإجراءات التي يُسمح بتنفيذها في البيئة. تدعم GYM كلاً من الإجراءات المنفصلة والمستمرة ، بالإضافة إلى الجمع بينهما
  • شكل وحدود الملاحظات التي توفرها البيئة للوكيل
  •  وظيفة مساعدة تسمى الخطوة (step ) لتنفيذ إجراء ، والتي تُرجع الملاحظة الحالية والمكافأة والإشارة إلى انتهاء الحلقة
  •  وظيفة مساعدة تسمى إعادة الضبط (reset) ، والتي تعيد البيئة إلى حالتها الأولية وتحصل على الملاحظة الأولى

دعنا الآن نتحدث عن مكونات البيئة هذه بالتفصيل.

فضاء الإجراء 

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

الإجراء المستمر له قيمة مرتبطة به ، على سبيل المثال ، عجلة القيادة ، والتي يمكن تدويرها بزاوية معينة ، أو دواسة الوقود ، والتي يمكن الضغط عليها بمستويات مختلفة من القوة. يتضمن وصف الإجراء المستمر حدود القيمة التي يمكن أن يمتلكها الإجراء. في حالة وجود عجلة قيادة ، يمكن أن تتراوح من -720 درجة إلى 720 درجة. بالنسبة إلى دواسة الوقود ، عادة ما تكون من 0 إلى 1.

بالطبع ، نحن لسنا مقيدين بفعل واحد. يمكن أن تتخذ البيئة إجراءات متعددة ، مثل الضغط على عدة أزرار في وقت واحد أو توجيه العجلة والضغط على دواستين (المكابح والمُسرع). لدعم مثل هذه الحالات ، تحدد Gym فئة حاوية خاصة تسمح بتداخل العديد من فضاءات الإجراء في إجراء واحد موحد.

فضاء المراقبة

كما هو مذكور في المقال الأول، الملاحظات هي أجزاء من المعلومات التي توفرها البيئة للوكيل ، في كل طابع زمني ، إلى جانب المكافأة. يمكن أن تكون الملاحظات بسيطة مثل مجموعة من الأرقام أو معقدة مثل العديد من الموترات متعددة الأبعاد التي تحتوي على صور ملونة من عدة كاميرات.

 يمكن أن تكون الملاحظة منفصلة ، مثل فضاء الإجراء. مثال على فصاء المراقبة المنفصلة هو المصباح الكهربائي ، والذي يمكن أن يكون في حالتين – تشغيل أو إيقاف تشغيل ، يُعطى لنا كقيمة منطقية.

لذلك ، يمكنك أن ترى التشابه بين الأفعال والملاحظات ، وكيف وجدوا تمثيلهم في  Gym. لنلقِ نظرة على مخطط:

يتضمن فئة Space  على  وظيفتين مساعدتين تهمنا : 

  • sample (): يؤدي هذا إلى إرجاع عينة عشوائية من الفضاء 
  • contains(x): يتحقق هذا مما إذا كانت الوسيطة x تنتمي إلى مجال الفضاء

كلتا الوظيفتين مجردة ومعاد تنفيذها في كل فئة من فئات الفضاء الفرعية:

فئة Discrete 

تمثل الفئة Discrete  مجموعة حصرية متبادلة من العناصر ، مرقمة من 0 إلى n – 1. حقلها الوحيد ، n ، هو عدد العناصر التي تصفها. على سبيل المثال ، يمكن استخدام Discrete (n = 4) لفضاء إجراء من أربعة اتجاهات للتحرك في [يسار ، يمين ، أعلى ، أو أسفل].

فئة BOX

تمثل فئة Box موترًا من n-أبعاد للأرقام المصنفة كفئات [منخفضة ، عالية]. على سبيل المثال ، يمكن أن يكون هذا دواسة تسريع بقيمة واحدة بين 0.0 و 1.0 ، والتي يمكن ترميزها بواسطة 

Box(low=0.0, high=1.0, shape=(1,), dtype=np.float32) 

مثال آخر على Box يمكن أن يكون ملاحظة شاشة أتاري ، وهي صورة RGB (أحمر وأخضر وأزرق) بحجم 210 × 160: 

Box(low=0, high=255, shape=(210, 160, 3), dtype=np.uint8)

في هذه الحالة ، تكون shape عبارة عن مجموعة مكونة من ثلاثة عناصر: البعد الأول هو ارتفاع الصورة ، والثاني هو العرض ، والثالث يساوي 3 ، وكلها تتوافق مع ثلاثة مستويات لونية للأحمر والأخضر والأزرق على التوالي. إذن ، في المجموع ، كل ملاحظة عبارة عن موتر (Tensor) ثلاثي الأبعاد.

فئة Tuple

الجزء الفرعي الأخير من Space هو فئة Tuple ، والتي تسمح لنا بدمج العديد من حالات فئة Space معًا. وهذا يمكننا من إنشاء فضاءات إجراء ومراقبة بأي تعقيد نريده.

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

 إلى جانب عناصر التحكم الأساسية هذه ، تحتوي السيارة على عناصر تحكم إضافية منفصلة ، مثل إشارة الانعطاف (التي يمكن أن تكون متوقفة عن التشغيل أو لليمين أو لليسار) أو بوق (تشغيل أو إيقاف). لدمج كل هذا في فئة واحدة لمواصفات فضاء الإجراء ، يمكننا إنشاء :

Tuple(spaces=(Box(low=-1.0, high=1.0, shape=(3,), dtype=np.float32), Discrete(n=3),Discrete(n=2)))

فئات أخرى

هناك فئات فرعية أخرى من الفضاء تم تحديدها في GYM، ولكن الثلاثة السابقة هي الأكثر فائدة. تطبق جميع الفئات الفرعية الوظائف المساعده sample() و contains() .

تقوم الوظيفة  sample () بإحضار عينة عشوائية مطابقة لفئة الفضاء و المعايير. هذا مفيد في الغالب لفضاءات الإجراء ، عندما نحتاج إلى اختيار الإجراء العشوائي.

تتحقق  contains()  من أن الحجج المدخلة  تتوافق مع معايير الفضاء ، ويتم استخدامها في الأجزاء الداخلية من gym  للتحقق من تصرفات الوكيل. على سبيل المثال ، يُرجع Discrete.sample () عنصرًا عشوائيًا من فئة Discrete، وسيكون Box.sample () موترًا عشوائيًا بأبعاد وقيم مناسبة تقع داخل نطاق فئة Box.

تحتوي كل بيئة على عضوين من النوع Space:  هما action_space وobservation_space. هذا يسمح لنا بإنشاء كود عام يمكن أن يعمل مع أي بيئة.

 بالطبع ، يختلف التعامل مع وحدات البكسل على الشاشة عن التعامل مع الملاحظات المنفصلة (كما في الحالة السابقة ، قد نرغب في معالجة الصور باستخدام طبقات تلافيفية أو بطرق أخرى من صندوق أدوات التبصير الحاسوبي) ؛ لذلك ، في معظم الأحيان ، هذا يعني تحسين الكود لبيئة معينة أو مجموعة من البيئات ، لكن gym لا تمنعنا من كتابة كود عام.

البيئة

يتم تمثيل البيئة في gym من قبل فئة Env ، كما ذكرنا سابقًا ، والتي تضم الأعضاء التاليين:

  • action_space: هذا هو مجال فئة Space ويوفر مواصفات للإجراءات المسموح بها في البيئة.
  • Observation_space: يحتوي هذا الحقل على نفس فئة الفضاء ، لكنه يحدد الملاحظات التي توفرها البيئة.
  • reset() : يؤدي هذا إلى إعادة تعيين البيئة إلى حالتها الأولية ، وإرجاع متجه الملاحظة الأولي.
  • step (): تسمح هذه الطريقة للوكيل باتخاذ الإجراء وإرجاع معلومات حول نتيجة الإجراء – الملاحظة التالية والمكافأة المحلية وعلامة نهاية الحلقة. هذه الطريقة معقدة بعض الشيء.

توجد طرق مساعدة إضافية في فئة Env ، مثل render () ، والتي تتيح لنا الحصول على الملاحظة في شكل يمكننا مراقبته ، لكننا لن نستخدمها. يمكنك العثور على القائمة الكاملة في وثائق Gym ، لكن دعنا نركز على الوظائف المساعدة الأساسية لـ Env: reset() و step().

لقد رأينا حتى الآن كيف يمكن للكود الخاص بنا الحصول على معلومات حول إجراءات البيئة وملاحظاتها ، لذا فأنت الآن بحاجة إلى التعرف على الإجراءات نفسها. يتم إجراء الاتصالات مع البيئة عبر الخطوة وإعادة التعيين.

الوظيفة المساعدة reset()

نظرًا لأن إعادة التعيين أبسط بكثير ، فسنبدأ بها. لا تحتوي طريقة إعادة التعيين على أي حجج؛ يوجه البيئة لإعادة ضبط حالتها الأولية والحصول على الملاحظة الأولية. لاحظ أنه يجب عليك استدعاء reset () بعد إنشاء البيئة. لأنه قد يكون لتواصل الوكيل مع البيئة نهاية (مثل شاشة “انتهت اللعبة”). تسمى هذه الجلسات بالحلقات ، وبعد نهاية الحلقة ، يحتاج الوكيل للبدء من جديد. القيمة المرجعة بهذه الطريقة هي الملاحظة الأولى للبيئة.

الوظيفة المساعدة step()

وظيفة step()  هي القطعة المركزية في وظائف البيئة. تقوم بعدة أشياء في الخطوة الواحدة وهي كالتالي:

  • إخبار البيئة بالإجراء الذي سننفذه في الخطوة التالية
  • الحصول على الملاحظة الجديدة من البيئة بعد هذا الإجراء
  • الحصول على المكافأة التي حصل عليها الوكيل بهذه الخطوة
  • الحصول على دلالة على أن الحلقة قد انتهت

يتم تمرير العنصر الأول (الإجراء) باعتباره الحجة الوحيدة لهذه الطريقة ، ويتم إرجاع الباقي بواسطة step (). على وجه التحديد ، هذه مجموعة (Python tuple وليس فئة Tuple التي ناقشناها في القسم السابق) مكونة من أربعة عناصر (الملاحظة ، والمكافأة ، والانجاز ، والمعلومات). لديهم هذه الأنواع والمعاني:

  • الملاحظة (observation): هذا متجه NumPy أو مصفوفة مع بيانات المراقبة.
  • المكافأة (reward): هذه هي القيمة العائمة للمكافأة.
  • الإنجاز(done): هذا مؤشر منطقي ، ويكون صحيحًا عند انتهاء الحلقة.
  • المعلومات (info): يمكن أن يكون هذا أي شيء خاص بالبيئة مع معلومات إضافية عن البيئة. الممارسة المعتادة هي تجاهل هذه القيمة في طرق RL العامة (مع مراعاة التفاصيل المحددة لبيئة معينة).

نستدعي الوظيفة المساعدة step () مع إجراء يتم تنفيذه حتى تصبح علامة “done” الخاصة بهذه الوظيفة True. ثم يمكننا استدعاء reset()  للبدء من جديد. هناك قطعة واحدة مفقودة – كيف نصنع بيئات Env في المقام الأول.

إنشاء البيئة

كل بيئة لها اسم فريد في هيئة EnvironmentName-vN ، حيث N هو الرقم المستخدم للتمييز بين الإصدارات المختلفة لنفس البيئة (على سبيل المثال ، عند إصلاح بعض الأخطاء أو إجراء بعض التغييرات الرئيسية الأخرى). لإنشاء بيئة ، توفر حزمة gym  وظيفة make (env_name) ،  وحجتها الوحيدة هي اسم البيئة في شكل سلسلة.

إصدار Gym 0.13.1 يحتوي على 859 بيئة بأسماء مختلفة. بالطبع ، كل هذه ليست بيئات فريدة ، حيث تتضمن هذه القائمة جميع إصدارات البيئة. بالإضافة إلى ذلك ، يمكن أن يكون لنفس البيئة اختلافات مختلفة في الإعدادات  وفضاءات الملاحظات. على سبيل المثال ، تحتوي لعبة Atari Game Breakout على أسماء البيئة التالية:

  • Breakout-v0 ، Breakout-v4: الإصدار الأصلي مع موضع أولي عشوائي واتجاه الكرة
  • BreakoutDeterministic-v0، BreakoutDeterministic-v4: نفس الموضع الأولي ومتجه السرعة للكرة
  • BreakoutNoFrameskip-v0 ، BreakoutNoFrameskip-v4: مع كل إطار معروض على الوكيل
  • Breakout-ram-v0 ، Breakout-ram-v4: مع ملاحظة ذاكرة محاكاة Atari الكاملة (128 بايت) بدلاً من بكسل الشاشة
  • Breakout-ramDeterministic-v0، Breakout-ramDeterministic-v4.0

في المجموع ، هناك 12 بيئة للعبة Breakout. في حالة عدم رؤيتك لها من قبل ، إليك لقطة شاشة من طريقة لعبها:

حتى بعد إزالة هذه التكرارات ، يأتي Gym 0.13.1 بقائمة رائعة من 154 بيئة فريدة ، والتي يمكن تقسيمها إلى عدة مجموعات:

  • مشاكل التحكم الكلاسيكية: هذه هي مهام الألعاب التي تُستخدم في نظرية التحكم الأمثل وأوراق التعلم المعزز كمعايير أو عروض توضيحية. عادة ما تكون بسيطة ، مع ملاحظة منخفضة الأبعاد و فضاءات إجراء ، لكنها مفيدة تحقيقات سريعة عند تنفيذ الخوارزميات. فكر فيهم على أنهم “MNIST for RL” (MNIST هي مجموعة بيانات للتعرف على أرقام الكتابة اليدوية من Yann LeCun ).
  • أتاري 2600: هذه ألعاب من منصة الألعاب الكلاسيكية من السبعينيات. هناك 63 لعبة فريدة.
  • الخوارزمية: هذه هي المشاكل التي تهدف إلى أداء مهام حسابية صغيرة ، مثل نسخ التسلسل الملحوظ أو إضافة الأرقام.
  • ألعاب الطاولة: هذه هي ألعاب Go و Hex.
  • Box2D: هذه هي البيئات التي تستخدم محاكي فيزياء Box2D لتعلم المشي أو التحكم في السيارة.
  • MuJoCo: هذا محاكي فيزيائي آخر يستخدم للعديد من مشاكل التحكم المستمر.
  • ضبط المعييار: يستخدم هذا التعلم المعزز لتحسين معايير الشبكة العصبية.
  • نص: هذه عبارة عن بيئات نصية بسيطة لعالم الشبكة.
  • PyGame: هذه هي عدة بيئات تم تنفيذها باستخدام محرك PyGame.
  • دوم: هذه تسع ألعاب مصغرة مبنية على ViZDoom.

يمكن العثور على القائمة الكاملة للبيئات على هنا . تتوفر مجموعة أكبر من البيئات في OpenAI Universe (التي تم إيقافها حاليًا بواسطة OpenAI)  يقوم OpenAI Universe بتوسيع واجهة Gym API ، لكنه يتبع نفس مبادئ التصميم والنموذج. 

إضافة تعليق