You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ML-For-Beginners/translations/fa/8-Reinforcement/2-Gym
localizeflow[bot] 59062d5b8e
[fa,ur,zh-CN] chore(i18n): sync translations
1 month ago
..
solution chore(i18n): sync translations with latest source changes (chunk 1/2, 610 changes) 7 months ago
README.md [fa,ur,zh-CN] chore(i18n): sync translations 1 month ago
assignment.md chore(i18n): sync translations with latest source changes (chunk 1/2, 610 changes) 7 months ago
notebook.ipynb 🌐 Update translations via Co-op Translator 12 months ago

README.md

اسکیت روی CartPole

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

آزمون پیش از تدریس

مقدمه

در این درس همان اصول Q-Learning را به مسئله‌ای با حالت پیوسته اعمال خواهیم کرد، یعنی حالتی که توسط یک یا چند عدد واقعی داده می‌شود. مسئله زیر را بررسی خواهیم کرد:

مسئله: اگر پیتر بخواهد از گرگ فرار کند، باید بتواند سریع‌تر حرکت کند. خواهیم دید که چگونه پیتر می‌تواند یاد بگیرد اسکیت کند، به خصوص چگونه تعادل خود را حفظ کند، با استفاده از Q-Learning.

فرار بزرگ!

پیتر و دوستانش برای فرار از گرگ خلاقیت به خرج می‌دهند! تصویر از Jen Looper

ما از نسخه ساده‌شده‌ای از تعادل استفاده خواهیم کرد که به عنوان مسئله CartPole شناخته می‌شود. در دنیای CartPole، ما یک لغزنده افقی داریم که می‌تواند به چپ یا راست حرکت کند و هدف این است که یک قطب عمودی را روی لغزنده تعادل دهیم.

a cartpole

پیش‌نیازها

در این درس از کتابخانه‌ای به نام OpenAI Gym برای شبیه‌سازی محیط‌های مختلف استفاده خواهیم کرد. می‌توانید کد این درس را به صورت محلی (مثلاً در Visual Studio Code) اجرا کنید که در این صورت شبیه‌سازی در پنجره‌ای جدید باز می‌شود. هنگام اجرای کد به صورت آنلاین، ممکن است به تنظیماتی در کد نیاز داشته باشید، همانطور که اینجا توضیح داده شده است.

OpenAI Gym

در درس قبلی قوانین بازی و حالت توسط کلاس Board که خودمان تعریف کردیم ارائه شد. اینجا از یک محیط شبیه‌سازی خاص استفاده خواهیم کرد که فیزیک پشت قطب تعادلی را شبیه‌سازی می‌کند. یکی از محبوب‌ترین محیط‌های شبیه‌سازی برای آموزش الگوریتم‌های تقویت یادگیری، Gym است که توسط OpenAI نگهداری می‌شود. با استفاده از این Gym می‌توانیم محیط‌های مختلفی از شبیه‌سازی CartPole تا بازی‌های آتاری بسازیم.

توجه: دیگر محیط‌های موجود در OpenAI Gym را می‌توانید اینجا مشاهده کنید.

ابتدا، بیایید gym را نصب کنیم و کتابخانه‌های مورد نیاز را وارد کنیم (کد بلاک 1):

import sys
!{sys.executable} -m pip install gym 

import gym
import matplotlib.pyplot as plt
import numpy as np
import random

تمرین - راه‌اندازی محیط CartPole

برای کار با مسئله تعادل CartPole، باید محیط مربوطه را راه‌اندازی کنیم. هر محیط با یک:

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

  • فضای عمل که اعمال ممکن را تعریف می‌کند. در مورد ما فضای عمل گسسته است و شامل دو عمل - چپ و راست - می‌شود. (کد بلاک 2)

  1. برای راه‌اندازی، کد زیر را تایپ کنید:

    env = gym.make("CartPole-v1")
    print(env.action_space)
    print(env.observation_space)
    print(env.action_space.sample())
    

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

  1. کد زیر را اجرا کنید و ببینید نتیجه چه می‌شود.

    به خاطر داشته باشید بهتر است این کد را روی نصب محلی پایتون اجرا کنید! (کد بلاک 3)

