رفتن به نوشته‌ها

آموزش تنسورفلو قسمت سوم

در قسمت‌ اول و دوم با پیاده‌سازی شبکه‌های پیشرو، سی‌ان‌ان و بازگشتی آشنا شدیم و دیدیم که چطور آنها را به راحتی با استفاده از API مدل میشه پیاده‌سازی کرد. ولی یک مبحث از قسمت قبلی جا موند و اونم استفاده از  لایه‌ها (Layers) در تنسورفلو است.در این قسمت ابتدا به توضیح لایه‌ها در تنسورفلو میپردازیم و سپس به پیاده‌سازی یک مدل کلاسیک در یادگیری تقویتی (RL یا Reinforcement Learning) خواهیم پرداخت.

۱) لایه‌ها در تنسورفلو

این مفهوم خیلی شبیه Model است ولی انعطاف‌پذیری بیشتری به ما خواهد داد. عملا با استفاده از این Layers هر مدلی از شبکه عصبی را می‌توانید با APIهای سطح بالا پیاده‌سازی کنید. این API به قدری انعطاف‌پذیر است که تمامی لایه‌ها مثل tf.keras.layers.Dense، tf.keras.layers.Dropout و …) از آن ارث‌بری کرده‌اند.

نحوه استفاده از آن بسیار راحت است و مانند قبل میتونید یک کلاس بسازید از Layers ارث‌بری کنید و لایه مورد نظر خودتون را پیاده‌سازی کنید.

همانطور که در کلاس بالا مشاهده می‌کنید در هنگام استفاده از این API باید ۴ تابع را پیاده‌سازی کنید:

  • تابع __init__: راستش این وجود این تابع اجباری نیست ولی برای اینکه پارامترهاتون ورودی‌تون رو نگه دارید بهتره که از __init__ استفاده کنید. مثلا اگر یه مقدار alpha دارید که قرار است در محاسباتتون استفاده بشه بد نیست که از طریق __init__ به لایه ارسال شود.
  • تابع build: این تابع جایی هست که ما وزن‌ها (نورون‌ها)‌ مون رو تعریف می‌کنیم. تعریف نورون‌ها با کمک توابع add_weight یا add_variable انجام میشه (تفاوتی بین این دو تابع وجود ندارد). وقتی که تمام متغیرها را تعریف کردیم در نهایت باید تابع self.build=True یا super() را صدا بزنیم تا لایه ما ساخته شود.
  • تابع call: این تابع در واقع جایی است که ما منطق (logic) لایه‌امون رو پیاده می‌کنیم. تنسور ورودی را به این تابع پاس میدهیم و منطق لایه روی آن اعمال می‌شود و سپس آن را به عنوان خروجی برمیگردانیم.
  • تابع compute_output_shape: در صورتی که shape تنسور ورودی ‌اتون رو تغییر دادید لازم است که shape جدید رو با استفاده از این تابع برگردانید.

با توجه به گفته‌های بالا اگر بخواهیم رگرسیون خطی‌امون رو با استفاده از این API در تنسورفلو پیاده‌سازی کنیم چیزی که خواهیم داشت مثل زیر خواهد بود:

چطوری از Layer استفاده می‌کنیم؟ دقیقا مثل اینکه یک لایه Dense یا LSTM میخواستیم در مدل‌امون تعریف کنیم:

۲) انواع API در کراس

تا الان ما هروقت خواستیم مدل بسازیم امدیم از tf.keras.Model ارث‌بری و بعدش کلاس‌امون رو تعریف کردیم ولی شاید بد نباشه که بدونید کراس یک راه دیگر هم قرار داده است و اونم استفاده از tf.keras.Sequential است. به طور کلی دو روش برای تعریف مدل‌ها وجود دارد که به اولی میگن functional و به دومی sequential گفته می‌شود. هرکدام از این دو مورد معایب و مزایا‌ی خودشون رو دارند که در ادامه با مثال آنها را بررسی می‌کنیم.

۲-۱) مدل Sequential

