فصل ۱۸: یادگیری تقویتی
یادگیری تقویتی (Reinforcement Learning یا RL) یکی از شاخههای قدیمی و در عین حال بسیار فعال یادگیری ماشین است. ایدهٔ اصلی این است که یک عامل نرمافزاری درون یک محیط مشاهده انجام میدهد، عملی را انتخاب میکند و از محیط پاداش میگیرد. هدف عامل این است که از راه آزمون و خطا سیاستی بیاموزد که مجموع پاداش مورد انتظار را در گذر زمان بیشینه کند. جهش بزرگ این حوزه زمانی رخ داد که DeepMind نشان داد یک سامانهٔ مبتنی بر یادگیری عمیق میتواند تنها با دریافت پیکسلهای خام، بازیهای Atari را از صفر یاد بگیرد و در بسیاری از آنها از انسان بهتر عمل کند؛ موفقیتهای بعدی AlphaGo این مسیر را پررنگتر کرد.
یادگیری برای بیشینهکردن پاداش
در RL، عامل در هر گام وضعیت محیط را از طریق Observation میبیند، یک Action اجرا میکند و Reward دریافت میکند. پاداش مثبت یا منفی تنها سیگنال مستقیم یادگیری است. محیط میتواند یک ربات واقعی، بازی رایانهای، صفحهٔ Go، ترموستات هوشمند، سامانهٔ معاملهگر یا هر سیستم پویای دیگری باشد.
- در رباتیک، Observation از حسگرها و دوربینها میآید و Action فرمان موتور است.
- در بازی Ms. Pac-Man، تصویر بازی Observation، وضعیت Joystick عمل و امتیاز بازی پاداش است.
- در Go، عامل ممکن است فقط در پایان بازی برای برد یا باخت پاداش بگیرد.
- در ترموستات، نزدیک ماندن به دمای مطلوب و کاهش مصرف انرژی میتواند پاداش مثبت ایجاد کند.
- در معاملهٔ خودکار، سود و زیان مالی نقش Reward را دارند.
شکل 18-1. یادگیری برای بیشینهکردن پاداش
وجود پاداش مثبت الزامی نیست. برای نمونه، عاملی که در هزارتو در هر گام یک جریمهٔ کوچک میگیرد، برای رسیدن سریعتر به خروجی مجبور میشود مسیر کارآمدتری بیاموزد. همین چارچوب برای خودروهای خودران، سامانههای پیشنهاددهنده، انتخاب تبلیغ و حتی تعیین ناحیهٔ توجه یک مدل بینایی نیز کاربرد دارد.
جستوجوی سیاست
الگوریتمی که تعیین میکند عامل در هر وضعیت چه عملی انجام دهد سیاست (Policy) نام دارد. سیاست میتواند یک قانون ساده یا یک شبکهٔ عصبی باشد که Observation را میگیرد و احتمال یا مقدار مناسب برای Actionها را تولید میکند.
شکل 18-2. جستوجوی سیاست
سیاست لزوماً Deterministic نیست. فرض کنید یک جاروبرقی رباتیک با احتمال p رو به جلو حرکت کند و با احتمال 1-p به چپ یا راست بچرخد و زاویهٔ چرخش نیز در بازهٔ -r تا +r تصادفی باشد. این یک سیاست تصادفی است. دو پارامتر p و r را میتوان با امتحانکردن ترکیبهای مختلف و انتخاب بهترین نتیجه تنظیم کرد؛ این همان جستوجوی Brute Force در فضای سیاستها است.
شکل 18-3. جستوجوی سیاست
برای فضاهای بزرگ، جستوجوی مستقیم بسیار پرهزینه است. الگوریتم ژنتیک یک راه دیگر است: جمعیتی از سیاستها ساخته میشود، سیاستهای ضعیف حذف میشوند و سیاستهای بهتر با جهش و ترکیب، نسل بعد را تشکیل میدهند. راه سوم استفاده از Gradient است: پارامترهای Policy در جهتی تغییر میکنند که Reward افزایش یابد. این خانواده از روشها Policy Gradient نام دارد.
آشنایی با OpenAI Gym و محیط CartPole
برای Train کردن عامل RL معمولاً به یک محیط شبیهسازیشده نیاز داریم؛ زیرا آموزش مستقیم در دنیای واقعی کند، پرهزینه و گاهی خطرناک است. OpenAI Gym مجموعهای از Environmentهای استاندارد برای بازیها، کنترل کلاسیک و شبیهسازی فیزیکی فراهم میکند. نمونهٔ این فصل محیط CartPole است که در آن باید با حرکت دادن یک گاری به چپ یا راست، میلهای را عمودی نگه داشت.
import gym
env = gym.make("CartPole-v1", render_mode="rgb_array")
obs, info = env.reset(seed=42)
شکل 18-4. آشنایی با OpenAI Gym و محیط CartPole
Observation در CartPole یک بردار چهاربعدی است: موقعیت افقی گاری، سرعت گاری، زاویهٔ میله و سرعت زاویهای. فضای Action نیز Discrete(2) است: مقدار صفر حرکت به چپ و مقدار یک حرکت به راست را نشان میدهد.
action = 1
obs, reward, done, truncated, info = env.step(action)
متد step() پس از اجرای عمل، Observation جدید، Reward، وضعیت پایان طبیعی Episode، وضعیت قطعشدن زودهنگام و اطلاعات اضافی را برمیگرداند. در CartPole در هر Step یک پاداش 1.0 داده میشود؛ بنابراین هرچه میله مدت بیشتری نیفتد، مجموع Reward بیشتر است.
یک Policy ساده میتواند فقط زاویهٔ میله را نگاه کند و اگر میله به چپ متمایل بود گاری را به چپ و در غیر این صورت به راست شتاب دهد:
def basic_policy(obs):
angle = obs[2]
return 0 if angle < 0 else 1
اجرای این قانون در ۵۰۰ Episode میانگین حدود 41.7 گام به دست میدهد و حتی بهترین Episode نیز بیش از 63 گام دوام نمیآورد؛ پس سیاست دستنویس هنوز ضعیف است.
سیاست شبکهٔ عصبی و مسئلهٔ اکتشاف در برابر بهرهبرداری
در سیاست شبکهٔ عصبی، مدل Observation را دریافت و احتمال Actionها را تولید میکند. برای CartPole کافی است یک خروجی Sigmoid داشته باشیم که احتمال حرکت به چپ را برآورد کند. بهجای انتخاب همیشگی Action با بیشترین احتمال، Action بهصورت تصادفی بر اساس همین احتمالها Sample میشود. این کار تعادل میان Exploration و Exploitation را حفظ میکند: عامل هم از رفتارهایی که خوب میداند استفاده میکند و هم گاهی رفتار تازهای را امتحان میکند.
شکل 18-5. سیاست شبکهٔ عصبی و مسئلهٔ اکتشاف در برابر بهرهبرداری
import tensorflow as tf
model = tf.keras.Sequential([
tf.keras.layers.Dense(5, activation="relu"),
tf.keras.layers.Dense(1, activation="sigmoid"),
])
اگر محیط تمام State را در Observation آشکار کند، Policy میتواند فقط Observation فعلی را ببیند. اگر بخشی از State مخفی یا Observation نویزی باشد، لازم است تاریخچهٔ Observation و Actionها نیز در نظر گرفته شود.
ارزیابی Actionها و مسئلهٔ تخصیص اعتبار
برخلاف Supervised Learning، در RL معمولاً نمیدانیم بهترین Action در هر Step چیست. Rewardها اغلب با تأخیر و پراکندهاند. اگر میله پس از ۱۰۰ Action بیفتد، مشخص نیست کدام Actionهای قبلی مفید و کدام مضر بودهاند. این مشکل Credit Assignment نام دارد.
یک روش رایج این است که ارزش هر Action را برابر مجموع Rewardهای آینده با Discount در نظر بگیریم. اگر ضریب تخفیف را با γ نشان دهیم، Return یک Action برابر است با پاداش همان Step بهعلاوهٔ پاداشهای آینده که هرچه دورترند با توانهای بیشتری از γ کاهش مییابند.
شکل 18-6. ارزیابی Actionها و مسئلهٔ تخصیص اعتبار
برای مثال با Rewardهای [10, 0, -50] و γ=0.8، Return اولین Action برابر -22 است. ضریب تخفیف نزدیک صفر آینده را کماهمیت میکند و مقدار نزدیک یک پاداشهای دور را نیز مهم نگه میدارد. در CartPole مقدار 0.95 انتخاب مناسبی است.
برای مقایسهٔ Actionها، Returnهای Episodeهای متعدد محاسبه و سپس با کمکردن میانگین و تقسیم بر انحراف معیار نرمال میشوند. مقدار مثبت را میتوان نشانهٔ Action بهتر از متوسط و مقدار منفی را نشانهٔ Action ضعیفتر در نظر گرفت. این کمیت Advantage نام دارد.
Policy Gradient و الگوریتم REINFORCE
در REINFORCE ابتدا چند Episode با Policy فعلی اجرا میشود. در هر Step گرادیانی محاسبه میکنیم که Action انتخابشده را محتملتر کند، اما هنوز آن را اعمال نمیکنیم. پس از پایان Episodeها، Advantage هر Action محاسبه میشود. Gradient یک Action خوب در همان جهت و Gradient Action بد با علامت معکوس استفاده میشود. در پایان میانگین Gradientهای وزنخورده به Optimizer داده میشود.
def play_one_step(env, obs, model, loss_fn):
with tf.GradientTape() as tape:
left_proba = model(obs[np.newaxis])
action = (tf.random.uniform([1, 1]) > left_proba)
y_target = tf.constant([[1.]]) - tf.cast(action, tf.float32)
loss = tf.reduce_mean(loss_fn(y_target, left_proba))
grads = tape.gradient(loss, model.trainable_variables)
obs, reward, done, truncated, info = env.step(int(action))
return obs, reward, done, truncated, grads
تابع دیگری چند Episode را اجرا میکند و Reward و Gradient تمام Stepها را نگه میدارد. سپس Rewardهای تخفیفخورده محاسبه و نرمال میشوند:
def discount_rewards(rewards, discount_factor):
discounted = np.array(rewards)
for step in range(len(rewards) - 2, -1, -1):
discounted[step] += discounted[step + 1] * discount_factor
return discounted
def discount_and_normalize_rewards(all_rewards, discount_factor):
all_discounted_rewards = [
discount_rewards(rewards, discount_factor)
for rewards in all_rewards
]
flat_rewards = np.concatenate(all_discounted_rewards)
mean = flat_rewards.mean()
std = flat_rewards.std()
return [(rewards - mean) / std for rewards in all_discounted_rewards]
در مثال فصل، ۱۵۰ Iteration، در هر Iteration ده Episode و حداکثر ۲۰۰ Step اجرا میشود. با discount_factor=0.95، Optimizer از نوع Nadam با Learning Rate برابر 0.01 و Loss از نوع Binary Cross-Entropy است. پس از وزندهی Gradientها با Advantage، مدل بهتدریج میآموزد میله را نزدیک سقف ۲۰۰ Step نگه دارد.
Policy Gradient ساده از نظر Sample Efficiency ضعیف است، چون برای برآورد Advantage به Episodeهای زیادی نیاز دارد. با این حال پایهٔ بسیاری از روشهای قویتر مانند Actor-Critic است. اگر دربارهٔ محیط دانش قبلی دارید، استفاده از Reward Shaping یا تقلید از یک Policy اولیهٔ مناسب میتواند آموزش را بسیار سریعتر کند.
زنجیرههای مارکوف و فرایند تصمیمگیری مارکوف
زنجیرهٔ مارکوف یک فرایند تصادفی بدون حافظه است: احتمال رفتن از State فعلی به State بعدی فقط به همین دو State وابسته است و نه به مسیر گذشته. شکل زیر یک زنجیرهٔ چهارحالته را نشان میدهد که یکی از Stateها Terminal است.
شکل 18-7. زنجیرههای مارکوف و فرایند تصمیمگیری مارکوف
فرایند تصمیمگیری مارکوف (MDP) این ایده را گسترش میدهد. در هر State عامل از میان چند Action انتخاب میکند؛ احتمال Transition به State بعدی به Action وابسته است و Transition میتواند Reward مثبت یا منفی داشته باشد. هدف، یافتن Policyای است که Reward تجمعی را بیشینه کند.
شکل 18-8. زنجیرههای مارکوف و فرایند تصمیمگیری مارکوف
در MDP مثال فصل، در Stateهای مختلف Actionهای متفاوتی مجازند و برخی مسیرها پاداش +10، +40 یا جریمهٔ -50 دارند. انتخاب بهترین Action به میزان اهمیتی که برای Rewardهای آینده قائل هستیم وابسته است.
معادلهٔ بهینگی Bellman و Value Iteration
Bellman مقدار بهینهٔ State را با V*(s) تعریف میکند: مجموع Rewardهای آینده که عامل در صورت رفتار بهینه از State s انتظار دارد. معادلهٔ بهینگی آن چنین است:
V*(s) = max_a Σ_s' T(s,a,s') [ R(s,a,s') + γ V*(s') ]
در این رابطه T(s,a,s') احتمال Transition، R(s,a,s') Reward آن Transition و γ ضریب تخفیف است. از همین رابطه الگوریتم Value Iteration به دست میآید:
V_(k+1)(s) =
max_a Σ_s' T(s,a,s') [ R(s,a,s') + γ V_k(s') ]
تمام Valueها ابتدا صفر میشوند و رابطه بارها تکرار میشود. با Iteration کافی، برآوردها به Valueهای بهینه همگرا میشوند. این نمونهای از Dynamic Programming است؛ یعنی مسئلهٔ پیچیده به زیرمسئلههایی شکسته میشود که بهصورت تکراری حل میشوند.