```python
env.reset()

for i in range(100):
   env.render()
   env.step(env.action_space.sample())
env.close()
```

    شما باید چیزی شبیه به این تصویر ببینید:

    cartpole بدون تعادل

  1. در طول شبیه‌سازی باید مشاهدات را دریافت کنیم تا تصمیم بگیریم چگونه عمل کنیم. در واقع تابع step مشاهدات فعلی، تابع پاداش و فَلَگ done را برمی‌گرداند که نشان می‌دهد آیا ادامه شبیه‌سازی منطقی است یا خیر: (کد بلاک 4)

    env.reset()
    
    done = False
    while not done:
       env.render()
       obs, rew, done, info = env.step(env.action_space.sample())
       print(f"{obs} -> {rew}")
    env.close()
    

    در خروجی دفترچه باید چیزی شبیه به این ببینید:

```text
[ 0.03403272 -0.24301182  0.02669811  0.2895829 ] -> 1.0
[ 0.02917248 -0.04828055  0.03248977  0.00543839] -> 1.0
[ 0.02820687  0.14636075  0.03259854 -0.27681916] -> 1.0
[ 0.03113408  0.34100283  0.02706215 -0.55904489] -> 1.0
[ 0.03795414  0.53573468  0.01588125 -0.84308041] -> 1.0
...
[ 0.17299878  0.15868546 -0.20754175 -0.55975453] -> 1.0
[ 0.17617249  0.35602306 -0.21873684 -0.90998894] -> 1.0
```

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

  1. حداقل و حداکثر مقدار این اعداد را بگیرید: (کد بلاک 5)

    print(env.observation_space.low)
    print(env.observation_space.high)
    

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

    در واقع، شبیه‌سازی CartPole وقتی حل شده در نظر گرفته می‌شود که بتوانیم میانگین پاداش 195 را در 100 آزمایش متوالی کسب کنیم.

گسسته‌سازی حالت

در Q-Learning باید جدول Q ساخته شود که مشخص می‌کند در هر حالت چه باید انجام داد. برای این کار باید حالت به صورت گسسته باشد، دقیقا به این معنا که حالت شامل تعداد محدودی مقدار گسسته باشد. بنابراین باید مشاهدات خود را به یک مجموعه محدود از حالات نقشه‌برداری کنیم که به آن گسسته‌سازی گفته می‌شود.

چند روش برای انجام این کار وجود دارد:

  • تقسیم به بازه‌ها. اگر بازه یک مقدار مشخص باشد، می‌توانیم آن بازه را به تعدادی باین (bin) تقسیم کنیم و سپس مقدار را با شماره‌ی باین مربوطه جایگزین کنیم. این کار را می‌توان با روش digitize در numpy انجام داد. با این روش اندازه حالت دقیق مشخص می‌شود زیرا به تعداد باین‌های انتخابی بستگی دارد.

می‌توانیم از درون‌یابی خطی استفاده کنیم تا مقادیر را به بازه‌ای محدود (مثلاً -20 تا 20) بیاوریم و سپس با رند کردن به عدد صحیح تبدیل کنیم. این روش کنترل کمتری روی اندازه حالت می‌دهد، به خصوص اگر بازه دقیق مقادیر ورودی را ندانیم. مثلاً در مورد ما 2 مقدار از 4 مقدار برای مقادیرشان بازه بالایی یا پایینی تعریف نشده، که ممکن است منجر به تعداد نامحدودی حالت شود.

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

  1. تابعی که مشاهدات مدل را گرفته و یک تاپل از 4 عدد صحیح برمی‌گرداند را اینجا داریم: (کد بلاک 6)

    def discretize(x):
        return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
    
  2. بیایید روش گسسته‌سازی دیگر با استفاده از باین‌ها را هم امتحان کنیم: (کد بلاک 7)

    def create_bins(i,num):
        return np.arange(num+1)*(i[1]-i[0])/num+i[0]
    
    print("Sample bins for interval (-5,5) with 10 bins\n",create_bins((-5,5),10))
    
    ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # بازه‌های مقادیر برای هر پارامتر
    nbins = [20,20,10,10] # تعداد بین‌ها برای هر پارامتر
    bins = [create_bins(ints[i],nbins[i]) for i in range(4)]
    
    def discretize_bins(x):
        return tuple(np.digitize(x[i],bins[i]) for i in range(4))
    
  3. حالا یک شبیه‌سازی کوتاه اجرا کنیم و مقادیر گسسته محیط را مشاهده کنیم. می‌توانید هر دو تابع discretize و discretize_bins را امتحان کنید و ببینید تفاوتی دارد یا خیر.

    discretize_bins شماره باین را برمی‌گرداند که از 0 شروع می‌شود. بنابراین مقادیری نزدیک صفر عددی نزدیک به وسط بازه (10) را می‌دهد. در discretize ما به دامنه مقادیر خروجی اهمیت ندادیم و اجازه دادیم مقادیر منفی باشند، بنابراین مقادیر حالت جابه‌جا نشده و عدد صفر همان صفر است. (کد بلاک 8)