این API به شما امکان می‌دهد که یکی یکی لایه‌هاتون رو پشت سر هم بچینید و مدل‌اتون رو بسازید و استفاده از آن بسیار بسیار راحت است. تنها مشکلی که من در هنگام استفاده از این API خوردم این بوده است که امکان share layer یا اینکه چندتا ورودی و خروجی داشته باشیم ندارد که خوب در بعضی شبکه‌ها مثل مدل‌های Siamese به مشکل خواهید خورد (مثلا شبکه زیر رو در نظر بگیرید، فرض کنید Sister networkها جفتشون یک شبکه LSTM باشند اگر بخواهیم این شبکه را در Sequential پیاده کنیم به مشکل میخوریم).

ولی راحتی استفاده از آنها به قدری زیاده که با ۵ ۶ خط کد میتونید یک مدل نسبتا قوی را پیاده‌سازی کنید:

همانطور که در بالا ملاحظه می‌کنید میشه یکی یکی لایه‌ها رو به کلاس Sequential اضافه کرد.

۲-۲) مدل functional

API دیگری که برای تعریف مدل‌ها وجود دارد functional نام دارد. این مدل رو قبلا ازش استفاده کردیم و تا به اینجا اغلب مدل‌هامون رو با استفاده از آن ساختیم. این مدل معایب مدل sequential (وزن مشترک، چند ورودی و خروجی) را ندارد ولی خوب شاید استفاده از آن مثل مدل Sequential راحت نباشد.

۳) یادگیری تقویتی

در سال ۲۰۱۳ استارتاپ دیپ‌مایند (DeepMind) مقاله‌ای به نام Playing Atari with Deep Reinforcement Learning منتشر کرد و نشان داد که چگونه یک برنامه کامپیوتری با بررسی پیکسل‌های بازی و امتیازاتی که کسب کرده است، قادر است یک بازی را یاد بگیرد. علاوه بر بازی آتاری، مدلی که در مقاله پیاده‌سازی کردند رو روی ۷ تا بازی دیگر هم اجرا کردند و نتایجی که گرفتند در حد انسان و گاها از انسان بهتر بود و اینجوری شد که اولین قدم‌های هوش مصنوعی همه منظوره ( General Artificial Intelligent) برداشته شد. منظور از هوش مصنوعی همه منظوره، هوش مصنوعی است که در محیط‌های مختلف بتواند کارایی داشته باشد.

این مقاله چون چالش جدید و متفاوتی رو مطرح می‌کرد خیلی مورد توجه قرار گرفت. در سال ۲۰۱۵ بعد از اینکه دیپ مایند توسط گوگل خریداری شد، مقاله‌ی دیگری در Nature چاپ شد و همون مدل رو روی ۴۹ بازی دیگه امتحان کردند که نتایج مشابه بود و در اغلب موارد از انسان بهتر بود و اینجوری شد که خیل عظیمی از توجهات رو نسبت به خودش جلب کرد. دیپ‌مایند یه جورایی نشان داد که وقتی یادگیری تقویتی وقتی با یادگیری عمیق ترکیب می‌شود به نتایج بسیار خوبی منجر خواهد شد و آلفاگو (alpha go) و بازی دوتا (dota) نمونه‌های خوبی هستند که بعد از آن منتشر شدند.

مثلا بازی آتاری Breakout رو در نظر بگیرید. هدف این بازی این است که بیل‌بیلک پایین صفحه را تکان بدهیم و خونه‌های رنگی بالا را خراب کنیم و هر دفعه که یکی از خونه‌ها را خراب می‌کنیم امتیاز ما افزایش پیدا میکنه و اصطلاحا یک پاداش (reward) به ما تعلق می‌گیرد (اگر دوست دارید بازی رو انجام بدید تو گوگل سرچ کنید Atari breakout).

