本文是使用 Tensorflow 的深度强化学习课程的一部分。
上一次,我们了解了 Q-Learning:一种生成 Q-table 的算法,代理使用它来找到在给定状态下采取的最佳动作。
但正如我们将看到的,在大状态空间环境中,生成和更新 Q 表可能变得无效。
本文是关于深度强化学习系列博文的第三部分。有关更多信息和更多资源,请查看课程大纲。
检查这里的教学大纲。
今天,我们将创建一个深度 Q 神经网络。我们将实现一个神经网络,而不是使用 Q 表,它采用一个状态并根据该状态为每个动作近似 Q 值。
多亏了这个模型,我们将能够创建一个学习玩Doom的代理!
在本文中,您将了解到:
- 什么是深度 Q 学习 (DQL)?
- 与 DQL 一起使用的最佳策略是什么?
- 如何处理时间限制问题
- 为什么我们使用经验回放
- DQL 背后的数学原理是什么
- 如何在 Tensorflow 中实现它
将“深度”添加到 Q-Learning
在上一篇文章中,我们通过 Q-learning 算法创建了一个玩 Frozen Lake 的代理。
我们实现了 Q-learning 函数来创建和更新 Q-table。把它想象成一个“备忘单”,帮助我们在给定当前状态的情况下找到一个动作的最大预期未来回报。这是一个很好的策略——但是,这是不可扩展的。
想象一下我们今天要做什么。我们将创建一个学习玩 Doom 的代理。Doom 是一个拥有巨大状态空间(数百万个不同状态)的大环境。为该环境创建和更新 Q 表根本没有效率。
在这种情况下,最好的想法是创建一个神经网络,该网络将在给定状态下近似每个动作的不同 Q 值。
深度 Q 学习如何工作?
这将是我们深度 Q 学习的架构:
这看起来很复杂,但我将逐步解释架构。
我们的 Deep Q 神经网络将一叠四帧作为输入。它们通过它的网络,并为给定状态下可能的每个动作输出一个 Q 值向量。我们需要取这个向量的最大 Q 值来找到我们的最佳动作。
一开始,代理的表现非常糟糕。但随着时间的推移,它开始将框架(状态)与要做的最佳动作联系起来。
预处理部分
预处理是一个重要的步骤。我们希望降低状态的复杂性,以减少训练所需的计算时间。
首先,我们可以对每个状态进行灰度化。颜色不会添加重要信息(在我们的例子中,我们只需要找到敌人并杀死他,我们不需要颜色来找到他)。这是一个重要的节省,因为我们将三个颜色通道 (RGB) 减少到 1(灰度)。
然后,我们裁剪框架。在我们的示例中,看到屋顶并不是很有用。
然后我们减小帧的大小,我们将四个子帧堆叠在一起。
时间限制问题
Arthur Juliani在他的文章中给出了关于这个主题的精彩解释。他有一个聪明的想法:使用LSTM 神经网络来处理问题。
但是,我认为初学者最好使用堆叠框架。
您可以问的第一个问题是为什么我们将帧堆叠在一起?
我们将帧堆叠在一起是因为它可以帮助我们处理时间限制问题。
让我们举个例子,在 Pong 游戏中。当你看到这个框架时:
你能告诉我球要去哪里吗?
不,因为一帧还不够有运动感!
但是如果我再添加三个帧呢?在这里你可以看到球向右移动。
我们的 Doom 代理也是如此。如果我们一次只给他一帧,它就没有运动的概念。如果它无法确定物体移动的位置和速度,它又如何做出正确的决定?
使用卷积网络
帧由三个卷积层处理。这些图层允许您利用图像中的空间关系。而且,由于帧堆叠在一起,您可以利用这些帧的一些空间属性。
如果你不熟悉卷积,请仔细阅读本优秀的直观文章由亚当Geitgey。
每个卷积层都会使用 ELU 作为激活函数。ELU 已被证明是一个很好的卷积层激活函数。
我们使用一个带有 ELU 激活函数的全连接层和一个输出层(一个带有线性激活函数的全连接层),为每个动作产生 Q 值估计。
经验回放:更有效地利用观察到的经验
经验回放将帮助我们处理两件事:
- 避免忘记以前的经历。
- 减少体验之间的相关性。
我将解释这两个概念。
这部分和插图的灵感来自Udacity的 Deep Learning Foundations Nanodegree 中 Deep Q Learning 一章中的精彩解释。
避免忘记以前的经历
我们有一个大问题:权重的可变性,因为动作和状态之间存在高度相关性。
记得在第一篇文章(强化学习简介)中,我们谈到了强化学习过程:
在每个时间步,我们都会收到一个元组(状态、动作、奖励、new_state)。我们从中学习(我们在神经网络中输入元组),然后抛出这个经验。
我们的问题是我们将与环境交互的连续样本提供给我们的神经网络。当它被新的体验覆盖时,它往往会忘记以前的体验。
例如,如果我们在第一级然后在第二级(完全不同),我们的代理可能会忘记在第一级如何表现。
通过学习如何在水位上玩,我们的智能体将忘记在第一级如何表现
因此,通过多次学习,可以更有效地利用以前的经验。
我们的解决方案:创建一个“重播缓冲区”。这会在与环境交互时存储经验元组,然后我们对一小批元组进行采样以提供给我们的神经网络。
将重放缓冲区视为一个文件夹,其中每张表都是一个体验元组。你通过与环境互动来喂养它。然后你拿一些随机表来馈送神经网络
这可以防止网络只了解它立即做了什么。
减少经验之间的相关性
我们还有另一个问题——我们知道每一个动作都会影响下一个状态。这会输出一系列高度相关的经验元组。
如果我们按顺序训练网络,我们的代理可能会受到这种相关性的影响。
通过从重放缓冲区随机采样,我们可以打破这种相关性。这可以防止动作值发生灾难性的振荡或发散。
举个例子会更容易理解。假设我们玩第一人称射击游戏,怪物可以出现在左侧或右侧。我们代理的目标是射击怪物。它有两把枪和两个动作:向左射击或向右射击。
该表表示 Q 值近似值
我们以有序的经验学习。假设我们知道如果我们射击一个怪物,下一个怪物来自同一个方向的概率是 70%。在我们的例子中,这是我们的经验元组之间的相关性。
让我们开始训练。我们的特工看到右边的怪物,并用正确的枪射击它。这是对的!
然后下一个怪物也来自右边(有70%的概率),代理会用右边的枪射击。再说一遍,这很好!
等等……
红枪是采取的行动
问题是,这种方法增加了在整个状态空间中使用正确枪支的价值。
我们可以看到怪物在左边和用右枪射击的Q值是正的(即使它不合理)
如果我们的智能体没有看到很多左边的例子(因为只有 30% 可能来自左边),我们的智能体只会通过选择右边来完成,而不管怪物来自哪里。这根本不合理。
即使怪物从左边来,我们的代理人也会用右边的枪射击
我们有两种并行的策略来处理这个问题。
首先,我们必须在与环境互动的同时停止学习。我们应该尝试不同的东西,随意玩一些来探索状态空间。我们可以将这些经验保存在重播缓冲区中。
然后,我们可以回忆这些经历并从中学习。之后,回去玩更新的值函数。
因此,我们将有一组更好的示例。我们将能够概括这些示例中的模式,以任何顺序回忆它们。
这有助于避免专注于状态空间的一个区域。这可以防止一遍又一遍地加强相同的动作。
这种方法可以看作是监督学习的一种形式。
我们将在以后的文章中看到我们也可以使用“优先体验重放”。这让我们可以更频繁地向神经网络呈现稀有或“重要”的元组。
我们的深度 Q 学习算法
先说一点数学:
请记住,我们使用 Bellman 方程更新给定状态和动作的Q 值:
在我们的例子中,我们想要更新我们的神经网络权重以减少错误。
误差(或 TD 误差)是通过我们的 Q_target(下一个状态的最大可能值)和 Q_value(我们当前对 Q 值的预测)之间的差异来计算的
该算法中发生了两个过程:
- 我们对执行操作的环境进行采样,并将观察到的体验元组存储在回放内存中。
- 随机选择一小批元组并使用梯度下降更新步骤从中学习。
让我们实现我们的深度 Q 神经网络
我们制作了一个视频,其中我们使用 Tensorflow 实现了一个深度 Q 学习代理,该代理学习玩 Atari Space Invaders ?️?。
现在我们知道它是如何工作的,我们将逐步实现我们的 Deep Q 神经网络。每个步骤和代码的每个部分都在下面链接的 Jupyter 笔记本中直接进行了解释。
您可以在深度强化学习课程存储库中访问它。
就这样!您刚刚创建了一个学习玩 Doom 的代理。惊人的!
不要忘记自己实现代码的每一部分。尝试修改我给你的代码真的很重要。尝试添加纪元、更改架构、添加固定 Q 值、更改学习率、使用更难的环境(例如 Health Gathering)……等等。玩得开心!
在下一篇文章中,我将讨论深度 Q 学习的最新改进:
- 固定 Q 值
- 优先体验回放
- 双DQN
- 决斗网络
但是下一次我们将通过训练一个玩 Doom 的代理来研究 Policy Gradients,我们将尝试通过收集生命值在敌对环境中生存。
如果你喜欢我的文章,所以其他人会在 Medium 上看到这篇文章。还有别忘了关注我哦!