```python
env.reset()

done = False
while not done:
   #محیط را رندر می‌کند
   obs, rew, done, info = env.step(env.action_space.sample())
   #چاپ مقادیر گسسته‌شده مشاهده‌ها
   print(discretize(obs))
env.close()
```

    اگر می‌خواهید ببینید محیط چگونه اجرا می‌شود، خطی که با env.render شروع می‌شود را از حالت کامنت خارج کنید. در غیر این صورت می‌توانید کد را در پس‌زمینه اجرا کنید که سریع‌تر است. ما در فرآیند Q-Learning اجرای «نامرئی» را استفاده خواهیم کرد.

ساختار جدول Q

در درس قبلی حالت جفتی از اعداد 0 تا 8 بود و بنابراین برای نمایش جدول Q از یک تنسور numpy با شکل 8x8x2 استفاده کردیم. اگر از گسسته‌سازی با باین استفاده کنیم، اندازه بردار حالت هم مشخص است و می‌توانیم همان رویکرد را داشته باشیم و حالت را به آرایه‌ای با شکل 20x20x10x10x2 نمایش دهیم (اینجا 2 بعد فضای عمل است و ابعاد اولی تعداد باین تعیین‌شده برای هر پارامتر فضای مشاهده است).

اما گاهی ابعاد دقیق فضای مشاهده معلوم نیست. در مورد تابع discretize هرگز نمی‌توانیم مطمئن باشیم که حالت در محدودهای خاص باقی می‌ماند زیرا بعضی مقادیر اصلی محدود نیستند. بنابراین رویکردی کمی متفاوت خواهیم داشت و جدول Q را به صورت دیکشنری نمایش می‌دهیم.

  1. جفت (state, action) را به عنوان کلید دیکشنری استفاده کنید و مقدار متناظر مقدار خانه جدول Q باشد. (کد بلاک 9)

    Q = {}
    actions = (0,1)
    
    def qvalues(state):
        return [Q.get((state,a),0) for a in actions]
    

    اینجا همچنین تابع qvalues() تعریف شده که برای حالت داده شده مقادیر جدول Q را در قالب لیستی شامل تمام اعمال ممکن برمی‌گرداند. اگر مقدار در جدول Q موجود نباشد، مقدار پیش‌فرض 0 برگردانده می‌شود.

بیایید Q-Learning را شروع کنیم

اکنون آماده‌ایم به پیتر آموزش دهیم تعادلش را حفظ کند!

  1. ابتدا چند ابرپارامتر را تنظیم کنیم: (کد بلاک 10)

    # ابرپارامترها
    alpha = 0.3
    gamma = 0.9
    epsilon = 0.90
    

    اینجا، alpha نرخ یادگیری است که مشخص می‌کند در هر گام تا چه حد باید مقادیر فعلی جدول Q را تنظیم کنیم. در درس قبلی با 1 شروع کردیم و سپس alpha را به مقادیر کمتر در طول آموزش کاهش دادیم. در این مثال برای سادگی ثابت نگه می‌داریم و می‌توانید بعداً با تغییر alpha آزمایش کنید.

    gamma فاکتور تخفیف است که نشان می‌دهد تا چه حد باید پاداش آینده را به پاداش فعلی ترجیح دهیم.

    epsilon فاکتور اکتشاف/بهره‌برداری است که تعیین می‌کند آیا باید اکتشاف را ترجیح دهیم یا بهره‌برداری را. در الگوریتم ما در درصد epsilon بعدی عمل بر اساس مقادیر جدول Q انتخاب می‌شود و در باقی موارد عمل به صورت تصادفی انجام می‌شود. این اجازه می‌دهد بخش‌هایی از فضای جستجو را که قبلاً ندیده‌ایم کاوش کنیم.

    از لحاظ تعادل - انتخاب عمل تصادفی (اکتشاف) مانند یک ضربه تصادفی به جهت اشتباه است و قطب باید یاد بگیرد چگونه تعادل را از این «اشتباهات» بازیابی کند.

بهبود الگوریتم