حالا فرض کنید میخواهیم به یک شبکه عصبی یاد بدهیم که این بازی را انجام دهد. ورودی شبکه احتمالا پیکسل‌های تصویر بازی خواهد بود و خروجی شبکه احتمالا یک سری حرکت (چپ و راست) خواهد بود. یه جورایی مثل یک مساله دسته‌بندی (classification) خواهد بود که به ازای هر فریم بازی تصمیم بگیریم که چه حرکتی را انجام بدهیم. خب حالا مشکلی که وجود داره این است که برای آموزش شبکه عصبی نیاز به داده آموزشی داریم! یک کاری که میشه کرد این است که به یک نفر که در این بازی خبره است بگیم بیاید بازی کنه و ما نحوه بازی کردن اون را رکورد کنیم و سپس شبکه‌امون را آموزش بدهیم. که خب یه جورایی غیر عملی است. در واقعیت ما اگر این بازی رو بخواهیم به کسی یاد بدیدم اول قوانین بازی رو بهش یاد می‌دیم و چندتا چیز ساده و اجازه می‌دهیم تا چند دور بازی کند. بعد از اینکه بازی کرد و دستش راه افتاد حالا استراتژی خاص برای بردن رو بهش یاد می‌دهیم. بعد طرف اینقدر بازی رو انجام میدهد که در آن حرفه‌ای می‌شود. تسک بالا نمونه‌ای از یادگیری تقویتی است. یادگیری تقویتی یک چیزی مابین یادگیری با نظارت (supervised) و بدون نظارت (unsupervised) است. در یادگیری بانظارت ما باید برای تمام حرکت‌های که میکنیم برچسب داشته باشیم و در یادگیری بدون نظارت ما اصلا برچسبی نداریم. در یادگیری تقویتی به ما بازخوردهایی به ازای بعضی حرکت‌ها داده می‌شود (ولی نه به ازای همه حرکت‌ها). به عبارت دیگر، در یادگیری تقویتی ما برای هر حرکتی که انجام می‌دهیم برچسب نداریم بلکه تنها گاهی اوقات برچسب خواهیم داشت که اصطلاحا به آنها پاداش گفته می‌شود. با توجه به پاداش‌هایی که گرفته میشود اون agent ما (برنامه ما) یاد میگیرد که چطوری با محیط (environemetn) اطرافش تعامل کند.

یادگیری تقویتی در واقع مدلی است که ما (و تقریبا تمام حیوانات) برای یادگیری ازش استفاده می‌کنیم. تشویقی که از پدر و مادر میشیم، نمراتی که در مدرسه می‌گیریم، حقوقی که دریافت می‌کنیم و … نمونه‌هایی از پاداش (reward) گرفتن هستند که ما در طول زندگی برای یادگیری استفاده می‌کنیم.

حالا اگر بخواهیم گفته‌های بالا به صورت ریاضی‌طور نشان بدهیم معمولا از مدلی به نام فرآیند تصمیم‌گیری مارکوف (Markov Decision Process) استفاده می‌شود.

فرض کنید یک عامل (agent) داریم که در محیط (environment) قرار گرفته است (منظور از محیط همون بازی آتاری است). بازی آتاری در هر لحظه در یک وضعیت (state) خاص قرار گرفته است (مختصات توپ، مختصات اون بیل‌بیلک پایین صفحه، جهت توپ و …). عامل (agent) ما می‌تونه در هر لحظه یک مجموعه از حرکت‌ها (action) را انجام بدهد (مثلا بیل‌بیلک پایین صفحه را به چپ یا راست حرکت بدهد). این حرکت‌ها گاهی اوقات به پاداش (reward) منجر خواهند شد (مثلا امتیاز افزایش پیدا میکنه). حرکت‌هایی که agent انجام میدهد وضعیت (state) بازی را تغییر میدهد (جهت حرکت توپ، تعداد خونه‌های رنگی و…) و ما را در یک وضعیت جدید (new state) میبرد. ما می‌تونیم یک سری حرکت (action) دیگر انجام بدهیم و به همین ترتیب بازی ادامه پیدا کند. قوانینی که برای انتخاب حرکت‌های (action) بعدی وجود دارد اصطلاحا Policy گفته می‌شود.

این state و action و policyهایی که برای رفتن از یک وضعیت به وضعیت دیگر وجود دارد Markov Descion Process گفته می‌شود که مثلا برای بازی بالا با فرض اینکه n تا حرکت انجام می‌دهیم به صورت زیر خواهد بود:

