پیاده سازی هوش مصنوعی که بهتر از شما بازی می کنه! (2)


این پست قسمت دوم یک موضوع دو قسمتیه، لطفا برای درک بهتر، قسمت قبلی رو حتما ی نگاهی بندازین ^_^

پروتکل های یادگیری

خب بریم سراغ جمع بندی قسمت یادگیری الگوریتم. در واقع جزییات و منطق روش یادگیری رو در عنوان های قبلی بحث کردیم که چطور قراره اتفاق بیفته و اینجا فقط میخایم جمع بندیش بکنیم.

برای یادگیری مقادیر وکتورهای وزن شبکه خط مشی که W1 و W2 بودند رو با مقادیر اولیه رندم میسازیم و صد دور بازی رو انجام میدیم. اگر فرض کنیم که هر بازی به صورت متوسط در 200 فریم انجام بشه، در انجام 100 تا بازی در مجموع قراره 200*100 یعنی 20000 بار تصمیم بگیریم که پد بره بالا یا پایین. و برای هرکدوم از این پارامتر ها ما مقدار شیب پارامتر رو داریم که بهمون میگه چجوری باید مقدار پارامتر رو به روز رسانی کنیم اگر که مثلا بخواهیم اون تصمیم رو که گرفتیم رو تشویق کنیم. پس تنها کاری که مونده اینه که به تصمیماتی که گرفتیم در طول بازی برچسب مناسب رو بزنیم. برای این کار باید ببینیم که اون تصمیم آیا به برد منتهی شده یا باخت. مثلا فرض کنیم 12 تا بازی رو بردیم و 88 تا بازی رو باختیم. پس برای این کار تمامی 12*200 تصمیمی که منتهی به برد شده رو به روز رسانی تشویقی میکنیم که در پست قبلی در مورد جزییاتش بحث کردیم و تمامی 88*200 تصمیمی که به باخت منتهی شدن رو به روز رسانی تنبیهی میکنیم. با این به روز رسانی شبکه ما با کمی احتمال بیشتر تصمیماتی که به برد رسیدن رو انجام میده و کمی با احتمال کمتر تصمیماتی که به باخت رسیدن رو انجام خواهد داد.

حالا شبکه و خط مشی که کمی بهتر شده رو دوباره اجازه میدیم 100 دور دیگه بازی کنه و این روند رو ادامه میدیم تا جایی که تبدیل به یک قهرمان پونگ بشه حتی بهتر از چینی ها! :)

مخلص کلام

شیب خط مشی یا policy gradient : یک خط مشی را برای مدتی استفاده می کنیم. چک می کنیم که کدوم تصمیمات به نتایج خوبی منتهی شدن. احتمال اون تصمیمات رو کمی بیشتر می کنیم.


https://www.youtube.com/watch?v=YOW8m2YGtRg


شما هم به همونی فکر میکنین که من فکر میکنم !؟

اگر دقیق به اتفاقاتی که ممکنه در این روند بیوفته فکر کنین به نکات جالب و عجیبی میرسین که ممکنه فکر کنین با یادگیری یجورایی متناقضن. مثلا فرض کنین که ما پد رو به شکلی تکون دادیم که به خوبی تونستیم توپ رو برگردونیم ولی در انتهای اون راند از بازی ما شکست خوردیم. پس با توجه به روندی که بحث کردیم ما باید اون حرکت خوبی که باعث شد به خوبی توپ رو برگردونیم رو تنبیه کنیم ! آره درسته شاید اینجا کمی counter intuitive به نظر برسه ولی حقیقت اینه که چون ما بازی رو میلیون ها بار انجام میدیم، در انتها تعداد دفعاتی که ما یک حرکت خوب رو تشویق میکنیم خیلی بیشتر از تنبیه ها میشه و این در نتیجه باعث میشه واقعا خط مشی ما آخر کار خوب عمل کنه چرا که به صورت میانگین حرکات خوب رو بیشتر تشویق کرده تا تنبیه !

توابع پاداشی که معروف ترند!

تا به اینجا با تنها روش و تابعی که باهاش آشنا شدیم که به ما پاداش و به نوعی نشانه میداد که داریم خوب بازی میکنیم یا نه، خروجی بازی بود که اگر بردیم بهمون پاداش مثبت میداد و اگه می باختیم پاداش منفی! یک روش پاداش گرفتن که بیشتر در یادگیری تقویتی مرسومه، پاداشی هست که در هر قدم از محیط میگیریم و این پاداش به قدم های بعدی هم منتشر یا propagate میشه. این پاداش رو عموما به صورت r subscript t نشون میدیم که میزان انتشار به قدم های بعدی رو هم با یک ضریب γ که بهش عامل تخفیف یا discount factor گفته میشه و مقداری بین صفر و یک هستش رو اعمال میکنه. ایده این نوع از پاداش اینه که میزان تشویق یک عمل برابر با جمع وزن دار تمامی پاداش هایی که بعدا هم میگیره ولی تاثیر پاداش های بعدی به صورت نمایی کمتر خواهد بود. برای درک عمیق تر این بخش و نحوه عملکرد سیستم پاداش برای بهتر شدن شبکه خط مشی میتونین ویدیو لکچر جان! روببینین :)

و اما کد

خب حالا که بخش های مختلف و مفاهیم تئوری رو بررسی کردیم وقتشه که ببینیم این داستان چجوری پیاده سازی میشه. کد مربوط به پیاده سازی رو میتونین از ریپو من بردارین که در واقع ویرایش شده کدی هست که مرد بی بدلیل دنیای یادگیری ماشین یعنی اندری کارپسی به اشتراک گذاشته. (این بشر رو بیشتر بشناسین واقعا آدم کار درستیه). از جیستم هم میتونین برش دارین. فورک کردم و خب ی سری ادیت ها روش انجام دادم. سعی میکنم نکات مهمش رو اینجا بررسی کنم ولی خدایی کد جمع و جور 130 خطی خیلی ساده ایه و با توضیح دادنش شاید دارم وقتتون رو میگیرم ولی امیدوارم کمک کنه.

کد پیاده سازی شبکه خط مشی دو لایه ای با 200 یونیت مخفی با RMSProp در دسته های 10 قسمتیه . دسته همون batch و قسمت همون episode منظورمه. در مورد اجرا کردنش هم خیلی ساده انگار دارین یک کد عادی پایتون رو اجرا میکنین و تنها چیزی که لازمه نصب کنین GymAI هستش که هم میتونین از راهنمای نصب سایت خودش استفاده کنین و هم از این لینک که یکی از مشکلاتی که احتمال زیاد براتون پیش خواهد اومد رو هم ازش جلوگیری میکنه! که خلاصش اینه:

بعد در مورد خروجی و نتایج میشه گفت که با توجه به اینکه بهینه سازی پارامتر های روی این کد انجام نشده برای ساده موندن کد، میتونه با سه چهار شب اجرا روی یک سیستم معمولی بدون GPU خروجی قابل قبول بده و اگه بخاین اون ادعای نه خیلی سخت که انسان رو هم ببره رو پاس کنین لازمه از convNet ها برای اون قسمت شبکه خط مشی استفاده کنین و پارامتر ها رو هم بهینه کنین که کار سختی نیست ولی لازمه که از یکی از فریموورک های یادگیری عمیق مثل pyTorch یا Tensorflow استفاده کنید که من نکردم ولی امیدوارم ایده و روش رو خوب گرفته باشین که براتون سخت نباشه. کد اون رو هم انجام میدم و میزارم توی ریپو خودم به زودی. ازم بخاین حتما :) من یکم پیگیری لازم دارم ^_^



به صورت کلی بخش هایی از کد رو که احساس کردم ممکنه گنگ باشه رو اینجا بحث میکنم در موردش. اگه بخشی نیاز به توضیح داشت لطفا در کامنت ها بپرسین. قول میدم جواب بدم.

`def prepro(I)
    &quot&quot&quot prepro 210x160x3 uint8 frame into 6400 (80x80) 1D float vector &quot&quot&quot
    I = I[35:195] # crop
    I = I[::2,::2,0] # downsample by factor of 2
    I[I == 144] = 0 # erase background (background type 1)
    I[I == 109] = 0 # erase background (background type 2)
    I[I != 0] = 1 # everything else (paddles, ball) just set to 1
    return I.astype(np.float).ravel()

این تابع وظیفه داره که تصویر ورودی رو کمی برای الگوریتم ساده تر کنه به این معنا که مثلا بخش های دور تصویر که تاثیری رو خود بازی کردن نداره رو برش میزنه در همون خط اول. بعد کلا نصف سمپل هارو پاک میکنه توی خط بعدی چرا که به احتمال زیاد نمونه هایی که پشت سر هم دارن میان زیاد تفاوتی با هم ندارن. بعد مقادیر مربوط به پس زمینه رو صفر میکنیم و فقط توپ و پد هارو یک میکنیم. و در آخر هم داده رو به یک وکتور یک بعدی تبدیل میکنیم.

تصویری از بازی بعد از پیش پردازش
تصویری از بازی بعد از پیش پردازش


حالا داخل ی حلقه فرایند های زیر رو انجام میدیم:

`# preprocess the observation, set input to network to be difference image
cur_x = prepro(observation)
x = cur_x - prev_x if prev_x is not None else np.zeros(D)
prev_x = cur_x

اینجا اول فریم گرفته شده از Gym رو به تابع اماده سازمون که بالا در موردش بحث کردیم میدیم تا بهمون ی وکتور بده. بعد این وکتور رو اختلافش رو نسبت به فریم قبلی به دست میاریم و به شبکه میدیم.

کد فقط دو بخش دیگه داره که توی توضیحات هم بهشون اشاره کردم، یعنی policy forward و policy backward که دو تا تابع هستن. تابع forward که دقیقا کارش اینه که مقادیر رو میگیره و بر اساس وزن هایی که احیانا تا الان یاد گرفته میگه که با چه احتمالی پد رو مثلا ببریم بالا. فقط یکی رو میگه چون میدونیم دیگری میشه یک منهای اون احتمالی که داده.

و اما تابع backward. زمانی که یک episode یا قسمت از بازی تموم میشه، که در این حالت مقدار متغیر done که از Gym میگیریم برابر با یک میشه، و در واقع در بازی یکی باخته ،کاری که میکنیم اینه که خب تونستیم ی نتیجه ای از بازی بگیریم و میتونیم این پاداش رو در شبکه propagate کنیم که ببینیم باید کدوم تصمیم هارو تشویق کنیم. برای این کار میایم مقادیر آرایه های xs و hs که مربوط به observation و hidden state ها بودن رو روی هم استک میکنیم. سپس مقدار discounted reward رو محاسبه میکنیم در تابع discount_reward و سپس این مقدار رو با eph که مربوط به hidden state ها هستند رو به تابع میدیم که در شبکه به صورت از آخر به اول ضرب نقطه ای بشه و مقادیر وزن هارو به روز رسانی کنه. اگه تسلط کافی روی back propagation ندارین این پست از استاد کارپسی رو پیشنهاد میکنم.

خب فهمیدم توضیح دادن کد واقعا کار سختیه!

سخن پایانی

تا حد امکان و تا جایی که فرصت داشتم سعی کردم توضیح بدم. اگه بخشی هست که بد نوشتم یا احیانا غلطی وجود داره و یا سوالی اگه بود حتما کامنت یا پیام بدین و مطرح کنین . مخلص 3>