همچنین می‌توانیم دو بهبود نسبت به الگوریتم درس قبلی انجام دهیم:

  • محاسبه میانگین پاداش تجمعی روی تعدادی شبیه‌سازی. هر 5000 تکرار پیشرفت را چاپ می‌کنیم و میانگین پاداش تجمعی در این بازه زمانی گرفته می‌شود. این یعنی اگر بیش از 195 امتیاز بگیریم، می‌توانیم مسئله را حل شده با کیفیت بالاتر از حد لازم در نظر بگیریم.

  • محاسبه بیشینه میانگین نتیجه تجمعی، Qmax و ذخیره جدول Q مربوط به آن نتیجه. وقتی آموزش را اجرا می‌کنید ممکن است ببینید که بعضی مواقع میانگین نتیجه تجمعی شروع به افت می‌کند و ما می‌خواهیم مقادیر جدول Q مربوط به بهترین مدل دیده شده در آموزش را نگه داریم.

  1. تمام پاداش‌های تجمعی هر شبیه‌سازی را در بردار rewards جمع‌آوری کنید برای رسم نمودارهای بعدی. (کد بلاک 11)

    def probs(v,eps=1e-4):
        v = v-v.min()+eps
        v = v/v.sum()
        return v
    
    Qmax = 0
    cum_rewards = []
    rewards = []
    for epoch in range(100000):
        obs = env.reset()
        done = False
        cum_reward=0
        # == انجام شبیه‌سازی ==
        while not done:
            s = discretize(obs)
            if random.random()<epsilon:
                # بهره‌برداری - انتخاب عمل بر اساس احتمالات جدول Q
                v = probs(np.array(qvalues(s)))
                a = random.choices(actions,weights=v)[0]
            else:
                # کاوش - انتخاب تصادفی عمل
                a = np.random.randint(env.action_space.n)
    
            obs, rew, done, info = env.step(a)
            cum_reward+=rew
            ns = discretize(obs)
            Q[(s,a)] = (1 - alpha) * Q.get((s,a),0) + alpha * (rew + gamma * max(qvalues(ns)))
        cum_rewards.append(cum_reward)
        rewards.append(cum_reward)
        # == دوره‌ای نتایج را چاپ کرده و میانگین پاداش را محاسبه کنید ==
        if epoch%5000==0:
            print(f"{epoch}: {np.average(cum_rewards)}, alpha={alpha}, epsilon={epsilon}")
            if np.average(cum_rewards) > Qmax:
                Qmax = np.average(cum_rewards)
                Qbest = Q
            cum_rewards=[]
    

آنچه ممکن است از این نتایج متوجه شوید:

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

  • پاداش شروع به افت می‌کند. گاهی پاداش شروع به افت می‌کند، یعنی ممکن است مقادیر یادگرفته شده در جدول Q را با مقادیری جایگزین کنیم که وضعیت را بدتر می‌کنند.

این مشاهده واضح‌تر است اگر پیشرفت آموزش را رسم کنیم.

رسم پیشرفت آموزش

در طول آموزش مقدار پاداش تجمعی هر تکرار را در بردار rewards جمع‌آوری کردیم. اینجا وقتی آن را نسبت به شماره تکرار رسم کنیم اینطور دیده می‌شود:

plt.plot(rewards)

پیشرفت خام

از این نمودار نمی‌توان هیچ چیز خاصی فهمید چون به دلیل طبیعت تصادفی فرآیند آموزش طول جلسات آموزش بسیار متفاوت است. برای درک بهتر می‌توان میانگین متحرک (running average) در فاصله‌ای مثلاً 100 آزمایش حساب کرد. این کار راحت با np.convolve انجام می‌شود: (کد بلاک 12)

def running_average(x,window):
    return np.convolve(x,np.ones(window)/window,mode='valid')

plt.plot(running_average(rewards,100))

پیشرفت آموزش

تغییر ابرپارامترها

برای پایدارتر کردن یادگیری، منطقی است که برخی از ابرپارامترها را در طول آموزش تنظیم کنیم. به خصوص:

  • برای نرخ یادگیری، alpha، می‌توانیم با مقادیر نزدیک به 1 شروع کنیم و سپس این پارامتر را کم کم کاهش دهیم. با گذشت زمان مقادیر احتمال خوبی در جدول Q خواهیم داشت پس باید آنها را کمی تنظیم کنیم و به طور کامل بازنویسی نکنیم.

  • افزایش epsilon. ممکن است بخواهیم epsilon را به آرامی افزایش دهیم تا کمتر اکتشاف کنیم و بیشتر بهره‌برداری کنیم. احتمالاً منطقی است با مقدار کم epsilon شروع کنیم و به سمت مقدار نزدیک به 1 پیش برویم.

وظیفه 1: با مقادیر ابرپارامتر بازی کنید و ببینید می‌توانید پاداش تجمعی بالاتری به دست آورید. آیا امتیاز بالای 195 می‌گیرید؟

وظیفه ۲: برای حل رسمی مسئله، باید میانگین پاداش ۱۹۵ را در طول ۱۰۰ اجرای متوالی به دست آورید. این را در طول آموزش اندازه‌گیری کنید و مطمئن شوید که مسئله به صورت رسمی حل شده است!

دیدن نتیجه در عمل

جالب خواهد بود که ببینیم مدل آموزش‌دیده چگونه رفتار می‌کند. بیایید شبیه‌سازی را اجرا کنیم و همان استراتژی انتخاب عمل در زمان آموزش را دنبال کنیم، نمونه‌برداری مطابق با توزیع احتمال در Q-Table: (بلاک کد ۱۳)

obs = env.reset()
done = False
while not done:
   s = discretize(obs)
   env.render()
   v = probs(np.array(qvalues(s)))
   a = random.choices(actions,weights=v)[0]
   obs,_,done,_ = env.step(a)
env.close()

شما باید چیزی شبیه به این ببینید:

یک میله تعادلی روی چرخ‌دستی


🚀چالش

وظیفه ۳: در اینجا، ما از نسخه نهایی Q-Table استفاده می‌کردیم، که ممکن است بهترین نسخه نباشد. به خاطر داشته باشید که بهترین Q-Table عملکرد را در متغیر Qbest ذخیره کرده‌ایم! همان مثال را با بهترین Q-Table امتحان کنید، با کپی کردن Qbest به Q و ببینید که آیا تفاوتی مشاهده می‌کنید یا خیر.

وظیفه ۴: در اینجا ما بهترین عمل را در هر مرحله انتخاب نمی‌کردیم، بلکه نمونه‌برداری با توجه به توزیع احتمال مربوطه انجام می‌دادیم. آیا منطقی‌تر نیست که همیشه بهترین عمل، یعنی بالاترین مقدار Q-Table را انتخاب کنیم؟ این کار می‌تواند با استفاده از تابع np.argmax انجام شود تا شماره عملیات مربوط به بالاترین مقدار Q-Table را پیدا کند. این استراتژی را پیاده‌سازی کنید و ببینید آیا تعادل بهتری برقرار می‌شود یا خیر.

آزمون پس از درس

تکلیف

آموزش یک ماشین کوهستانی

نتیجه‌گیری

ما اکنون یاد گرفته‌ایم که چگونه عامل‌ها را آموزش دهیم تا فقط با ارائه تابع پاداش که حالت دلخواه بازی را تعریف می‌کند، و با دادن فرصت کاوش هوشمندانه در فضای جستجو، نتایج خوبی کسب کنند. ما الگوریتم Q-Learning را با موفقیت در محیط‌های گسسته و پیوسته، اما با اعمال گسسته، پیاده‌سازی کرده‌ایم.

همچنین مهم است که موقعیت‌هایی را بررسی کنیم که حالت عمل نیز پیوسته است و زمانی که فضای مشاهده بسیار پیچیده‌تر است، مانند تصویر صفحه بازی آتاری. در این مسائل معمولاً باید از تکنیک‌های قدرتمندتر یادگیری ماشین، مانند شبکه‌های عصبی، برای کسب نتایج بهتر استفاده کنیم. این مباحث پیشرفته‌تر موضوع دوره پیشرفته‌تر آینده ما در هوش مصنوعی است.


سلب مسئولیت: این سند با استفاده از سرویس ترجمه هوش مصنوعی Co-op Translator ترجمه شده است. در حالی که ما در تلاش برای دقت هستیم، لطفاً توجه داشته باشید که ترجمه‌های خودکار ممکن است شامل خطاها یا نادرستی‌هایی باشند. سند اصلی به زبان مادری خود باید به عنوان منبع معتبر در نظر گرفته شود. برای اطلاعات حیاتی، ترجمه حرفه‌ای انسانی توصیه می‌شود. ما در قبال هرگونه سوء تفاهم یا برداشت نادرست ناشی از استفاده از این ترجمه مسئولیتی نداریم.