که در بالا s همان state یا وضعیت را نشان می‌دهد، a حرکت یا action را نشان می‌دهد و r پاداش یا reward را نشان می‌دهد و بازی وقتی تمام می‌شود که ما به وضعیت مرحله n برسیم (که مثلا صفحه پیروزی خواهد بود).

برای اینکه در بلند مدت موفق باشیم، نه تنها باید تلاش کنیم که پاداش‌های فعلی رو ببریم بلکه باید پاداش‌هایی که در آینده هم نصیب ما شود هم در نظر بگیریم. یعنی یکم باید آینده نگر هم باشیم!

در فرآیند تصمیم‌گیری مارکوف (Markov Descion Process) کل پاداش‌هایی که نصیب‌مون میشود را می‌تونیم به صورت زیر بنویسیم:

که اگر بخواهیم بر حسب زمان عبارت بالا را بنویسیم می‌تونیم بگیم:

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

که در عبارت بالا پارامتر گاما γ یک ضریب بین ۰ الی ۱ خواهد بود که هرچه به سمت آینده میریم کمتر خواهد شد.

همانطور که ملاحظه می‌کنید عبارت بالا را میتونیم در قالب یک تابع بازگشتی بنویسم:

که اگر در اینجا γ را صفر قرار بدهیم مثل این است که آینده را نگاه نکنیم و فقط بر اساس پاداش فعلی حرکت بعدی را انجام دهیم و اگر محیط‌امون قابل پیش‌بینی باشد (deterministic) و گاما را یک قرار بدهیم در تمامی حرکت‌ها پاداش یکسان دریافت خواهیم کرد. چیزی که متداول است این است که گاما را حدود ۰.۹ در نظر بگیریم. این γ یه جورایی مثل decay factor میمونه (اگر نمیدونید چیه ازش بگذرید).

یک استراتژی خوب برای agent ما این است که در بازی همیشه حرکتی را انتخاب کند که عبارت بالا را ماکسیمم کند (پاداش حداکثری گیرمون بیاد).

۳-۱ Q-Learning:

کیو لرنینگ (Q learning) یک تکنیک برای یادگیری تقویتی است که هدفش یادگرفتن قوانین بازی یا Policyها است که به عامل (agent) بگه چه حرکت‌های (action) انجام دهد. در واقع ما در Q Learning یک تابع Q تعریف می‌کنیم که هدفش ماکسیمم کردن مقدار پاداش R است که در بالا به آن اشاره کردیم:

میشه اینجوری به Q نگاه کرد که این تابع به دنبال پیدا کردن بهترین امتیاز ممکن در انتهای بازی با انجام حرکت a در وضعیت s است. این تابع Q-function نامیده می‌شود چون کیفیت (quality) یک حرکت (action) در یک وضعیت (state) خاص را می‌سنجد.

شاید الان بپرسید چطوری با داشتن وضعیت و حرکت فعلی امتیاز انتهای بازی را حدس می‌زنیم؟ در واقع نمی‌تونیم! ولی از لحاظ تئوری همچنین تابعی وجود دارد! در واقع هدف Q-Learning این است که با کمک معادله Bellman و به صورت iterative یک تقریب از تابع Q-function که گفتیم وجود ندارد به ما بدهد.

منظور از تقریب زدن یعنی با اضافه کردن یک درصدی از خطا به تابع تئوری برسیم. که در این صورت معادله ما چیزی شبیه زیر خواهد شد:

