一、说明
二、QL框架
强化学习 (RL) 是机器学习生态系统的一部分,其中代理通过与环境交互来学习,以获得实现目标的最佳策略。它与监督式机器学习算法完全不同,在监督式机器学习算法中,我们需要提取和处理这些数据。强化学习不需要数据。相反,它从环境和奖励系统中学习以做出更好的决策。
例如,在马里奥视频游戏中,如果角色采取随机动作(例如向左移动),则根据该动作,它可能会获得奖励。采取行动后,代理(马里奥)处于新状态,该过程重复进行,直到游戏角色到达关卡终点或死亡。
此情节将重复多次,直到马里奥学会通过最大化奖励来驾驭环境。
我们可以将强化学习分为五个简单的步骤:
- 代理在环境中处于状态零。
- 它将根据特定的策略采取行动。
- 它将根据该行动获得奖励或惩罚。
- 通过学习先前的动作并优化策略。
- 该过程将重复,直到找到最佳策略。
三、什么是 Q-Learning?
Q 学习是一种无模型、基于价值的离策略算法,它将根据代理的当前状态找到最佳的一系列操作。“Q”代表质量。质量表示该操作在最大化未来奖励方面有多大价值。
基于模型的算法使用转换和奖励函数来估计最优策略并创建模型。相比之下,无模型算法则通过没有转换和奖励函数的经验来学习其行为的后果。
基于价值的方法训练价值函数,以了解哪种状态更有价值并采取行动。另一方面,基于策略的方法直接训练策略,以了解在给定状态下应采取哪种行动。
在离策略中,算法评估并更新与采取行动所用策略不同的策略。相反,在策略算法评估并改进采取行动所用相同策略。
Q 学习中的关键术语
在我们了解 Q-learning 的工作原理之前,我们需要学习一些有用的术语来理解 Q-learning 的基础知识。
- 状态:代理在环境中的当前位置。
- 动作(a):代理在特定状态下采取的步骤。
- 奖励:对于每一个动作,代理都会得到奖励和惩罚。
- 情节:阶段的结束,此时代理无法采取新的行动。当代理已实现目标或失败时,就会发生这种情况。
- Q(S t+1 , a):在特定状态下执行操作的预期最佳 Q 值。
- Q(S t , A t ):是 Q(S t+1 , a) 的当前估计。
- Q-Table:代理维护状态和动作集的 Q 表。
- 时间差分(TD) :利用当前状态和动作以及之前的状态和动作 来估计Q(S t+1 , a)的预期值。
四、Q-Learning 如何工作?
我们将以结冰的湖泊为例,详细了解 Q 学习的工作原理。在这个环境中,代理必须从起点穿过结冰的湖泊到达目标,而不能掉进洞里。最好的策略是走最短的路径到达目标。
4.1 Q-表
代理将使用 Q 表根据环境中每个状态的预期奖励采取最佳行动。简而言之,Q 表是一组动作和状态的数据结构,我们使用 Q 学习算法来更新表中的值。
4.2 Q 函数
Q 函数使用贝尔曼方程,以状态(s)和动作(a)作为输入。该方程简化了状态值和状态动作值的计算。
五、Q 学习算法
5.1 初始化 Q 表
我们首先初始化 Q 表。我们将根据动作数量构建列,根据状态数量构建行。
在我们的示例中,角色可以上下左右移动。我们有四种可能的动作和四种状态(开始、空闲、错误路径和结束)。您还可以考虑掉入洞中的错误路径。我们将使用 0 值初始化 Q-Table。
5.2 选择一个动作
第二步很简单。一开始,代理会选择采取随机动作(向下或向右),第二次运行时,它将使用更新的 Q 表来选择动作。
5.3 执行操作
选择一个动作并执行该动作将重复多次,直到训练循环停止。使用 Q 表选择第一个动作和状态。在我们的例子中,Q 表的所有值都是零。
然后,代理将向下移动并使用贝尔曼方程更新 Q 表。每次移动时,我们都会更新 Q 表中的值,并使用它来确定最佳行动方案。
最初,代理处于探索模式,并选择随机动作来探索环境。Epsilon 贪婪策略是一种平衡探索和利用的简单方法。epsilon 代表在探索机会较小的情况下选择探索和利用的概率。
开始时,epsilon 率较高,这意味着代理处于探索模式。在探索环境时,epsilon 会降低,代理会开始利用环境。在探索过程中,随着每次迭代,代理对估计 Q 值的信心会越来越强
在冰冻湖泊示例中,代理不知道环境,因此需要采取随机动作(向下移动)才能启动。如上图所示,Q 表使用贝尔曼方程进行更新。
5.4 衡量回报
采取行动后,我们将衡量结果和回报。
- 达成目标的奖励为+1
- 走错路(掉进洞里)的奖励是0
- 在冰冻湖上空闲或移动的奖励也是 0。
5.5 更新 Q 表
我们将使用方程更新函数 Q(S t , A t )。它使用前一集的估计 Q 值、学习率和时间差异误差。时间差异误差是使用即时奖励、折扣后的最大预期未来奖励和前一个估计 Q 值计算的。
该过程重复多次,直到 Q 表更新并且 Q 值函数最大化。
一开始,代理会探索环境以更新 Q 表。当 Q 表准备就绪时,代理将开始利用并开始做出更好的决策。
在湖面结冰的情况下,代理将学习采取最短路径到达目标并避免跳入洞中。
六、Q-Learning Python 教程
我们将使用 Gym 环境、Pygame 和 Numpy 从头开始构建 Q 学习模型。Python 教程是Thomas Simonini 的Notebook的修改版本。它包括初始化环境和 Q 表、定义贪婪策略、设置超参数、创建和运行训练循环和评估以及可视化结果。
如果您在创建和运行训练循环时遇到问题,您可以使用输出 检查代码源。
6.1 配置
设置虚拟显示
我们首先安装所有依赖项以生成回放视频(Gif)。我们需要一个虚拟屏幕(pyvirtualdisplay)来渲染环境并记录帧。
注意:通过使用“%%capture”,我们抑制了 Jupyter 单元的输出。
%%capture
!pip install pyglet==1.5.1
!apt install python-opengl
!apt install ffmpeg
!apt install xvfb
!pip3 install pyvirtualdisplay# Virtual display
from pyvirtualdisplay import Displayvirtual_display = Display(visible=0, size=(1400, 900))
virtual_display.start()
我们现在将安装依赖项来帮助我们创建、运行和评估训练循环。
- gym:用于初始化FrozenLake-v1环境。
- pygame:用于FrozenLake-v1 UI。
- numPy:用于创建和处理 Q 表。
%%capture
!pip install gym==0.24
!pip install pygame
!pip install numpy!pip install imageio imageio_ffmpeg
我们现在将导入所需的库。
- Imageio 用于创建动画。
- tqdm 用于进度条。
import numpy as np
import gym
import random
import imageio
from tqdm.notebook import trange
我们将使用冰冻湖健身库创建一个防滑的 4x4 环境。
- 有两个网格版本:“4x4”和“8x8”。
- 如果“is_slippery=True”,则由于冰冻湖的滑溜性质,代理可能不会朝预定的方向移动。
初始化环境之后,我们来进行环境分析。
env = gym.make("FrozenLake-v1",map_name="4x4",is_slippery=False)print("Observation Space", env.observation_space)
print("Sample observation", env.observation_space.sample()) # display a random observation
Observation Space Discrete(16)
Sample observation 15
行动空间:
- 0:向左移动
- 1:向下移动
- 2:向右移动
- 3:向上移动
奖励函数:
- 达成目标:+1
- 掉坑:0
- 停留在冰冻湖面:0
print("Action Space Shape", env.action_space.n)
print("Action Space Sample", env.action_space.sample())
Action Space Shape 4
Action Space Sample 1
6.2 创建并初始化Q表
Q 表的列表示动作,行表示状态。我们可以使用 OpenAI Gym 来查找动作空间和状态空间。然后我们将使用此信息来创建 Q 表。
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")
There are 16 possible states There are 4 possible actions
为了初始化 Q 表,我们将创建一个包含 state_space 和 action 空间的 Numpy 数组。我们将创建一个 16 X 4 数组。
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)
6.3 贪婪策略
在上一节中,我们了解了处理探索和利用权衡的 epsilon 贪婪策略。以 1 - ε 的概率,我们进行利用;以 ε 的概率,我们进行探索。
在 epsilon_greedy_policy 中我们将:
- 生成0到1之间的随机数。
- 如果随机数大于 epsilon,我们将进行利用。这意味着代理将在给定状态下采取具有最高值的操作。
- 否则,我们将进行探索(采取随机行动)。
def epsilon_greedy_policy(Qtable, state, epsilon):random_int = random.uniform(0,1)if random_int > epsilon:action = np.argmax(Qtable[state])else:action = env.action_space.sample()return action
我们现在知道 Q 学习是一种离策略算法,这意味着采取行动和更新函数的策略是不同的。
在这个例子中,Epsilon Greedy 策略是代理策略,而 Greedy 策略是更新策略。
贪婪策略也将是训练代理时的最终策略。它用于从 Q 表中选择最高的状态和动作值。
def greedy_policy(Qtable, state):action = np.argmax(Qtable[state])return action
这些超参数用于训练循环,对其进行微调将会给你带来更好的结果。
代理需要探索足够的状态空间才能学习良好的值近似值;我们需要逐步衰减 epsilon。如果衰减率很高,代理可能会卡住,因为它没有探索足够的状态空间。
- 共有 10,000 个训练片段和 100 个评估片段。
- 学习率为0.7。
- 我们使用“FrozenLake-v1”作为环境,每集最多有 99 步。
- 伽马(折扣率)为 0.95。
- eval_seed:环境的评估种子。
- 开始时探索的epsilon 概率为 1.0,最小概率为 0.05。
- 埃普西隆概率的指数衰减率为0.0005。
# Training parameters
n_training_episodes = 10000
learning_rate = 0.7 # Evaluation parameters
n_eval_episodes = 100 # Environment parameters
env_id = "FrozenLake-v1"
max_steps = 99
gamma = 0.95
eval_seed = [] # Exploration parameters
max_epsilon = 1.0
min_epsilon = 0.05
decay_rate = 0.0005
6.4 模型训练
在训练循环中,我们将:
- 为训练片段创建一个循环。
- 我们首先要减少 epsilon。因为在每一集中,我们需要的探索越来越少,而利用越来越多。
- 重置环境。
- 创建一个嵌套循环以获取最大步数。
- 使用 epsilon 贪婪策略选择动作。
- 采取行动(A t)并观察预期奖励(R t+1)和状态(S t+1)。
- 采取行动(a)并观察结果状态(s')和奖励(r)。
- 使用公式更新 Q 函数。
- 如果“done = True”,则完成情节并打破循环。
- 最后,将当前状态更改为新状态。
- 完成所有训练阶段后,该函数将返回更新的 Q 表。
def train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable):for episode in trange(n_training_episodes):epsilon = min_epsilon + (max_epsilon - min_epsilon)*np.exp(-decay_rate*episode)# Reset the environmentstate = env.reset()step = 0done = False# repeatfor step in range(max_steps):action = epsilon_greedy_policy(Qtable, state, epsilon)new_state, reward, done, info = env.step(action)Qtable[state][action] = Qtable[state][action] + learning_rate * (reward + gamma * np.max(Qtable[new_state]) - Qtable[state][action])# If done, finish the episodeif done:break# Our state is the new statestate = new_statereturn Qtable
我们花了3秒完成10,000次训练。
Qtable_frozenlake = train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable_frozenlake)
我们可以看到,训练过的 Q 表具有值,代理现在将使用这些值来导航环境并实现目标。
Qtable_frozenlake
array([[0.73509189, 0.77378094, 0.77378094, 0.73509189],[0.73509189, 0. , 0.81450625, 0.77378094],[0.77378094, 0.857375 , 0.77378094, 0.81450625],[0.81450625, 0. , 0.77378094, 0.77378094],[0.77378094, 0.81450625, 0. , 0.73509189],[0. , 0. , 0. , 0. ],[0. , 0.9025 , 0. , 0.81450625],[0. , 0. , 0. , 0. ],[0.81450625, 0. , 0.857375 , 0.77378094],[0.81450625, 0.9025 , 0.9025 , 0. ],[0.857375 , 0.95 , 0. , 0.857375 ],[0. , 0. , 0. , 0. ],[0. , 0. , 0. , 0. ],[0. , 0.9025 , 0.95 , 0.857375 ],[0.9025 , 0.95 , 1. , 0.9025 ],[0. , 0. , 0. , 0. ]])
Evaluation_agent 运行“n_eval_episodes”事件并返回奖励的平均值和标准差。
- 在循环中,我们首先检查是否存在评估种子。如果没有,那么我们将重置没有种子的环境。
- 嵌套循环将运行至max_steps。
- 代理将使用 Q-Table 采取在给定状态下具有最大预期未来奖励的行动。
- 计算獎勵。
- 改变状态。
- 如果完成(代理掉入洞中或者目标已实现),则打破循环。
- 附加结果。
- 最后,我们将使用这些结果来计算平均值和标准差。
def evaluate_agent(env, max_steps, n_eval_episodes, Q, seed):episode_rewards = []for episode in range(n_eval_episodes):if seed:state = env.reset(seed=seed[episode])else:state = env.reset()step = 0done = Falsetotal_rewards_ep = 0for step in range(max_steps):# Take the action (index) that have the maximum rewardaction = np.argmax(Q[state][:])new_state, reward, done, info = env.step(action)total_rewards_ep += rewardif done:breakstate = new_stateepisode_rewards.append(total_rewards_ep)mean_reward = np.mean(episode_rewards)std_reward = np.std(episode_rewards)return mean_reward, std_reward
如你所见,我们获得了满分,标准差为零。这意味着我们的代理在所有 100 集中都达到了目标。
# 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}")
Mean_reward=1.00 +/- 0.00
6.5 可视化结果
到目前为止,我们一直在玩数字,为了进行演示,我们需要创建一个代理从一开始直到达到目标的动画 Gif。
- 我们将首先通过使用 0-500 的随机整数重置环境来创建状态。
- 使用 rdb_array 创建图像数组来渲染环境。
- 然后将“img”附加到“images”数组。
- 在循环中,我们将使用 Q-Table 采取步骤并为每个步骤渲染图像。
- 最后,我们将使用这个数组和 imageio 来创建每秒一帧的 Gif。
def record_video(env, Qtable, out_directory, fps=1):images = [] done = Falsestate = env.reset(seed=random.randint(0,500))img = env.render(mode='rgb_array')images.append(img)while not done:# Take the action (index) that have the maximum expected future reward given that stateaction = np.argmax(Qtable[state][:])state, reward, done, info = env.step(action) # We directly put next_state = state for recording logicimg = env.render(mode='rgb_array')images.append(img)imageio.mimsave(out_directory, [np.array(img) for i, img in enumerate(images)], fps=fps)
video_path="/content/replay.gif"
video_fps=1
record_video(env, Qtable_frozenlake, video_path, video_fps)from IPython.display import Image
Image('./replay.gif')