动手实践
现在我们已经学习了 Q 学习算法,让我们从头开始实现它,并在两种环境中训练我们的 Q 学习智能体。
- Frozen-Lake-v1(非滑冰和滑冰版本) ☃️:我们的智能体需要通过仅在冰冻方块 (F) 上行走,并避开洞 (H) 来**从起始状态 (S) 到达目标状态 (G)**。
- 自动驾驶出租车 🚖 需要**学习在城市中导航**,**将乘客从 A 点运送到 B 点**。
得益于一个排行榜,您可以将自己的结果与其他同学进行比较,并交流最佳实践以提高智能体的得分。谁将在单元 2 的挑战中获胜?
为了验证此动手实践以用于认证流程,您需要将训练好的出租车模型推送到 Hub,并**获得大于或等于 4.5 的结果**。
要查找您的结果,请访问排行榜并查找您的模型,**结果 = 平均奖励 - 奖励标准差**
有关认证流程的更多信息,请查看此部分 👉 https://huggingface.co/deep-rl-course/en/unit0/introduction#certification-process
您可以在这里查看您的进度 👉 https://huggingface.co/spaces/ThomasSimonini/Check-my-progress-Deep-RL-Course
**要开始动手实践,请点击“在 Colab 中打开”按钮** 👇
我们强烈**建议学生使用 Google Colab 进行动手练习**,而不是在个人电脑上运行它们。
使用 Google Colab,**您可以专注于学习和实验,而无需担心设置环境的技术方面**。
单元 2:使用 FrozenLake-v1 ⛄ 和 Taxi-v3 🚕 的 Q 学习
在本笔记本中,**您将从头开始编写第一个强化学习智能体**,使用 Q 学习玩 FrozenLake ❄️,将其与社区共享,并尝试不同的配置。
⬇️ 以下是您**在短短几分钟内就能实现**的示例。⬇️
🎮 环境:
📚 RL-Library:
- Python 和 NumPy
- Gymnasium
我们一直在努力改进我们的教程,因此,**如果您在本笔记本中发现任何问题**,请在 GitHub 仓库中打开问题。
本笔记本的目标 🏆
在本笔记本的最后,您将
- 能够使用**Gymnasium**,环境库。
- 能够从头开始编写 Q 学习智能体。
- 能够**将训练好的智能体和代码推送到 Hub**,并附带漂亮的视频回放和评估分数 🔥。
本笔记本来自深度强化学习课程
在本免费课程中,您将
- 📖 学习深度强化学习的**理论和实践**。
- 🧑💻 学习**使用著名的深度 RL 库**,如 Stable Baselines3、RL Baselines3 Zoo、CleanRL 和 Sample Factory 2.0。
- 🤖 在**独特的环境中训练智能体**
还有更多检查📚教学大纲👉 https://simoninithomas.github.io/deep-rl-course
别忘了报名课程(我们收集您的电子邮件,以便在发布每个单元时发送链接,并向您提供有关挑战和更新的信息)。
保持联系的最佳方式是加入我们的 Discord 服务器,与社区和我们交流👉🏻 https://discord.gg/ydHrjt3WP5
先决条件🏗️
在深入研究笔记本电脑之前,您需要
🔲 📚 学习阅读第二单元的 Q 学习 🤗
Q 学习的简要回顾
Q 学习是 RL 算法,它
训练Q 函数,一个动作值函数,它由Q 表在内部存储器中编码,其中包含所有状态-动作对值。
给定一个状态和动作,我们的 Q 函数将在 Q 表中搜索相应的值。
训练完成后,我们得到了一个最优 Q 函数,即最优 Q 表。
而且,如果我们拥有最优 Q 函数,那么我们也拥有最优策略,因为我们知道对每个状态,应该采取的最佳动作。
但是,在开始时,我们的Q 表毫无用处,因为它为每个状态-动作对提供了任意值(大多数情况下我们将 Q 表初始化为 0 值)。但是,随着我们探索环境并更新 Q 表,它将为我们提供越来越好的近似值
这是 Q 学习的伪代码
让我们编写第一个强化学习算法🚀
为了验证此动手实践以用于认证流程,您需要将训练好的出租车模型推送到 Hub,并**获得大于或等于 4.5 的结果**。
要查找您的结果,请访问排行榜并查找您的模型,**结果 = 平均奖励 - 奖励标准差**
有关认证流程的更多信息,请查看此部分 👉 https://huggingface.co/deep-rl-course/en/unit0/introduction#certification-process
安装依赖项并创建虚拟显示🔽
在笔记本电脑中,我们需要生成一个回放视频。为此,在使用 Colab 时,我们需要有一个虚拟屏幕来渲染环境(从而录制帧)。
因此,以下单元格将安装库,并创建和运行一个虚拟屏幕🖥
我们将安装多个库
gymnasium
:包含 FrozenLake-v1 ⛄ 和 Taxi-v3 🚕 环境。pygame
:用于 FrozenLake-v1 和 Taxi-v3 的 UI。numpy
:用于处理我们的 Q 表。
Hugging Face Hub 🤗 作为一个中心位置,任何人都可以在其中共享和探索模型和数据集。它具有版本控制、指标、可视化和其他功能,使您可以轻松地与其他人协作。
您可以在此处查看所有可用的深度强化学习模型(如果它们使用 Q 学习)👉 https://huggingface.co/models?other=q-learning
pip install -r https://raw.githubusercontent.com/huggingface/deep-rl-class/main/notebooks/unit2/requirements-unit2.txt
sudo apt-get update sudo apt-get install -y python3-opengl apt install ffmpeg xvfb pip3 install pyvirtualdisplay
为了确保使用新安装的库,有时需要重新启动笔记本电脑运行时。下一个单元格将强制运行时崩溃,因此您需要重新连接并从这里开始运行代码。借助此技巧,我们将能够运行虚拟屏幕。
import os
os.kill(os.getpid(), 9)
# Virtual display
from pyvirtualdisplay import Display
virtual_display = Display(visible=0, size=(1400, 900))
virtual_display.start()
导入包📦
除了安装的库之外,我们还使用
random
:生成随机数(这将对 ε-贪婪策略有用)。imageio
:生成回放视频。
import numpy as np
import gymnasium as gym
import random
import imageio
import os
import tqdm
import pickle5 as pickle
from tqdm.notebook import tqdm
我们现在准备编写我们的 Q 学习算法 🔥
第 1 部分:冰湖⛄(非滑冰版本)
创建和理解冰湖环境⛄ (( https://gymnasium.org.cn/environments/toy_text/frozen_lake/ )
💡 开始使用环境时,养成查看其文档的良好习惯
👉 https://gymnasium.org.cn/environments/toy_text/frozen_lake/
我们将训练我们的 Q 学习代理仅通过在冰冻瓷砖(F)上行走来从起始状态(S)导航到目标状态(G),并避开洞(H)。
我们可以有两种环境尺寸
map_name="4x4"
:一个 4x4 网格版本map_name="8x8"
:一个 8x8 网格版本
环境有两种模式
is_slippery=False
:由于冰湖的非滑性(确定性),代理始终按预期方向移动。is_slippery=True
:由于冰湖的滑性(随机性),代理可能不会始终按预期方向移动。
现在让我们先用 4x4 地图和非滑性来简化。我们添加了一个名为render_mode
的参数,该参数指定环境的显示方式。在我们的例子中,因为我们希望在最后记录环境的视频,所以我们需要将 render_mode 设置为 rgb_array。
如文档中所述,“rgb_array”:返回一个表示环境当前状态的单个帧。帧是一个形状为 (x, y, 3) 的 np.ndarray,表示 x x y 像素图像的 RGB 值。
# Create the FrozenLake-v1 environment using 4x4 map and non-slippery version and render_mode="rgb_array"
env = gym.make() # TODO use the correct parameters
解决方案
env = gym.make("FrozenLake-v1", map_name="4x4", is_slippery=False, render_mode="rgb_array")
您可以像这样创建自己的自定义网格
desc=["SFFF", "FHFH", "FFFH", "HFFG"]
gym.make('FrozenLake-v1', desc=desc, is_slippery=True)
但我们现在将使用默认环境。
让我们看看环境是什么样子的:
# We create our environment with gym.make("<name_of_the_environment>")- `is_slippery=False`: The agent always moves in the intended direction due to the non-slippery nature of the frozen lake (deterministic).
print("_____OBSERVATION SPACE_____ \n")
print("Observation Space", env.observation_space)
print("Sample observation", env.observation_space.sample()) # Get a random observation
我们看到 Observation Space Shape Discrete(16)
,观察结果是一个整数,表示**代理的当前位置,计算方式为 current_row * ncols + current_col(其中行和列都从 0 开始)**。
例如,在 4x4 地图中,目标位置的计算方式如下:3 * 4 + 3 = 15。可能的观察结果数量取决于地图的大小。**例如,4x4 地图有 16 种可能的观察结果。**
例如,这就是 state = 0 的样子
print("\n _____ACTION SPACE_____ \n")
print("Action Space Shape", env.action_space.n)
print("Action Space Sample", env.action_space.sample()) # Take a random action
动作空间(代理可以采取的可能动作集)是离散的,有 4 种可用的动作 🎮
- 0:向左移动
- 1:向下移动
- 2:向右移动
- 3:向上移动
奖励函数 💰
- 到达目标:+1
- 到达洞穴:0
- 到达冰面:0
创建并初始化 Q 表 🗄️
(👀 伪代码的步骤 1)
现在该初始化我们的 Q 表了!要了解需要使用多少行(状态)和列(动作),我们需要知道动作空间和观察空间。我们之前已经知道它们的值,但我们希望通过编程方式获取它们,以便我们的算法可以泛化到不同的环境中。Gym 提供了一种方法来实现这一点:env.action_space.n
和 env.observation_space.n
state_space =
print("There are ", state_space, " possible states")
action_space =
print("There are ", action_space, " possible actions")
# Let's create our Qtable of size (state_space, action_space) and initialized each values at 0 using np.zeros. np.zeros needs a tuple (a,b)
def initialize_q_table(state_space, action_space):
Qtable =
return Qtable
Qtable_frozenlake = initialize_q_table(state_space, action_space)
解决方案
state_space = env.observation_space.n
print("There are ", state_space, " possible states")
action_space = env.action_space.n
print("There are ", action_space, " possible actions")
# Let's create our Qtable of size (state_space, action_space) and initialized each values at 0 using np.zeros
def initialize_q_table(state_space, action_space):
Qtable = np.zeros((state_space, action_space))
return Qtable
Qtable_frozenlake = initialize_q_table(state_space, action_space)
定义贪婪策略 🤖
记住,我们有两个策略,因为 Q 学习是一种**非策略**算法。这意味着我们**使用不同的策略来行动和更新价值函数**。
- ε-贪婪策略(行动策略)
- 贪婪策略(更新策略)
贪婪策略也将是我们 Q 学习代理完成训练后将使用的最终策略。贪婪策略用于使用 Q 表来选择一个动作。
def greedy_policy(Qtable, state):
# Exploitation: take the action with the highest state, action value
action =
return action
解决方案
def greedy_policy(Qtable, state):
# Exploitation: take the action with the highest state, action value
action = np.argmax(Qtable[state][:])
return action
定义ε-贪婪策略 🤖
ε-贪婪是训练策略,用于处理探索/利用权衡。
ε-贪婪的想法
以概率 1 - ɛ:我们进行利用(即我们的代理选择具有最高状态-动作对值的动作)。
以概率 ɛ:我们进行探索(尝试一个随机动作)。
随着训练的进行,我们逐渐**降低ε值,因为我们将需要越来越少的探索和越来越多的利用**。
def epsilon_greedy_policy(Qtable, state, epsilon):
# Randomly generate a number between 0 and 1
random_num =
# if random_num > greater than epsilon --> exploitation
if random_num > epsilon:
# Take the action with the highest value given a state
# np.argmax can be useful here
action =
# else --> exploration
else:
action = # Take a random action
return action
解决方案
def epsilon_greedy_policy(Qtable, state, epsilon):
# Randomly generate a number between 0 and 1
random_num = random.uniform(0, 1)
# if random_num > greater than epsilon --> exploitation
if random_num > epsilon:
# Take the action with the highest value given a state
# np.argmax can be useful here
action = greedy_policy(Qtable, state)
# else --> exploration
else:
action = env.action_space.sample()
return action
定义超参数 ⚙️
与探索相关的超参数是最重要的超参数之一。
- 我们需要确保我们的代理**充分探索状态空间**以学习一个好的值近似值。为此,我们需要逐步衰减ε。
- 如果你太快地降低ε(衰减速率太高),**你的代理可能会卡住**,因为你的代理没有充分探索状态空间,因此无法解决问题。
# Training parameters
n_training_episodes = 10000 # Total training episodes
learning_rate = 0.7 # Learning rate
# Evaluation parameters
n_eval_episodes = 100 # Total number of test episodes
# Environment parameters
env_id = "FrozenLake-v1" # Name of the environment
max_steps = 99 # Max steps per episode
gamma = 0.95 # Discounting rate
eval_seed = [] # The evaluation seed of the environment
# Exploration parameters
max_epsilon = 1.0 # Exploration probability at start
min_epsilon = 0.05 # Minimum exploration probability
decay_rate = 0.0005 # Exponential decay rate for exploration prob
创建训练循环方法
训练循环是这样的
For episode in the total of training episodes:
Reduce epsilon (since we need less and less exploration)
Reset the environment
For step in max timesteps:
Choose the action At using epsilon greedy policy
Take the action (a) and observe the outcome state(s') and reward (r)
Update the Q-value Q(s,a) using Bellman equation Q(s,a) + lr [R(s,a) + gamma * max Q(s',a') - Q(s,a)]
If done, finish the episode
Our next state is the new state
def train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable):
for episode in tqdm(range(n_training_episodes)):
# Reduce epsilon (because we need less and less exploration)
epsilon = min_epsilon + (max_epsilon - min_epsilon)*np.exp(-decay_rate*episode)
# Reset the environment
state, info = env.reset()
step = 0
terminated = False
truncated = False
# repeat
for step in range(max_steps):
# Choose the action At using epsilon greedy policy
action =
# Take action At and observe Rt+1 and St+1
# Take the action (a) and observe the outcome state(s') and reward (r)
new_state, reward, terminated, truncated, info =
# Update Q(s,a):= Q(s,a) + lr [R(s,a) + gamma * max Q(s',a') - Q(s,a)]
Qtable[state][action] =
# If terminated or truncated finish the episode
if terminated or truncated:
break
# Our next state is the new state
state = new_state
return Qtable
解决方案
def train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable):
for episode in tqdm(range(n_training_episodes)):
# Reduce epsilon (because we need less and less exploration)
epsilon = min_epsilon + (max_epsilon - min_epsilon) * np.exp(-decay_rate * episode)
# Reset the environment
state, info = env.reset()
step = 0
terminated = False
truncated = False
# repeat
for step in range(max_steps):
# Choose the action At using epsilon greedy policy
action = epsilon_greedy_policy(Qtable, state, epsilon)
# Take action At and observe Rt+1 and St+1
# Take the action (a) and observe the outcome state(s') and reward (r)
new_state, reward, terminated, truncated, info = env.step(action)
# Update Q(s,a):= Q(s,a) + lr [R(s,a) + gamma * max Q(s',a') - Q(s,a)]
Qtable[state][action] = Qtable[state][action] + learning_rate * (
reward + gamma * np.max(Qtable[new_state]) - Qtable[state][action]
)
# If terminated or truncated finish the episode
if terminated or truncated:
break
# Our next state is the new state
state = new_state
return Qtable
训练 Q 学习代理 🏃
Qtable_frozenlake = train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable_frozenlake)
让我们看看我们的 Q 学习表现在是什么样子的 👀
Qtable_frozenlake
评估方法 📝
- 我们定义了要用于测试 Q 学习代理的评估方法。
def evaluate_agent(env, max_steps, n_eval_episodes, Q, seed):
"""
Evaluate the agent for ``n_eval_episodes`` episodes and returns average reward and std of reward.
:param env: The evaluation environment
:param n_eval_episodes: Number of episode to evaluate the agent
:param Q: The Q-table
:param seed: The evaluation seed array (for taxi-v3)
"""
episode_rewards = []
for episode in tqdm(range(n_eval_episodes)):
if seed:
state, info = env.reset(seed=seed[episode])
else:
state, info = env.reset()
step = 0
truncated = False
terminated = False
total_rewards_ep = 0
for step in range(max_steps):
# Take the action (index) that have the maximum expected future reward given that state
action = greedy_policy(Q, state)
new_state, reward, terminated, truncated, info = env.step(action)
total_rewards_ep += reward
if terminated or truncated:
break
state = new_state
episode_rewards.append(total_rewards_ep)
mean_reward = np.mean(episode_rewards)
std_reward = np.std(episode_rewards)
return mean_reward, std_reward
评估我们的 Q 学习代理 📈
- 通常,你的平均奖励应该是 1.0
- **环境比较容易**,因为状态空间很小(16)。你可以尝试做的是用滑滑的版本替换它,它引入了随机性,使环境更复杂。
# Evaluate our Agent
mean_reward, std_reward = evaluate_agent(env, max_steps, n_eval_episodes, Qtable_frozenlake, eval_seed)
print(f"Mean_reward={mean_reward:.2f} +/- {std_reward:.2f}")