که در اینجا a’ و s’ به ترتیب حرکت و وضعیت بعدی هستند. حالا عبارت بالا اصن چی میگه؟ عبارت بالا میخواد بگه که ماکسیمم کردن پاداش مجموع ((Q(s,a) برابراست با پاداش فعلی (r) به اضافه حداکثر پاداش آتی در وضعیت‌های بعدی ((‘Q(s’,a).

اگر بخواهیم یک شبه کد برای عبارت بالا بنویسم چیزی شبیه زیر خواهد بود:

عبارت بالا می‌گه تا وقتی که بازی قطع تمام نشده است، حرکتی را انجام بده که سود کلی را بیشتر کند. اگر بخواهیم به چیزهایی که تو ماشین لرنینگ و دیپ لرنینگ هست توضیح بدهیم میشه گفت در عبارت بالا‌ [Q= [s,a پیش‌بینی مدل (خروجی مدل است) و [‘γMaxQ= [s’,a برچسب (label) ماست. به عبارت دیگر اون چیزی که بعد از الفا و داخل پرانتز قرار دارد همان خطای (loss) ما خواهد بود.

خب قسمت‌های بالا بیشتر جنبه مقدماتی داشت و تئوری قضیه بود حالا بیایید ببنیم چطوری میتونیم حرفای بالا را پیاده‌سازی کنیم. تا الان فهمیدیم که loss ما چی خواهد بود که خودش نصف مساله است! در مرحله بعدی نوبت به آموزش میرسه! برای آموزش چی لازم داریم؟ نمونه‌های آزمایش (traning example) و برچسب (label). حالا در ادامه یک مثال واقعی رو بررسی می‌کنیم و می‌بینیم که چطور نمونه‌های آزمایش و برچسب رو می‌تونیم پیدا کنیم!

۳-۲ یادگیری تقویتی در تنسورفلو

بیایید با یک مثال معروف به نام MountainCar پیاده‌سازی ‌امون رو انجام بدهیم. کلیت مساله MountainCar این است که یک ماشین در داخل چاله قرار گرفته است و می‌خواهیم آن را از داخل چاله خارج کنیم (به پرچم برسونیم). ماشین توانایی و قدرت خروجی یک دفعه‌ای را ندارد (موتور ماشین اونقدرا قوی نیست) و به همین علت با عقب و جلو کردن‌های متوالی باید سعی در خارج کردن خود از داخل چاله کند.

 

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

برای جزئیات کامل درباره نصب و مستندات Gym می‌تونید به وبسایت آنها مراجعه کنید ولی اون چیزی که ما در ابتدا به آن نیاز خواهیم داشت نصب پکیج است که میتونید از طریق pip آن را نصب کنید:

و با تیکه کد زیر می‌تونید محیط‌اتون رو تست کنید:

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

به طور کلی برنامه‌ای که باید بنویسیم منطقش باید شبیه کد زیر باشد:

در ابتدا یک enviroment می‌سازیم (محیط بازی که همون ماشین و گودال است)، بعد حالت اولیه (state) را مقداردهی می‌کنیم (state در واقع مختصات فعلی ماشین ما خواهد بود که به صورت زوج x , y است و هدف بازی هم این است که به x=0.5 برسیم). بعد از مدل‌مون میخواهیم که حرکت (action) بعدی را پیش‌بینی کند و وقتی هم که فلگ done صحیح بود به معنای این است که به بالای تپه رسیدیم و یک پاداش به agent امون میدهیم.

خب همانطور که در بالا اشاره کردیم مساله ما مثل یک نوع دسته‌بندی (classification) خواهد بود که در هر لحظه باید حرکت مناسب را حدس بزند. حرکت‌های (action) که ماشین ما می‌تواند انجام بدهد به صورت زیر است که هر کدام با یک عدد صحیح نمایش داده‌ می‌شوند (که به اعداد زیر action space گفته می‌شود):

پس ما یک Classifier باید داشته باشیم که وضعیت (state) را بگیرد و به ما بگوید کدوم جهت حرکت کند که اگر بخواهیم این Classifier را پیاده‌سازی کنیم چیزی شبیه زیر خواهد بود:

یک شبکه پیشرو با دولایه مخفی که خروجی لایه آخر به تعداد action_space‌های ما خواهد بود (۳ تا).

حالا باید دیتای مورد نیاز برای آموزش این شبکه را ایجاد کنیم. در ابتدای بازی شبکه هیچ دانشی راجع به اینکه کجاست، چیکار قرار بکنه و چه طوری باید بازی کند ندارد.

میتونیم در ابتدا به صورت تصادفی (random) یک سری حرکت انجام بدهیم (به چپ برو، به راست برو، دوبار به چپ برو و…) تا با محیط بازی آشنا بشیم! مثلا فرض کنید ۳۲ بار (اندازه یک batch) تصادفی این حرکت‌ها را انجام می‌دهیم و سپس شبکه‌امون رو آموزش می‌دهیم. این حرکات تصادفی تکرار می‌کنیم. گاهی اوقات این حرکت‌ها ممکن است به پاداش ختم بشه. اونایی که پاداش ختم شد هم به نمونه‌های آموزش‌امون اضافه می‌کنیم  و شبکه را آموزش می‌دهیم. در ابتدای کار حرکت‌هایی (action) که انجام می‌دهیم چندان بدرد نمیخورند ولی به محض اینکه یکی از نمونه‌های یک پاداش دریافت کنیم (یعنی یک نمونه انجام بدهیم که در جهت رسیدن به بالای تپه باشه) مدل یک علامت دریافت می‌کنه! میفهمه که اگر بتونه ماشین رو به بالای سمت راست هدایت کنم loss کم خواهد شد. در نتیجه در دورهای بعدی (iteration) تلاش می‌کنه حرکت‌های را پیش‌بینی کند که loss را کم کند و ما را به بالای تپه هدایت کند. کدی که برای این تیکه می‌تونیم بزنیم میتونه به صورت زیر باشه:

در ابتدای کار یک حد آستانه (epsilon) قرار می‌دهیم و میگیم که بیشتر نمونه‌ها را تصادفی درست کن (چون شبکه در ابتدای کار نمیدونه چی به چیه!) و این نمونه‌ها رو به replay_buffer که نمونه‌های آزمایش ماست اضافه می‌کنیم. این replay_buffer مثل یک دیتاست میمونه که ما حرکت‌های که کردیم رو بهش اضافه می‌کنیم و بعدا هم اندازه یک batch ازش نمونه برداری می‌کنیم و شبکه رو آموزش میدهیم.

یک شرط میزاریم می‌گیم که به محض اینکه اندازه دیتاست از اندازه batch بیشتر شد شروع کن به آموزش شبکه!

نمونه‌های آزمایشی رو از replay_buffer واکشی می‌کنیم و طبق تابع خطایی که بالا داشتیم مقدار خطا را حدس و شبکه امون رو آموزش می‌دهیم. این loop رو اینقدر تکرار می‌کنیم که در iteration حدودا ۲۵۰، مدل بعد از کلی بازی کردن یاد میگیره که چطوری بره بالای تپه!

پس به طور کلی چیزی که خواهیم داشت به صورت زیر خواهد بود:

تقریبا بعد از ۱۵ دقیقه مشاهده می‌کنید که ماشین یاد میگیره که خودش بره بالای تپه!

امیدوارم که توضیحاتم کافی باشند و اگر جاییش ناقص بود خوشحال میشم در کامنت‌ها بگید.

منتشر شده در تنسورفلوکراسیادگیری عمیق

نظر

  1. پارسا پارسا

    سلام
    پروژه دیپ لرنینگ هم انجام می دید؟
    در صورت مثبت بودن پاسخ لطفا به ایمیل زیر اطلاع بدید. ایمیل مستقیم شما را نداشتم. ممنون
    parsa.arianpoor1@gmail.com

    • هادیفر هادیفر

      سلام، نه ولی اگر موضوعش جذاب باشه شاید بتونم کمک‌تون کنم

  2. mahsa mahsa

    سلام
    در مورد لودکردن فولدرهای ترین وتست تصویر با روش tf امکانش هست راهنمایی کنید؟

  3. آقا خدا قوت.
    مطالب خیلی عالی بودن.
    من تا امروز فرصت نکرده بودم همه اش رو بخونم.
    فقط اگر بشه درباره rnn lstm ها هم بنویسید خیلی خوبه.
    کلا هر چیزی که به سری زمانی بخوره خیلی جالبه بنظرم

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *