AI-For-Beginners/translations/fa/lessons/6-Other/22-DeepRL/CartPole-RL-PyTorch.ipynb

501 lines
23 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# آموزش RL برای متعادل کردن Cartpole\n",
"\n",
"این نوت‌بوک بخشی از [برنامه درسی AI برای مبتدیان](http://aka.ms/ai-beginners) است. این آموزش از [آموزش رسمی PyTorch](https://pytorch.org/tutorials/intermediate/reinforcement_q_learning.html) و [این پیاده‌سازی Cartpole با PyTorch](https://github.com/yc930401/Actor-Critic-pytorch) الهام گرفته شده است.\n",
"\n",
"در این مثال، از یادگیری تقویتی (RL) برای آموزش مدلی استفاده خواهیم کرد که بتواند یک میله را روی یک چرخ‌دستی که می‌تواند به چپ و راست روی یک محور افقی حرکت کند، متعادل نگه دارد. برای شبیه‌سازی این میله از محیط [OpenAI Gym](https://www.gymlibrary.ml/) استفاده خواهیم کرد.\n",
"\n",
"> **توجه**: می‌توانید کد این درس را به صورت محلی (مثلاً از طریق Visual Studio Code) اجرا کنید، که در این صورت شبیه‌سازی در یک پنجره جدید باز خواهد شد. اگر کد را به صورت آنلاین اجرا می‌کنید، ممکن است نیاز باشد برخی تغییرات در کد اعمال کنید، همان‌طور که [اینجا](https://towardsdatascience.com/rendering-openai-gym-envs-on-binder-and-google-colab-536f99391cc7) توضیح داده شده است.\n",
"\n",
"ابتدا مطمئن می‌شویم که Gym نصب شده است:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"!{sys.executable} -m pip install gym"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"حالا بیایید محیط CartPole را ایجاد کنیم و ببینیم چگونه می‌توانیم با آن کار کنیم. یک محیط دارای ویژگی‌های زیر است:\n",
"\n",
"* **فضای اقدام** مجموعه‌ای از اقدامات ممکن است که می‌توانیم در هر مرحله از شبیه‌سازی انجام دهیم \n",
"* **فضای مشاهده** فضایی است که مشاهداتی که می‌توانیم انجام دهیم در آن قرار دارد \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import gym\n",
"\n",
"env = gym.make(\"CartPole-v1\")\n",
"\n",
"print(f\"Action space: {env.action_space}\")\n",
"print(f\"Observation space: {env.observation_space}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"بیایید ببینیم شبیه‌سازی چگونه کار می‌کند. حلقه زیر شبیه‌سازی را اجرا می‌کند تا زمانی که `env.step` پرچم پایان `done` را برنگرداند. ما به صورت تصادفی اقدامات را با استفاده از `env.action_space.sample()` انتخاب می‌کنیم، که به این معناست که آزمایش احتمالاً خیلی سریع شکست خواهد خورد (محیط CartPole زمانی پایان می‌یابد که سرعت CartPole، موقعیت یا زاویه آن خارج از محدودیت‌های مشخص باشد).\n",
"\n",
"> شبیه‌سازی در یک پنجره جدید باز خواهد شد. شما می‌توانید کد را چندین بار اجرا کنید و ببینید چگونه رفتار می‌کند.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"env.reset()\n",
"\n",
"done = False\n",
"total_reward = 0\n",
"while not done:\n",
" env.render()\n",
" obs, rew, done, info = env.step(env.action_space.sample())\n",
" total_reward += rew\n",
" print(f\"{obs} -> {rew}\")\n",
"print(f\"Total reward: {total_reward}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"شما می‌توانید مشاهده کنید که مشاهدات شامل ۴ عدد هستند. این اعداد عبارتند از:\n",
"- موقعیت چرخ‌دستی\n",
"- سرعت چرخ‌دستی\n",
"- زاویه میله\n",
"- نرخ چرخش میله\n",
"\n",
"`rew` پاداشی است که در هر مرحله دریافت می‌کنیم. در محیط CartPole، شما برای هر مرحله شبیه‌سازی ۱ امتیاز دریافت می‌کنید و هدف این است که مجموع پاداش‌ها را به حداکثر برسانید، یعنی مدت زمانی که CartPole می‌تواند بدون افتادن تعادل خود را حفظ کند.\n",
"\n",
"در یادگیری تقویتی، هدف ما آموزش یک **سیاست** $\\pi$ است که برای هر حالت $s$ به ما بگوید کدام عمل $a$ را انجام دهیم، به عبارت دیگر $a = \\pi(s)$.\n",
"\n",
"اگر بخواهید یک راه‌حل احتمالی داشته باشید، می‌توانید سیاست را به‌عنوان بازگرداندن مجموعه‌ای از احتمالات برای هر عمل تصور کنید، یعنی $\\pi(a|s)$ به معنای احتمال انجام عمل $a$ در حالت $s$ خواهد بود.\n",
"\n",
"## روش گرادیان سیاست\n",
"\n",
"در ساده‌ترین الگوریتم RL، که **گرادیان سیاست** نامیده می‌شود، ما یک شبکه عصبی را آموزش می‌دهیم تا عمل بعدی را پیش‌بینی کند.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import torch\n",
"\n",
"num_inputs = 4\n",
"num_actions = 2\n",
"\n",
"model = torch.nn.Sequential(\n",
" torch.nn.Linear(num_inputs, 128, bias=False, dtype=torch.float32),\n",
" torch.nn.ReLU(),\n",
" torch.nn.Linear(128, num_actions, bias = False, dtype=torch.float32),\n",
" torch.nn.Softmax(dim=1)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"ما شبکه را با اجرای آزمایش‌های متعدد آموزش خواهیم داد و پس از هر اجرا شبکه خود را به‌روزرسانی می‌کنیم. بیایید تابعی تعریف کنیم که آزمایش را اجرا کرده و نتایج (به‌اصطلاح **ردپا**) - تمام حالت‌ها، اقدامات (و احتمالات پیشنهادی آن‌ها) و پاداش‌ها - را بازگرداند:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def run_episode(max_steps_per_episode = 10000,render=False): \n",
" states, actions, probs, rewards = [],[],[],[]\n",
" state = env.reset()\n",
" for _ in range(max_steps_per_episode):\n",
" if render:\n",
" env.render()\n",
" action_probs = model(torch.from_numpy(np.expand_dims(state,0)))[0]\n",
" action = np.random.choice(num_actions, p=np.squeeze(action_probs.detach().numpy()))\n",
" nstate, reward, done, info = env.step(action)\n",
" if done:\n",
" break\n",
" states.append(state)\n",
" actions.append(action)\n",
" probs.append(action_probs.detach().numpy())\n",
" rewards.append(reward)\n",
" state = nstate\n",
" return np.vstack(states), np.vstack(actions), np.vstack(probs), np.vstack(rewards)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"شما می‌توانید یک قسمت را با شبکه آموزش‌ندیده اجرا کنید و مشاهده کنید که پاداش کل (یعنی طول قسمت) بسیار کم است:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"s, a, p, r = run_episode()\n",
"print(f\"Total reward: {np.sum(r)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"یکی از جنبه‌های پیچیده الگوریتم گرادیان سیاست، استفاده از **پاداش‌های تخفیف‌خورده** است. ایده این است که بردار مجموع پاداش‌ها را در هر مرحله از بازی محاسبه کنیم و در این فرآیند، پاداش‌های اولیه را با استفاده از ضریب $gamma$ تخفیف دهیم. همچنین بردار حاصل را نرمال‌سازی می‌کنیم، زیرا قصد داریم از آن به عنوان وزن برای تأثیرگذاری بر آموزش خود استفاده کنیم:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"eps = 0.0001\n",
"\n",
"def discounted_rewards(rewards,gamma=0.99,normalize=True):\n",
" ret = []\n",
" s = 0\n",
" for r in rewards[::-1]:\n",
" s = r + gamma * s\n",
" ret.insert(0, s)\n",
" if normalize:\n",
" ret = (ret-np.mean(ret))/(np.std(ret)+eps)\n",
" return ret"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"حالا بیایید آموزش واقعی را شروع کنیم! ما ۳۰۰ قسمت اجرا خواهیم کرد و در هر قسمت مراحل زیر را انجام می‌دهیم:\n",
"\n",
"1. آزمایش را اجرا کرده و ردگیری (trace) را جمع‌آوری می‌کنیم.\n",
"2. تفاوت (`gradients`) بین اقدامات انجام‌شده و احتمالات پیش‌بینی‌شده را محاسبه می‌کنیم. هرچه این تفاوت کمتر باشد، بیشتر مطمئن می‌شویم که اقدام درست را انجام داده‌ایم.\n",
"3. پاداش‌های تخفیف‌خورده را محاسبه کرده و گرادیان‌ها را در پاداش‌های تخفیف‌خورده ضرب می‌کنیم - این کار تضمین می‌کند که گام‌هایی با پاداش‌های بالاتر تأثیر بیشتری بر نتیجه نهایی داشته باشند نسبت به گام‌هایی با پاداش‌های کمتر.\n",
"4. اقدامات هدف مورد انتظار برای شبکه عصبی ما تا حدی از احتمالات پیش‌بینی‌شده در طول اجرا گرفته می‌شود و تا حدی از گرادیان‌های محاسبه‌شده. ما از پارامتر `alpha` برای تعیین میزان تأثیر گرادیان‌ها و پاداش‌ها استفاده می‌کنیم - این مفهوم به عنوان *نرخ یادگیری* الگوریتم تقویتی شناخته می‌شود.\n",
"5. در نهایت، شبکه خود را بر اساس حالت‌ها و اقدامات مورد انتظار آموزش می‌دهیم و این فرآیند را تکرار می‌کنیم.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"optimizer = torch.optim.Adam(model.parameters(), lr=0.01)\n",
"\n",
"def train_on_batch(x, y):\n",
" x = torch.from_numpy(x)\n",
" y = torch.from_numpy(y)\n",
" optimizer.zero_grad()\n",
" predictions = model(x)\n",
" loss = -torch.mean(torch.log(predictions) * y)\n",
" loss.backward()\n",
" optimizer.step()\n",
" return loss"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alpha = 1e-4\n",
"\n",
"history = []\n",
"for epoch in range(300):\n",
" states, actions, probs, rewards = run_episode()\n",
" one_hot_actions = np.eye(2)[actions.T][0]\n",
" gradients = one_hot_actions-probs\n",
" dr = discounted_rewards(rewards)\n",
" gradients *= dr\n",
" target = alpha*np.vstack([gradients])+probs\n",
" train_on_batch(states,target)\n",
" history.append(np.sum(rewards))\n",
" if epoch%100==0:\n",
" print(f\"{epoch} -> {np.sum(rewards)}\")\n",
"\n",
"plt.plot(history)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"حالا بیایید قسمت را با رندر اجرا کنیم تا نتیجه را ببینیم:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"_ = run_episode(render=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"امیدوارم متوجه شده باشید که اکنون میله می‌تواند به‌خوبی تعادل خود را حفظ کند!\n",
"\n",
"## مدل Actor-Critic\n",
"\n",
"مدل Actor-Critic توسعه‌ای پیشرفته‌تر از گرادیان‌های سیاستی (policy gradients) است که در آن یک شبکه عصبی طراحی می‌کنیم تا هم سیاست (policy) و هم پاداش‌های تخمینی را یاد بگیرد. این شبکه دو خروجی خواهد داشت (یا می‌توانید آن را به‌عنوان دو شبکه جداگانه در نظر بگیرید):\n",
"* **Actor** عملی را که باید انجام شود پیشنهاد می‌دهد و توزیع احتمالات حالت را به ما می‌دهد، مشابه مدل گرادیان سیاستی.\n",
"* **Critic** تخمین می‌زند که پاداش حاصل از آن اقدامات چه خواهد بود. این بخش مجموع پاداش‌های تخمینی در آینده را در حالت فعلی بازمی‌گرداند.\n",
"\n",
"بیایید چنین مدلی را تعریف کنیم:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from itertools import count\n",
"import torch.nn.functional as F"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
"env = gym.make(\"CartPole-v1\")\n",
"\n",
"state_size = env.observation_space.shape[0]\n",
"action_size = env.action_space.n\n",
"lr = 0.0001\n",
"\n",
"class Actor(torch.nn.Module):\n",
" def __init__(self, state_size, action_size):\n",
" super(Actor, self).__init__()\n",
" self.state_size = state_size\n",
" self.action_size = action_size\n",
" self.linear1 = torch.nn.Linear(self.state_size, 128)\n",
" self.linear2 = torch.nn.Linear(128, 256)\n",
" self.linear3 = torch.nn.Linear(256, self.action_size)\n",
"\n",
" def forward(self, state):\n",
" output = F.relu(self.linear1(state))\n",
" output = F.relu(self.linear2(output))\n",
" output = self.linear3(output)\n",
" distribution = torch.distributions.Categorical(F.softmax(output, dim=-1))\n",
" return distribution\n",
"\n",
"\n",
"class Critic(torch.nn.Module):\n",
" def __init__(self, state_size, action_size):\n",
" super(Critic, self).__init__()\n",
" self.state_size = state_size\n",
" self.action_size = action_size\n",
" self.linear1 = torch.nn.Linear(self.state_size, 128)\n",
" self.linear2 = torch.nn.Linear(128, 256)\n",
" self.linear3 = torch.nn.Linear(256, 1)\n",
"\n",
" def forward(self, state):\n",
" output = F.relu(self.linear1(state))\n",
" output = F.relu(self.linear2(output))\n",
" value = self.linear3(output)\n",
" return value"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"ما نیاز داریم که توابع `discounted_rewards` و `run_episode` خود را کمی تغییر دهیم:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def discounted_rewards(next_value, rewards, masks, gamma=0.99):\n",
" R = next_value\n",
" returns = []\n",
" for step in reversed(range(len(rewards))):\n",
" R = rewards[step] + gamma * R * masks[step]\n",
" returns.insert(0, R)\n",
" return returns\n",
"\n",
"def run_episode(actor, critic, n_iters):\n",
" optimizerA = torch.optim.Adam(actor.parameters())\n",
" optimizerC = torch.optim.Adam(critic.parameters())\n",
" for iter in range(n_iters):\n",
" state = env.reset()\n",
" log_probs = []\n",
" values = []\n",
" rewards = []\n",
" masks = []\n",
" entropy = 0\n",
" env.reset()\n",
"\n",
" for i in count():\n",
" env.render()\n",
" state = torch.FloatTensor(state).to(device)\n",
" dist, value = actor(state), critic(state)\n",
"\n",
" action = dist.sample()\n",
" next_state, reward, done, _ = env.step(action.cpu().numpy())\n",
"\n",
" log_prob = dist.log_prob(action).unsqueeze(0)\n",
" entropy += dist.entropy().mean()\n",
"\n",
" log_probs.append(log_prob)\n",
" values.append(value)\n",
" rewards.append(torch.tensor([reward], dtype=torch.float, device=device))\n",
" masks.append(torch.tensor([1-done], dtype=torch.float, device=device))\n",
"\n",
" state = next_state\n",
"\n",
" if done:\n",
" print('Iteration: {}, Score: {}'.format(iter, i))\n",
" break\n",
"\n",
"\n",
" next_state = torch.FloatTensor(next_state).to(device)\n",
" next_value = critic(next_state)\n",
" returns = discounted_rewards(next_value, rewards, masks)\n",
"\n",
" log_probs = torch.cat(log_probs)\n",
" returns = torch.cat(returns).detach()\n",
" values = torch.cat(values)\n",
"\n",
" advantage = returns - values\n",
"\n",
" actor_loss = -(log_probs * advantage.detach()).mean()\n",
" critic_loss = advantage.pow(2).mean()\n",
"\n",
" optimizerA.zero_grad()\n",
" optimizerC.zero_grad()\n",
" actor_loss.backward()\n",
" critic_loss.backward()\n",
" optimizerA.step()\n",
" optimizerC.step()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"اکنون حلقه اصلی آموزش را اجرا خواهیم کرد. ما از فرآیند آموزش دستی شبکه با محاسبه توابع زیان مناسب و به‌روزرسانی پارامترهای شبکه استفاده خواهیم کرد:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\n",
"actor = Actor(state_size, action_size).to(device)\n",
"critic = Critic(state_size, action_size).to(device)\n",
"run_episode(actor, critic, n_iters=100)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"env.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## نکات کلیدی\n",
"\n",
"ما در این نمایش دو الگوریتم یادگیری تقویتی را مشاهده کردیم: گرادیان سیاست ساده و بازیگر-منتقد پیچیده‌تر. می‌توانید ببینید که این الگوریتم‌ها با مفاهیم انتزاعی حالت، عمل و پاداش کار می‌کنند - بنابراین می‌توان آن‌ها را در محیط‌های بسیار متفاوت اعمال کرد.\n",
"\n",
"یادگیری تقویتی به ما این امکان را می‌دهد که بهترین استراتژی برای حل مسئله را تنها با نگاه کردن به پاداش نهایی یاد بگیریم. این واقعیت که نیازی به مجموعه داده‌های برچسب‌گذاری‌شده نداریم، به ما اجازه می‌دهد شبیه‌سازی‌ها را بارها تکرار کنیم تا مدل‌های خود را بهینه کنیم. با این حال، هنوز چالش‌های زیادی در یادگیری تقویتی وجود دارد که اگر تصمیم بگیرید بیشتر روی این حوزه جذاب از هوش مصنوعی تمرکز کنید، ممکن است آن‌ها را بیاموزید.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**سلب مسئولیت**: \nاین سند با استفاده از سرویس ترجمه هوش مصنوعی [Co-op Translator](https://github.com/Azure/co-op-translator) ترجمه شده است. در حالی که ما تلاش می‌کنیم دقت را حفظ کنیم، لطفاً توجه داشته باشید که ترجمه‌های خودکار ممکن است شامل خطاها یا نادرستی‌ها باشند. سند اصلی به زبان اصلی آن باید به عنوان منبع معتبر در نظر گرفته شود. برای اطلاعات حساس، توصیه می‌شود از ترجمه حرفه‌ای انسانی استفاده کنید. ما مسئولیتی در قبال سوءتفاهم‌ها یا تفسیرهای نادرست ناشی از استفاده از این ترجمه نداریم.\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.4 64-bit",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
}
},
"coopTranslator": {
"original_hash": "04f8d9978cd11281d81dd037cbf6ce20",
"translation_date": "2025-08-31T15:45:54+00:00",
"source_file": "lessons/6-Other/22-DeepRL/CartPole-RL-PyTorch.ipynb",
"language_code": "fa"
}
},
"nbformat": 4,
"nbformat_minor": 2
}