بعد أن تعرفنا على openAi GYM في المقال السابق ، سنقوم في هذا المقال بتدريب إحدى البيئات المسماة CartPole
بيئة CartPole
دعنا نطبق معرفتنا ونستكشف واحدة من أبسط بيئات RL التي يوفرها Gym.
import gym e = gym.make('CartPole-v0')
هنا ، قمنا باستيراد حزمة gym وخلقنا بيئة تسمى CartPole. هذه البيئة من مجموعة التحكم الكلاسيكية وجوهرها هو التحكم في المنصة بعصا متصلة بجزءها السفلي (انظر الشكل التالي).
الحيلة هي أن هذه العصا تميل إلى السقوط يمينًا أو يسارًا وتحتاج إلى موازنة ذلك بتحريك المنصة إلى اليمين أو اليسار في كل خطوة.
ملاحظة هذه البيئة عبارة عن أربعة أرقام عائمة (float number) تحتوي على معلومات حول إحداثيات x لمركز كتلة العصا ، وسرعتها ، وزاويتها على المنصة ، وسرعتها الزاوية.
بالطبع ، من خلال تطبيق بعض المعرفة في الرياضيات والفيزياء ، لن يكون من المعقد تحويل هذه الأرقام إلى إجراءات عندما نحتاج إلى موازنة العصا ، ولكن مشكلتنا هي – كيف نتعلم موازنة هذا النظام دون معرفة المعنى الدقيق من الأعداد المرصودة وفقط بالحصول على المكافأة؟
المكافأة في هذه البيئة هي 1 ، وتعطى في كل خطوة زمنية. تستمر الحلقة حتى تسقط العصا ، لذلك للحصول على المزيد من المكافآت المتراكمة ، نحتاج إلى موازنة المنصة بطريقة لتجنب سقوط العصا.
دعنا نواصل جلستنا.
>>> obs = e.reset() >>> obs array([-0.04937814, -0.0266909 , -0.03681807, -0.00468688])
هنا ، قمنا بإعادة ضبط البيئة وحصلنا على الملاحظة الأولى (نحتاج دائمًا إلى إعادة ضبط البيئة المنشأة حديثًا). كما قلنا ، الملاحظة هي أربعة أرقام ، لذا دعونا نتحقق من كيفية معرفة ذلك مسبقًا.
>>> e.action_space Discrete(2) >>> e.observation_space Box(4,)
حقل فضاء الإجراء هو من النوع المنفصل (discrete) ، لذا ستكون إجرائتنا 0 أو 1 فقط ، حيث يعني 0 دفع المنصة إلى اليسار وتعني 1 إلى اليمين. فضاء المراقبة هي BOX(4,) ، مما يعني متجهًا بحجم 4 بقيم داخل الفاصل الزمني [inf ، inf].
>>> e.step(0) (array([-0.04991196, -0.22126602, -0.03691181, 0.27615592]), 1.0, False, {})
هنا ، دفعنا منصتنا إلى اليسار من خلال تنفيذ الإجراء 0 وحصلنا على مجموعة العناصر الأربعة:
- ملاحظة جديدة ، وهي متجه جديد لأربعة أعداد
- مكافأة 1.0
- علامة “تم (Done)” ذات القيمة False ، مما يعني أن الحلقة لم تنته بعد وأننا بخير إلى حد ما
- معلومات إضافية حول البيئة ، وهو قاموس فارغ
بعد ذلك ، سنستخدم طريقة sample() لفئة Space في action_space و observation_space.
>>> e.action_space.sample() 0 >>> e.action_space.sample() 1 >>> e.observation_space.sample() array([2.06581792e+00, 6.99371255e+37, 3.76012475e-02, -5.19578481e+37]) >>> e.observation_space.sample() array([4.6860966e-01, 1.4645028e+38, 8.6090848e-02, 3.0545910e+37])
أعادت هذه الطريقة عينة عشوائية من المساحة الأساسية ، والتي في حالة فضاء الإجراء المنفصلة لدينا تعني عددًا عشوائيًا من 0 أو 1 ، وبالنسبة لفضاء المراقبة تعني متجهًا عشوائيًا من أربعة أرقام.
قد لا تبدو العينة العشوائية لفضاء المراقبة مفيدة ، وهذا صحيح ، ولكن يمكن استخدام العينة من فضاء الإجراء عندما لا نكون متأكدين من كيفية تنفيذ إجراء ما. هذه الميزة مفيدة بشكل خاص لأنك لا تعرف أي طرق التعلم المعزز الأخرى حتى الآن ، لكننا ما زلنا نريد اللعب مع بيئة Gym . الآن بعد أن عرفت ما يكفي لتنفيذ أول وكيل تصرف عشوائي لـ CartPole ، فلنقم بذلك.
وكيل CartPole العشوائي
الكود كالتالي :
import gym if __name__ == "__main__": env = gym.make("CartPole-v0") total_reward = 0.0 total_steps = 0 obs = env.reset()
هنا ، أنشأنا البيئة وأعدنا تهيئة عداد الخطوات ومجمع المكافآت. في السطر الأخير ، قمنا بإعادة ضبط البيئة للحصول على الملاحظة الأولى (والتي لن نستخدمها ، حيث أن وكيلنا عشوائي).
while True: action = env.action_space.sample() obs, reward, done, _ = env.step(action) total_reward += reward total_steps += 1 if done: break print("Episode done in %d steps, total reward %.2f" % ( total_steps, total_reward))
في هذه الحلقة ، أخذنا عينة من إجراء عشوائي ، ثم طلبنا من البيئة تنفيذه وإرجاع الملاحظة التالية (obs) ، و reward ، و علامة done .
إذا انتهت الحلقة ، فإننا نوقف الحلقة ونبين عدد الخطوات التي اتخذناها وكم المكافأة التي تم تجميعها. إذا بدأت هذا المثال ، فسترى شيئًا كهذا (ولكن ليس بالضبط ، بسبب عشوائية الوكيل):
rl_book_samples/Chapter02$ python 02_cartpole_random.py Episode done in 12 steps, total reward 12.00
في المتوسط ، يأخذ وكيلنا العشوائي من 12 إلى 15 خطوة قبل أن يسقط العمود وتنتهي الحلقة. تحتوي معظم البيئات في GYM على “حد مكافأة” ، وهو متوسط المكافأة التي يجب أن يحصل عليها الوكيل خلال 100 حلقة متتالية “لحل” البيئة.
بالنسبة إلى CartPole ، هذه الحدود هي 195 ، مما يعني أنه في المتوسط ، يجب على الوكيل أن يثبت العصا لمدة 195 خطوة زمنية أو أكثر. باستخدام هذا المنظور ، يبدو أداء وكيلنا العشوائي ضعيفًا.
وظائف GYM الإضافية – أغلفة وشاشات
ما ناقشناه حتى الآن يغطي ثلثي واجهة برمجة تطبيقات Gym core والوظائف الأساسية المطلوبة لبدء كتابة الوكلاء . ما تبقى من واجهة برمجة التطبيقات يمكنك العيش بدونها ، لكنها ستجعل حياتك أسهل و الكود الخاص بك أنظف. لذلك ، دعنا نغطي بإيجاز بقية API.
أغلفة (Wrappers)
في كثير من الأحيان ، سترغب في توسيع وظائف البيئة بطريقة عامة. على سبيل المثال ، تخيل أن البيئة تمنحك بعض الملاحظات ، لكنك تريد تجميعها في مُخزن مؤقت (buffer ) وتقديم الملاحظات الأخيرة N للوكيل. هذا سيناريو شائع لألعاب الكمبيوتر الديناميكية ، عندما لا يكفي إطار واحد للحصول على المعلومات الكاملة حول حالة اللعبة.
مثال آخر هو عندما تريد أن تكون قادرًا على اقتصاص وحدات البكسل أو معالجتها مسبقًا لجعلها أكثر ملاءمة للوكيل للتعامل معها ، أو إذا كنت تريد تسوية نقاط المكافأة بطريقة ما. هناك العديد من المواقف التي لها نفس البنية – تريد “تغليف (wrap)” البيئة الحالية وإضافة بعض المنطق الإضافي للقيام بشيء ما. توفر gym إطارًا مناسبًا لهذه المواقف – فئة Wrapper.
يظهر هيكل الفئة في الرسم البياني التالي.
ترث فئة Wrapper فئة Env. و تقبل حجة واحدة – حالة فئة البيئة المراد تغليفها . لإضافة وظائف إضافية ، تحتاج إلى إعادة تعريف الوظائف التي تريد تمديدها ، مثل step() أو reset() . الشرط الوحيد هو استدعاء الوظيفة الأصلية للفئة العليا.
للتعامل مع المتطلبات أكثر تحديدًا ، مثل فئة Wrapper التي تريد معالجة الملاحظات من البيئة فقط ، أو الإجراءات فقط ، هناك فئات فرعية من Wrapper تسمح بتصفية جزء معين فقط من المعلومات. وهم على النحو التالي:
- غلاف الملاحظة (ObservationWrapper) : أنت بحاجة إلى إعادة تعريف وظيفة الملاحظة (obs) الخاصة بالفئة العليا . الحجة obs هي ملاحظة من البيئة المغلفة ، ويجب أن تعيد هذه الوظيفة المساعدة الملاحظة التي ستعطى للوكيل.
- غلاف المكافأة (RewardWrapper) : يعرض وظيفة المكافأة (rew) ، والتي يمكنها تعديل قيمة المكافأة الممنوحة للوكيل.
- غلاف الإجراء (ActionWrapper): أنت بحاجة إلى تجاوز وظيفة الإجراء (act) ، والتي يمكنها تعديل الإجراء الذي تم تمريره إلى البيئة المغلفة بواسطة الوكيل.
لجعل الأمر أكثر عملية قليلاً ، دعنا نتخيل موقفًا نريد فيه التدخل في تدفق الإجراءات التي يرسلها الوكيل ، مع احتمال 10٪ ، نستبدل الإجراء الحالي بآخر عشوائي. قد يبدو من غير الحكمة القيام به ، ولكن هذه الحيلة البسيطة هي واحدة من أكثر الأساليب العملية والأكثر فعالية لحل مشكلة الاستكشاف / الاستغلال التي ذكرناها بإيجاز في المقال الثاني.
من خلال إصدار الإجراءات العشوائية ، نجعل وكيلنا يستكشف البيئة ومن وقت لآخر يبتعد عن المسار المطروق لسياسته. هذا شيء سهل القيام به باستخدام فئة ActionWrapper
import gym from typing import TypeVar import random Action = TypeVar('Action') class RandomActionWrapper(gym.ActionWrapper): def __init__(self, env, epsilon=0.1): super(RandomActionWrapper, self).__init__(env) self.epsilon = epsilon
هنا ، قمنا بتهيئة غلافنا عن طريق استدعاء وظيفة __init__ الخاصة بالفئة العلوية أو الـ parent وحفظ إبسيلون (احتمالية إجراء عشوائي).
def action(self, action: Action) -> Action: if random.random() < self.epsilon: print("Random!") return self.env.action_space.sample() return action
هذه الوظيفة نحتاجها لتجاوزه إجراءات الوكيل الأصلية . في كل مرة نرمي فيها النرد ، ومع احتمال إبسيلون ، نقوم بأخذ عينة من الإجراء العشوائي من فضاء الإجراء وإعادته بدلاً من الإجراء الذي أرسله لنا الوكيل.
لاحظ أنه باستخدام action_space و التغليف التجريدي ، تمكنا من كتابة كود مجردة ، والذي سيعمل مع أي بيئة من Gym. بالإضافة إلى ذلك ، يجب علينا طباعة الرسالة في كل مرة نقوم فيها باستبدال الإجراء ، فقط للتحقق من أن غلافنا يعمل. في كود الإنتاج ، بالطبع ، لن يكون هذا ضروريًا.
if __name__ == "__main__": env = RandomActionWrapper(gym.make("CartPole-v0"))
الآن حان الوقت لتطبيق غلافنا. سننشئ بيئة CartPole عادية وننقلها إلى مُنشئ Wrapper الخاص بنا. من الآن فصاعدًا ، سنستخدم الغلاف الخاص بنا كمثال Env عادي ، بدلاً من CartPole الأصلي. نظرًا لأن فئة Wrapper ترث فئة Env وتكشف نفس الواجهة ، يمكننا تعشيش (nest) أغلفتنا في أي مزيج نريده. هذا حل قوي وأنيق وعام.
obs = env.reset() total_reward = 0.0 while True: obs, reward, done, _ = env.step(0) total_reward += reward if done: break print("Reward got: %.2f" % total_reward)
هذا هو نفس الكود تقريبًا ، باستثناء أنه في كل مرة نصدر فيها نفس الإجراء ، 0 ، يقوم وكيلنا بالقيام بنفس الإجراءه. من خلال تشغيل الكود ، يجب أن ترى أن الغلاف يعمل بالفعل.
rl_book_samples/Chapter02$ python 03_random_actionwrapper.py Random! Random! Random! Random! Reward got: 12.00
إذا كنت تريد ، يمكنك اللعب باستخدام معلمة epsilon عند إنشاء الغلاف والتحقق من أن العشوائية تعمل على تحسين درجة الوكيل في المتوسط.
يجب أن ننتقل الآن وننظر إلى جوهرة أخرى مثيرة للاهتمام مخبأة داخل gym : المراقب.
أداة المراقبة (monitor)
فئة أخرى يجب أن تكون على دراية بها هي Monitor. يتم تنفيذها مثل Wrapper ويمكنها كتابة معلومات حول أداء وكيلك في ملف ، مع تسجيل فيديو اختياري لوكيلك أثناء العمل.
أداة المراقبة مفيدة ، حيث يمكنك إلقاء نظرة على حياة الوكيل داخل البيئة. إذن ، إليك كيفية إضافة Monitor إلى وكيل CartPole العشوائي ، وهو الاختلاف الوحيد
if __name__ == "__main__": env = gym.make("CartPole-v0") env = gym.wrappers.Monitor(env, "recording")
الحجة الثانية التي نمررها إلى Monitor هي مكان حفظ النتائج. لا يجب أن يكون هذا المكان موجودًا ، وإلا فسيفشل البرنامج باستثناء (للتغلب على هذا ، يمكنك إما إزالة ما هو موجود أو تمرير حجة force = True إلى مُنشئ فئة Monitor).
تتطلب فئة Monitor أن تكون الأداة المساعدة FFmpeg موجودة على النظام ، والتي تُستخدم لتحويل الملاحظات الملتقطة إلى ملف إخراج فيديو. يجب أن تكون هذه الأداة المساعدة متاحة ، وإلا ستثير المراقبة استثناءً.
لبدء هذا المثال ، يجب استيفاء أحد هذه المتطلبات الأساسية الثلاثة:
- يجب تشغيل الكود في جلسة X11 بامتداد OpenGL (GLX)
- يجب أن يبدأ الكود في شاشة افتراضية Xvfb
- يمكنك استخدام إعادة توجيه X11 في اتصال SSH
والسبب في ذلك هو تسجيل الفيديو ، والذي يتم عن طريق التقاط لقطات شاشة للنافذة التي رسمتها البيئة. تستخدم البيئة برنامج OpenGL لرسم صورته ، لذا يجب أن يكون الوضع الرسومي مع OpenGL موجودًا.
قد تكون هذه مشكلة لجهاز افتراضي في السحابة ، والذي لا يحتوي فعليًا على شاشة وواجهة رسومية قيد التشغيل. و في هذه الحالة نستخدم Xvfb (X11 Virtual Framebuffer)
لبدء برنامجك في بيئة Xvfb ، تحتاج إلى تثبيته على جهازك (يتطلب هذا عادةً تثبيت حزمة xvfb) وتشغيل البرنامج النصي الخاص ، xvfb-run:
$ xvfb-run -s "-screen 0 640x480x24" python 04_cartpole_random_monitor.py [2017-09-22 12:22:23,446] Making new env: CartPole-v0 [2017-09-22 12:22:23,451] Creating monitor directory recording [2017-09-22 12:22:23,570] Starting new video recorder writing to recording/openaigym.video.0.31179.video000000.mp4 Episode done in 14 steps, total reward 14.00 [2017-09-22 12:22:26,290] Finished writing results. You can upload them to the scoreboard via gym.upload('recording')
كما ترى من السجل السابق ، تمت كتابة الفيديو بنجاح حسنًا ، حتى تتمكن من إلقاء نظرة خاطفة على أحد أقسام وكيلك من خلال تشغيله.
ختأماُ
في هذا المقال قمنا بإنشاء وكيل يتصرف بطريقة عشوائية كما تعلمنا كيفية توسيع وظائف البيئات الحالية بطريقة معيارية وأصبحت على دراية بطريقة لتسجيل نشاط وكيلنا باستخدام فئة المراقبة.
إضافة تعليق