05.md 74.4 KB
Newer Older
W
wizardforcel 已提交
1 2
# Q 学习和深度 Q 网络

W
wizardforcel 已提交
3
在第 3 章,“马尔可夫决策过程”中,我们讨论了遵循马尔可夫性质的环境转移模型以及延迟的奖励和值函数的概念(或工具)。 好,在本章中,我们将研究马尔可夫决策过程,了解 Q 学习,以及一种称为深度 Q 网络的改进方法,用于在不同环境中进行泛化。
W
wizardforcel 已提交
4 5 6 7

我们将在本章介绍以下主题:

*   人工智能的有监督和无监督学习
W
wizardforcel 已提交
8
*   基于模型的学习和无模型学习
W
wizardforcel 已提交
9 10 11 12 13
*   Q 学习
*   深度 Q 网络
*   蒙特卡罗树搜索算法
*   SARSA 算法

W
wizardforcel 已提交
14
# 为什么是强化学习?
W
wizardforcel 已提交
15

W
wizardforcel 已提交
16
2014 年,Google 以高达 5 亿美元的价格收购了伦敦一家名为 DeepMind 的创业公司。 在新闻中,我们了解到他们创建了一个 AI 智能体来击败任何 Atari 游戏,但是 Google 付出如此高的价格收购它的主要原因是因为这一突破向**通用人工智能**靠近了一步。 通用人工智能被称为 AI 智能体。 它能够完成各种任务并像人类一样泛化。 当它超过这一点时,该点称为人工超级智能。 目前,AI 社区所做的工作就是我们所说的人工智能,即人工智能,其中 AI 智能体能够执行多个任务,但不能概括各种任务。
W
wizardforcel 已提交
17

W
wizardforcel 已提交
18
DeepMind 在研究期刊 **Nature** 上发表了他们的论文[《通过深度强化学习进行人类水平控制》](http://www.davidqiu.com:8888/research/nature14236.pdf),这表明,他们的深度强化学习算法可成功应用于 50 种不同的 Atari 游戏,并在其中 30 种游戏中达到高于人类水平的表现。 他们的 AI 智能体称为**深度 Q 学习器**。 在详细深入学习强化学习之前,让我们回顾一下强化学习的基础知识。
W
wizardforcel 已提交
19 20 21

有监督和无监督的学习是 AI 应用社区众所周知的。 **监督学习**处理包含输入特征和目标标签(连续或离散)的标记数据集,并创建将这些输入特征映射到目标标签的模型。 另一方面,**无监督学习**处理仅包含输入特征但不包含目标标签的未标记数据集,其目的是发现基础模式,以将数据分类到不同集群中,并分别定义其效用。 不同集群中特定类型的数据。

W
wizardforcel 已提交
22
因此,使用有监督和无监督的学习,我们可以创建数据分类器/回归器或数据生成器,通过一次学习就可以通过一批数据进行学习。 为了随着时间的推移增强学习,该批量需要合并越来越多的数据,从而导致有监督和无监督的学习变得缓慢且难以推广。 让我们考虑一种情况,您希望 AI 智能体为您玩特定的视频/虚拟游戏,但要注意的是,随着时间的流逝,该算法应该变得智能。
W
wizardforcel 已提交
23 24 25

那么,如何解决这个问题呢?

W
wizardforcel 已提交
26
假设我们拍摄了特定视频游戏中所有最佳玩家的视频,并以图像帧和目标标签的形式输入数据,作为可能采取的不同动作的集合。 由于我们具有输入特征和目标标签,因此形成了监督学习分类问题。 假设数据量巨大,并且我们可以使用具有最新 GPU 的高端机器,那么为该任务创建一个深度神经网络完全有意义。
W
wizardforcel 已提交
27 28 29

但是这里有什么收获呢?

W
wizardforcel 已提交
30
为了创建可解决此分类问题的深度神经网络,以使最终的 AI 智能体可以击败该游戏中任何级别的任何对手,我们的输入数据需要数千小时的视频数据分配到不同级别的游戏中, 不同的玩家,采用不同的方法赢得比赛,因此我们的神经网络可以以最佳方式概括映射。 获得更多数据的原因是为了避免欠拟合。 此外,过拟合中的大量数据也可能是一个问题,但是正则化是根据给定数据将模型推广到最佳状态的可能解决方案。 因此,我们看到,即使在获得了数千小时的专家播放器的视频数据(即非常高的数据量)之后,这种有监督的学习方法似乎也不是一个很好的解决方案。 这是因为,与其他应用的 AI 问题不同,此处的数据集是动态的而不是静态的。
W
wizardforcel 已提交
31

W
wizardforcel 已提交
32
此处的训练数据是连续的,新的框架在游戏世界中不断出现。 现在,问问自己我们人类如何学习这项任务,答案很简单,即我们通过与环境交互而不是看着其他人与环境交互来学习最好。 因此,AI 智能体可以尝试通过与环境交互来更好地学习,并通过一系列的反复试验来逐步发展其学习成果。
W
wizardforcel 已提交
33

W
wizardforcel 已提交
34
在现实世界和游戏世界中,环境通常是*随机*,其中可能发生许多事件。 由于所有事件都与某种发生概率相关联,因此可以对它们进行统计分析,但不能精确确定。 假设在给定的`e`环境中,我们只有三个动作来执行`a``b``c`,但是每个动作都有一些与之相关的某种不确定性,即它们的出现机会是随机的,并且它们中的任何一个都可以发生,但是每个结果都不确定。 对于监督分类问题,我们认为环境是确定性的,其中确定了与特定动作相关的结果,并且结果是精确的预测,即特定类别(目标标签)。 在继续讨论该主题之前,让我们看一下两种环境之间的区别:
W
wizardforcel 已提交
35

W
wizardforcel 已提交
36
*   **确定性环境**:由于没有不确定性,智能体的动作可以唯一地确定结果的环境。 例如,在国际象棋中,您将一块从一个正方形移到另一个正方形。 因此,确定结果,即结果平方。 没有随机性。
W
wizardforcel 已提交
37 38 39 40
*   **随机环境**:每个动作都与某种程度的随机性相关联的环境,因此,无论我们采取何种行动,都无法确定结果。 例如,将飞镖投掷到旋转的棋盘上或掷骰子。 在这两种情况下,结果均不确定。

因此,对于基于随机环境的问题,似乎最好的学习方法是尝试各种可能性。 因此,与其通过监督分类将其解决为模式识别问题,不如通过试错法更好,在这种方法中,结果标签将替换为量化特定操作对完成给定问题的最终目的的有用性的奖励。 声明。

W
wizardforcel 已提交
41
这产生了环境-智能体交互方法,我们在第 1 章,“深度强化–架构和框架”中讨论了该方法,在该系统中,我们设计了一种使智能体与环境交互的系统。 首先,通过传感器感知状态,通过效应器对环境执行一些操作,然后接收反馈,即对所采取操作的奖励,如下图所示:
W
wizardforcel 已提交
42 43 44

![](img/c7f38da6-965e-4b9f-9149-bc088223f8bd.png)

W
wizardforcel 已提交
45
根据传感器在特定时间步长感测环境时接收到的信号,此处的状态基本上是智能体对环境的看法。
W
wizardforcel 已提交
46

W
wizardforcel 已提交
47
# 基于模型的学习和无模型学习
W
wizardforcel 已提交
48

W
wizardforcel 已提交
49
在第 3 章,“马尔可夫决策过程”中,我们使用状态,动作,奖励,转移模型和折扣因子来解决我们的马尔可夫决策过程,即 MDP 问题。 因此,如果 MDP 问题的所有这些要素均可用,我们可以轻松地使用规划算法为目标提出解决方案。 这种类型的学习称为**基于模型的学习**,其中 AI 智能体将与环境交互,并基于其交互,将尝试近似环境的模型,即状态转换模型。 给定模型,现在智能体可以尝试通过值迭代或策略迭代找到最佳策略。
W
wizardforcel 已提交
50

W
wizardforcel 已提交
51
但是,对于我们的 AI 智能体来说,学习环境的显式模型不是必需的。 它可以直接从与环境的交互中得出最佳策略,而无需构建模型。 这种学习称为**无模型学习**。 无模型学习涉及在没有具体环境模型的情况下预测某个策略的值函数。
W
wizardforcel 已提交
52 53 54 55 56 57 58 59 60 61

可以使用两种方法完成无模型学习:

*   蒙特卡洛学习
*   时差学习

我们将在以下主题中讨论它们两者。

# 蒙特卡洛学习

W
wizardforcel 已提交
62
蒙特卡洛(Monte Carlo)是用于模型免费学习的最简单方法,在该方法中,智能体会观察剧集中前进的所有步骤(即前瞻)的回报。 因此,在时间`t`时的总估计报酬为`R[t]`
W
wizardforcel 已提交
63 64 65

![](img/142010fa-0c37-4b54-b47d-e27ca48bd3d6.png)

W
wizardforcel 已提交
66
这里,`γ`是折扣因子,`T`是剧集结束的时间步长。 我们可以使用以下代码初始化蒙特卡洛学习技术:
W
wizardforcel 已提交
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87

```py
Initialize:

     i.e. the policy to be evaluated
    V i.e. an arbitrary state-value function
    Returns(s) = empty list 
    #here Returns(s) refer to returns for a particular state i.e. the series of rewards the agent receives from that state onward

Repeat forever:

    Generate an episode using the current 
    For each state 's' appearing in the episode perform the following:
        R = returns following the first occurrence of 's'
        Append R to Returns(s)
        V(s) = Average(Returns(s))
    Update policy as per V
```

# 时差学习

W
wizardforcel 已提交
88
与在蒙特卡洛学习中我们要全面展望未来不同,在时间差异学习中,我们只有一个展望,也就是说,我们只观察到剧集的下一步:
W
wizardforcel 已提交
89 90 91

![](img/d82f0db8-7eb5-4425-b229-a08877c1ba7d.png)

W
wizardforcel 已提交
92
时间差异学习是一种用于学习值和策略迭代方法中的值函数以及 Q 学习中的 Q 函数的方法。
W
wizardforcel 已提交
93

W
wizardforcel 已提交
94
如果我们希望我们的 AI 智能体始终选择最大化折扣未来奖励的行动,那么我们需要某种时间差异学习。 为此,我们需要定义一个函数 Q,该函数表示当我们在状态`s`上执行动作`a`时最大的未来折扣。 因此,Q 函数表示给定状态下的动作质量。 使用它,我们可以通过仅了解当前状态和操作来估计最终得分,而在此之后无需进行任何操作。 因此,目标将是对具有最高 Q 值的状态采取该措施。 因此,我们必须通过称为 Q 学习的过程来学习此 Q 函数。
W
wizardforcel 已提交
95

W
wizardforcel 已提交
96
# 策略内和策略外学习
W
wizardforcel 已提交
97

W
wizardforcel 已提交
98
顾名思义,**脱离策略的**学习是独立于智能体行为的最优策略学习。 因此,您不需要一开始就使用特定的策略,并且即使通过随机动作开始,智能体也可以学习最佳策略,最终收敛到最佳策略。 Q 学习是非策略学习的一个例子。
W
wizardforcel 已提交
99 100 101 102 103

另一方面,**基于策略的**学习通过执行当前策略并通过探索方法对其进行更新来学习最佳策略。 因此,基于策略的学习取决于开始时的策略。 SARSA 算法是基于策略学习的示例。

# Q 学习

W
wizardforcel 已提交
104
在强化学习中,我们希望 Q 函数`Q(s, a)`预测状态`s`的最佳动作,以便最大化未来的回报。 使用 Q 学习估计 Q 函数,该过程涉及使用贝尔曼方程通过一系列迭代更新 Q 函数的过程,如下所示:
W
wizardforcel 已提交
105 106 107 108 109

![](img/8276b9a8-c086-4554-9c03-a77af4be5eb4.png)

这里:

W
wizardforcel 已提交
110
`Q(s, a)`为当前状态`s`和动作`a`对的`Q`
W
wizardforcel 已提交
111 112 113

![](img/ea5b424c-de3c-4fbc-a152-adbc346fd533.png) =学习收敛速度

W
wizardforcel 已提交
114
![](img/48218f21-9c36-4878-ab3e-bb4cbf9ee090.png) =未来奖励的折扣系数
W
wizardforcel 已提交
115

W
wizardforcel 已提交
116
`Q(s', a')`为在状态`s`下采取动作`a`之后,所得状态`s'`的状态动作对的`Q`
W
wizardforcel 已提交
117

W
wizardforcel 已提交
118
`R`表示即时奖励
W
wizardforcel 已提交
119 120 121 122 123 124 125 126

![](img/84d0d09c-e36e-4493-bf36-f0ec2f63fccb.png) =未来奖励

在状态空间和动作空间是离散的更简单的情况下,使用 Q 表实现 Q 学习,其中表代表状态,列代表动作。

Q 学习涉及的步骤如下:

1.  随机初始化 Q 表
W
wizardforcel 已提交
127
2.  对于每个剧集,请执行以下步骤:
W
wizardforcel 已提交
128
    1.  对于给定状态`s`,从 Q 表中选择动作`a`
W
wizardforcel 已提交
129 130 131
    2.  执行动作`a`
    3.  奖励`R`和状态`s'`
    4.  通过以下方法更新当前状态操作对的 Q 值,即`Q(s, a)`
W
wizardforcel 已提交
132 133 134

![](img/8276b9a8-c086-4554-9c03-a77af4be5eb4.png)

W
wizardforcel 已提交
135
但是,这里并没有探索新路径,并且在大多数情况下,智能体正在利用已知路径。 因此,实现了一定程度的随机性,以使 AI 智能体有时通过采取随机动作而不是当前的最佳动作来随机探索新路径。 探索背后的原因是,它增加了获得比当前更好的路径(即新的最佳策略)的可能性:
W
wizardforcel 已提交
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151

```py
Create Q-table where rows represent different states and columns represent different actions
Initialize Q(s,a) arbitrarily
For each episode:
    Start with the starting state i.e. Initialize s to start
    Repeat for each step in the episode:
        Choose action a for s using the policy derived from Q
        [e.g. -greedy, either for the given 's' which 'a' has the max Q-value or choose a random action]
        Take the chosen action a, observe reward R and new state s'
        Update 

    until s is the terminal state
end
```

W
wizardforcel 已提交
152
# 探索利用困境
W
wizardforcel 已提交
153

W
wizardforcel 已提交
154
下表总结了探索与利用之间的困境:
W
wizardforcel 已提交
155

W
wizardforcel 已提交
156 157
| **探索** | **利用** |
| --- | --- |
W
wizardforcel 已提交
158 159
| 除了当前的最佳动作之外,随机选择其他动作,并希望获得更好的回报。 | 选择当前的最佳操作而不尝试其他操作。 |

W
wizardforcel 已提交
160
因此,难题是 AI 是仅根据当前最佳策略基于动作信任已获悉的 Q 值,还是应该随机尝试其他动作以希望获得更好的回报,从而改善 Q 值,因此, 得出更好的最佳策略。
W
wizardforcel 已提交
161

W
wizardforcel 已提交
162
# OpenAI Gym 山地车问题的 Q 学习
W
wizardforcel 已提交
163

W
wizardforcel 已提交
164
**山地车**是强化学习领域的标准测试问题。 它由动力不足的汽车组成,必须将陡峭的山坡驱动到标志点,如下图所示:
W
wizardforcel 已提交
165 166 167 168 169

![](img/b2dea4b9-c53a-4380-a729-7330fa346818.png)

这里的要点是重力要比汽车的引擎强,因此即使在全油门的情况下,汽车也无法在陡峭的斜坡上加速。 因此,汽车必须以相反的方向反向行驶来利用势能,然后再利用势能到达右上角的标志点。

W
wizardforcel 已提交
170
在这里,状态空间是连续的,由两点定义:位置和速度。 对于给定的状态(即位置和速度),智能体可以采取三个离散的动作,即向前移动(向图中右上方),向相反方向(向图中左上方)或不使用引擎 ,即汽车处于空档。 智能体会收到负面奖励,直到达到目标状态。
W
wizardforcel 已提交
171

W
wizardforcel 已提交
172
Q 学习可以轻松地应用于具有离散状态空间和动作的环境,但是这个问题成为了强化学习算法的测试平台,因为它具有连续状态空间,并且需要离散化连续状态空间或函数逼近才能将其映射到离散类。
W
wizardforcel 已提交
173 174 175 176 177

下面列出了山地车问题的技术细节,供您参考:

状态空间是二维且连续的。 它由位置和速度组成,具有以下值:

W
wizardforcel 已提交
178 179
*   **位置**`(-1.2, 0.6)`
*   **速度**`(-0.07, 0.07)`
W
wizardforcel 已提交
180 181 182

动作空间是离散的和一维的,具有三个选项:

W
wizardforcel 已提交
183
*   (左,中,右)
W
wizardforcel 已提交
184

W
wizardforcel 已提交
185
每个时间步数都奖励 -1。
W
wizardforcel 已提交
186 187 188 189 190 191 192 193

起始状态:

*   **位置**:-0.5
*   **速度**:0.0

终端状态条件:

W
wizardforcel 已提交
194
*   **剧集**在大于等于 0.6 的位置结束
W
wizardforcel 已提交
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228

正如我们现在看到的 Q 学习的参数一样,我们现在将研究解决山地车问题的 Q 学习的实现。

首先,我们将使用以下代码导入依赖项并检查山地车环境:

```py
#importing the dependencies

import gym
import numpy as np

#exploring Mountain Car environment

env_name = 'MountainCar-v0'
env = gym.make(env_name)

print("Action Set size :",env.action_space)
print("Observation set shape :",env.observation_space) 
print("Highest state feature value :",env.observation_space.high) 
print("Lowest state feature value:",env.observation_space.low) 
print(env.observation_space.shape) 
```

先前的打印语句输出以下内容:

```py
Making new env: MountainCar-v0
('Action Set size :', Discrete(3))
('Observation set shape :', Box(2,))
('Highest state feature value :', array([ 0.6 , 0.07]))
('Lowest state feature value:', array([-1.2 , -0.07]))
(2,)
```

W
wizardforcel 已提交
229
因此,我们看到动作空间是一个离散集合,显示了三个可能的动作,状态空间是一个二维连续空间,其中一个维度满足位置,而另一个则满足汽车的速度。 接下来,我们将使用以下代码分配超参数,例如状态数,剧集数,学习率(初始和最小值),折扣因子伽玛,剧集中的最大步数以及 ε 贪婪的`ε`
W
wizardforcel 已提交
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268

```py
n_states = 40  # number of states
episodes = 10 # number of episodes

initial_lr = 1.0 # initial learning rate
min_lr = 0.005 # minimum learning rate
gamma = 0.99 # discount factor
max_steps = 300
epsilon = 0.05

env = env.unwrapped
env.seed(0)         #setting environment seed to reproduce same result
np.random.seed(0)   #setting numpy random number generation seed to reproduce same random numbers
```

我们的下一个任务是创建一个函数来对连续状态空间进行离散化。 离散化是将连续状态空间观察转换为离散状态空间集:

```py
def discretization(env, obs):

    env_low = env.observation_space.low
    env_high = env.observation_space.high

    env_den = (env_high - env_low) / n_states
    pos_den = env_den[0]
    vel_den = env_den[1]

    pos_high = env_high[0]
    pos_low = env_low[0]
    vel_high = env_high[1]
    vel_low = env_low[1]

    pos_scaled = int((obs[0] - pos_low)/pos_den)  #converts to an integer value
    vel_scaled = int((obs[1] - vel_low)/vel_den)  #converts to an integer value

    return pos_scaled,vel_scaled
```

W
wizardforcel 已提交
269
现在,我们将通过初始化 Q 表并相应地更新 Q 值来开始实现 Q 学习算法。 在这里,我们将奖励值更新为当前位置与最低点(即起点)之间的绝对差值,以便它通过远离中心即最低点来最大化奖励。 这样做是为了实现更好的收敛:
W
wizardforcel 已提交
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308

```py
#Q table
#rows are states but here state is 2-D pos,vel
#columns are actions
#therefore, Q- table would be 3-D

q_table = np.zeros((n_states,n_states,env.action_space.n))
total_steps = 0
for episode in range(episodes):
      obs = env.reset()
      total_reward = 0
      # decreasing learning rate alpha over time
      alpha = max(min_lr,initial_lr*(gamma**(episode//100)))
      steps = 0
      while True:
          env.render()
          pos,vel = discretization(env,obs)

          #action for the current state using epsilon greedy
          if np.random.uniform(low=0,high=1) < epsilon:
                a = np.random.choice(env.action_space.n)
          else:
                a = np.argmax(q_table[pos][vel])
          obs,reward,terminate,_ = env.step(a) 
          total_reward += abs(obs[0]+0.5)

          #q-table update
          pos_,vel_ = discretization(env,obs)
          q_table[pos][vel][a] = (1-alpha)*q_table[pos][vel][a] + alpha*(reward+gamma*np.max(q_table[pos_][vel_]))
          steps+=1
          if terminate:
                break
      print("Episode {} completed with total reward {} in {} steps".format(episode+1,total_reward,steps)) 

while True: #to hold the render at the last step when Car passes the flag
      env.render() 
```

W
wizardforcel 已提交
309
根据学习情况,前面的 Q 学习器将以以下方式打印输出:
W
wizardforcel 已提交
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333

```py
Episode 1 completed with total reward 8433.30289388 in 26839 steps
Episode 2 completed with total reward 3072.93369963 in 8811 steps
Episode 3 completed with total reward 1230.81734028 in 4395 steps
Episode 4 completed with total reward 2182.31111239 in 6629 steps
Episode 5 completed with total reward 2459.88770998 in 6834 steps
Episode 6 completed with total reward 720.943914405 in 2828 steps
Episode 7 completed with total reward 389.433014729 in 1591 steps
Episode 8 completed with total reward 424.846699654 in 2362 steps
Episode 9 completed with total reward 449.500988781 in 1413 steps
Episode 10 completed with total reward 222.356805259 in 843 steps
```

这还将渲染一个环境,显示汽车在行驶并采取最佳路径并达到目标状态,如以下屏幕截图所示:

![](img/69dbc107-d503-4cbe-b66c-c073c7a4a74d.png)

山地赛车游戏的最终状态视图

因此,我们看到无模型学习可以从其与环境的交互中得出最佳策略,而无需创建环境模型。 因此,我们已经知道,Q 学习是一种无模型的时间差异学习,它通过估计 Q 函数来找到最佳状态动作选择策略。

# 深度 Q 网络

W
wizardforcel 已提交
334
如果我们回想起第 2 章和“使用 OpenAI Gym 训练强化学习智能体”,我们曾尝试在其中实现基本的 Q 网络,之后我们针对一个实际问题研究了 Q 学习。 由于连续的状态和动作空间,使用 Q 表不是可行的解决方案。 而且,Q 表是特定于环境的,而不是通用的。 因此,我们需要一个模型,该模型可以将作为输入提供的状态信息映射到可能的一组动作的 Q 值。 在这里,神经网络开始发挥函数逼近器的作用,函数逼近器可以接受向量形式的状态信息输入,并学习将其映射为所有可能动作的 Q 值。
W
wizardforcel 已提交
335

W
wizardforcel 已提交
336
让我们讨论游戏环境中的 Q 学习问题以及深度 Q 网络的发展。 考虑将 Q 学习应用于游戏环境,该状态将由玩家,障碍物,对手等的位置来定义,但这将是特定于游戏的,即使在我们创建一个以某种方式表示该游戏所有可能状态的 Q 表,也不能在其他游戏环境中推广。
W
wizardforcel 已提交
337

W
wizardforcel 已提交
338
好吧,游戏环境有一个共同点,那就是全部由像素组成。 如果可以将像素输入可以映射到动作的模型中,则可以在所有游戏中将其推广。 DeepMind 的卷积神经网络实现具有游戏图像帧,其中输入和输出是该环境中每个可能动作的 Q 值。 卷积神经网络由三个卷积层和两个全连接层组成。 **卷积神经网络****CNN**)的一个元素是池化层,在此已避免。 使用池化层的主要原因是在图像中进行对象检测的情况下,其中图像中对象的位置并不重要,而在此处,在游戏框架中对象的位置非常重要时,则不然。
W
wizardforcel 已提交
339

W
wizardforcel 已提交
340
因此,在游戏环境中,**深度 Q 网络****DQN**)由连续的游戏帧组成,作为捕获动作的输入,并为游戏中所有可能的动作输出 Q 值。 游戏。 由于将深度神经网络用作 Q 函数的函数逼近器,因此此过程称为深度 Q 学习。
W
wizardforcel 已提交
341 342 343 344

与 Q 网络相比,深度 Q 网络具有更强的泛化能力。 为了将 Q 网络转换为深度 Q 网络,我们需要进行以下改进:

*   使用卷积神经网络代替单层神经网络
W
wizardforcel 已提交
345
*   使用经验回放
W
wizardforcel 已提交
346
*   分离目标网络来计算目标 Q 值
W
wizardforcel 已提交
347 348 349 350 351 352 353

在以下主题中,我们将详细讨论每个参数:

# 使用卷积神经网络代替单层神经网络

我们的游戏环境是视频,而卷积神经网络在计算机视觉方面已经显示了最新的成果。 而且,游戏框架中物体检测的水平应该接近人类水平的能力,并且卷积神经网络从图像中学习表示,类似于人类原始视觉皮层的行为。

W
wizardforcel 已提交
354
DeepMind 在其 DQN 网络中使用了三个卷积层和两个全连接层,从而在 Atari 游戏中实现了超人水平的表现,如以下流程图所示:
W
wizardforcel 已提交
355 356 357

![](img/373d89a3-057c-444c-91b2-bbe4eeb5e1b4.png)

W
wizardforcel 已提交
358
# 使用经验回放
W
wizardforcel 已提交
359

W
wizardforcel 已提交
360
添加到深度 Q 网络的另一个重要功能是**经验回放**。 该功能背后的想法是,智能体可以存储其过去的经验,并分批使用它们来训练深度神经网络。 通过存储经验,智能体可以随机抽取批量,并帮助网络从各种数据中学习,而不仅仅是对即时经验的决策正式化。 这些经历中的每一个都以包括**状态,动作,奖励****下一个状态**的四维向量的形式存储。
W
wizardforcel 已提交
361

W
wizardforcel 已提交
362
为了避免存储问题,体验重放的缓冲区是固定的,并且随着新体验的存储,旧体验将被删除。 为了训练神经网络,从缓冲区中提取均匀批量的随机经验。
W
wizardforcel 已提交
363

W
wizardforcel 已提交
364
# 分离目标网络来计算目标 Q 值
W
wizardforcel 已提交
365

W
wizardforcel 已提交
366
生成目标 Q 值的单独网络是一项重要功能,它使深层 Q 网络具有独特性。 由此单独的目标网络生成的 Q 值用于计算智能体在训练过程中采取的每项操作之后的损失。 使用两个网络而不是一个网络的原因在于,由于每个步骤的权重变化,主要的 Q 网络值在每个步骤中都在不断变化,这使得从该网络生成的 Q 值不稳定。
W
wizardforcel 已提交
367

W
wizardforcel 已提交
368
为了获得稳定的 Q 值,使用了另一个神经网络,其权重与主要 Q 网络相比变化缓慢。 这样,训练过程更加稳定。 DeepMind 的文章也发表在[这个页面](http://www.davidqiu.com:8888/research/nature14236.pdf)中。 他们发现这种方法能够稳定训练过程。
W
wizardforcel 已提交
369

W
wizardforcel 已提交
370
[改编自 Minh 等人(2015)](http://www.davidqiu.com:8888/research/nature14236.pdf),以下是 DQN 的伪代码:
W
wizardforcel 已提交
371 372 373 374 375

```py
Input: the image(game frame) pixels 

Initialize replay memory D for experience replay
W
wizardforcel 已提交
376
Initialize action-value function`Q`i.e. primary neural network with random weight   
W
wizardforcel 已提交
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
Initialize target action-value function QT i.e. target neural network with weights 

for each episode do
    Initialize sequence  and preprocessed sequence 
    for t = 0 to max_step in an episode do
        Choose  using -greedy such that 

        Perform action 
        Observe reward  and image 
        Set  and preprocess 
        Store transition  in D

        // experience replay
        Sample random batch of transitions  from D
        Set 
        Compute the cost function = 
        Perform gradient descent on the cost function w.r.t. the primary network parameter θ

        // periodic update of target network
        After every C steps reset , i.e., set 

    until end of episode 
end
```

# 深度 Q 网络以及其他方面的进步

W
wizardforcel 已提交
404
随着更多的研究和更多的时间,深度 Q 网络已经进行了许多改进,从而获得了更好的架构,从而提供了更高的表现和稳定性。 在本节中,我们将仅讨论两种著名的架构,即**双重 DQN****决斗 DQN**
W
wizardforcel 已提交
405 406 407

# 双 DQN

W
wizardforcel 已提交
408
使用**双重 DQN(DDQN)**的原因是常规 DQN 高估了在给定状态下可能采取的措施的 Q 值。 在常规 DQN 中,所有动作之间的高估都不相等。 因此,问题仍然存在:否则,所有行动之间的均等估计就不会成为问题。 结果,某些次优的行为获得了更高的值,因此学习最佳策略的时间增加了。 这导致我们对常规 DQN 架构进行了少量修改,并导致了所谓的 DDQN,即双深度 Q 网络。
W
wizardforcel 已提交
409 410 411 412 413 414 415 416 417 418 419 420 421

在 DDQN 中,我们不是在训练过程中计算目标 Q 值时取最大 Q 值,而是使用主要网络选择操作,并使用目标网络为该操作生成目标 Q 值。 这使动作脱钩。 从目标 Q 网络中进行选择,生成目标 Q 值,从而减少高估,并有助于更快地进行训练。 DDQN 中的目标 Q 值通过以下公式更新:

![](img/da95ebb9-7b3c-454e-950c-125072e0b43f.png)

这里:

![](img/9ce748d1-69db-4379-9a41-8f3ee9dc46c2.png)代表主网络的权重,并且

![](img/e6c5b010-6999-4954-bfb0-64848de92322.png)代表目标网络的权重。

# 决斗 DQN

W
wizardforcel 已提交
422
**决斗 DQN** 的情况下,Q 值已被修改为状态的值函数和操作的优势函数的总和。 值函数`V(s)`量化处于状态`s`的有用性或优势,优势函数`A(a)`量化行为的优势代替其他可能的操作。 因此,
W
wizardforcel 已提交
423 424 425

![](img/f31160d6-e246-41db-84d5-91fc9bd2495c.png)

W
wizardforcel 已提交
426
决斗 DQN 具有独立的网络来计算值和优势函数,然后将它们组合回以获取 Q 函数的值。 将值和优势的计算脱钩的原因在于,对于给定状态下的每个动作,智能体不必照顾不必要的值函数。 因此,将这些计算去耦会导致鲁棒的状态动作 Q 值。
W
wizardforcel 已提交
427

W
wizardforcel 已提交
428
# 用于 OpenAI Gym 山地车问题的深度 Q 网络
W
wizardforcel 已提交
429

W
wizardforcel 已提交
430
在针对山地车问题实现 Q 学习时,我们已经讨论了环境。 让我们直接深入实现一个深度 Q 网络来解决山地车问题。 首先,我们将使用以下代码导入所需的库:
W
wizardforcel 已提交
431 432 433 434 435 436 437 438 439

```py
#importing the dependencies

import numpy as np
import tensorflow as tf
import gym
```

W
wizardforcel 已提交
440
让我们讨论一下 DQN 类,其中包含深度 Q 网络的架构:
W
wizardforcel 已提交
441

W
wizardforcel 已提交
442
*   `__init__``self``learning_rate``gamma``n_features``n_actions``epsilon``parameter_changing_pointer``memory_size`):默认构造器,用于分配超参数,例如:
W
wizardforcel 已提交
443
    *   `learning_rate`
W
wizardforcel 已提交
444
    *   `gamma`,即折扣因子
W
wizardforcel 已提交
445
    *   `n_feature`:状态中的要素数,即状态中的尺寸数
W
wizardforcel 已提交
446
    *   `epsilon`:ε 贪婪条件的阈值,以利用或探索动作
W
wizardforcel 已提交
447 448 449 450 451 452
*   `build_networks()`:使用 Tensorflow 创建主要和目标网络
*   `target_params_replaced(self)`:用主要网络参数替换目标网络参数
*   `store_experience(self,obs,a,r,obs_)`:存储经验,即(状态,动作,奖励,新状态)的元组
*   `fit(self)`:训练我们的深度 Q 网络
*   `epsilon_greedy(self,obs)`:对于给定的观察状态,要采取的操作,即根据现有策略利用操作或随机探索新操作

W
wizardforcel 已提交
453
可以使用以下代码定义具有`main`函数的 DQN 类的架构:
W
wizardforcel 已提交
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474

```py
class DQN:
    def __init__(self,learning_rate,gamma,n_features,
                 n_actions,epsilon,parameter_changing_pointer,memory_size):
    ....
    def build_networks(self):
    ....
    def target_params_replaced(self):
    ....
    def store_experience(self,obs,a,r,obs_):
    ....
    def fit(self):
    ....
    def epsilon_greedy(self,obs): 
    ....

if __name__ == "__main__":
    ....
```

W
wizardforcel 已提交
475
`__init__`:在以下代码片段中解释了默认构造器以及注释:
W
wizardforcel 已提交
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505

```py
def __init__(self,learning_rate,gamma,n_features,n_actions,epsilon,parameter_changing_pointer,memory_size):

        self.learning_rate = learning_rate
        self.gamma = gamma
        self.n_features = n_features
        self.n_actions = n_actions
        self.epsilon = epsilon
        self.batch_size = 100
        self.experience_counter = 0
        self.experience_limit = memory_size
        self.replace_target_pointer = parameter_changing_pointer
        self.learning_counter = 0
        self.memory = np.zeros([self.experience_limit,self.n_features*2+2]) #for experience replay

        self.build_networks()
        #to fetch parameters under the collection : 'primary_network_parameters'
        p_params = tf.get_collection('primary_network_parameters')

        #to fetch parameters under the collection : 'target_network_parameters'
        t_params = tf.get_collection('target_network_parameters')

        #replacing tensor replace the target network parameters with primary network parameters
        self.replacing_target_parameters = [tf.assign(t,p) for t,p in zip(t_params,p_params)]

        self.sess = tf.Session()
        self.sess.run(tf.global_variables_initializer())
```

W
wizardforcel 已提交
506
现在让我们初始化`build_networks(self)`。 它是构建主要网络和目标网络的函数:
W
wizardforcel 已提交
507 508 509 510 511 512

*`variable_scope``primary_network`下创建主要网络参数,并在`primary_network_parameters`集合中创建
*`variable_scope``target_network`下创建目标网络参数,并在`target_network_parameters`集合中创建目标网络参数
*   这两个参数具有相同的结构,即:
    *   `w1`:与输入层关联的权重矩阵
    *   `b1`:与输入层关联的偏置向量
W
wizardforcel 已提交
513
    *   `ReLU`:信号从输入到隐藏层的激活函数
W
wizardforcel 已提交
514
    *   `w2`:与隐藏层关联的权重矩阵
W
wizardforcel 已提交
515
    *   `b2`:与隐藏层关联的偏置向量
W
wizardforcel 已提交
516
*   计算主网络输出的 Q 值与目标网络输出的 Q 值之间的损失
W
wizardforcel 已提交
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
*   使用 **adam** 优化器将损失降到最低

我们将使用以下代码定义`build_networks(self)`函数:

```py
def build_networks(self):
        #primary network
        hidden_units = 10
        self.s = tf.placeholder(tf.float32,[None,self.n_features])
        self.qtarget = tf.placeholder(tf.float32,[None,self.n_actions])

        with tf.variable_scope('primary_network'):
                c = ['primary_network_parameters', tf.GraphKeys.GLOBAL_VARIABLES]
                # first layer
                with tf.variable_scope('layer1'):
                    w1 = tf.get_variable('w1',[self.n_features,hidden_units],initializer=tf.contrib.layers.xavier_initializer(),dtype=tf.float32,collections=c)
                    b1 = tf.get_variable('b1',[1,hidden_units],initializer=tf.contrib.layers.xavier_initializer(),dtype=tf.float32,collections=c)
                    l1 = tf.nn.relu(tf.matmul(self.s, w1) + b1)

                # second layer
                with tf.variable_scope('layer2'):
                    w2 = tf.get_variable('w2',[hidden_units,self.n_actions],initializer=tf.contrib.layers.xavier_initializer(),dtype=tf.float32,collections=c)
                    b2 = tf.get_variable('b2',[1,self.n_actions],initializer=tf.contrib.layers.xavier_initializer(),dtype=tf.float32,collections=c)
                    self.qeval = tf.matmul(l1, w2) + b2

        with tf.variable_scope('loss'):
                self.loss = tf.reduce_mean(tf.squared_difference(self.qtarget,self.qeval))

        with tf.variable_scope('optimizer'):
                self.train = tf.train.AdamOptimizer(self.learning_rate).minimize(self.loss)

        #target network
        self.st = tf.placeholder(tf.float32,[None,self.n_features])

        with tf.variable_scope('target_network'):
                c = ['target_network_parameters', tf.GraphKeys.GLOBAL_VARIABLES]
                # first layer
                with tf.variable_scope('layer1'):
                        w1 = tf.get_variable('w1', [self.n_features,hidden_units],initializer=tf.contrib.layers.xavier_initializer(),dtype=tf.float32,collections=c)
                        b1 = tf.get_variable('b1', [1,hidden_units],initializer=tf.contrib.layers.xavier_initializer(),dtype=tf.float32,collections=c)
                        l1 = tf.nn.relu(tf.matmul(self.st, w1) + b1)

                # second layer
                with tf.variable_scope('layer2'):
                        w2 = tf.get_variable('w2',[hidden_units,self.n_actions],initializer=tf.contrib.layers.xavier_initializer(),dtype=tf.float32,collections=c)
                        b2 = tf.get_variable('b2',[1,self.n_actions],initializer=tf.contrib.layers.xavier_initializer(),dtype=tf.float32,collections=c)
                        self.qt = tf.matmul(l1, w2) + b2

```

W
wizardforcel 已提交
567
现在,我们将使用以下代码定义`target_params_replaced(self)`。 运行将主网络参数分配给目标网络参数的张量操作的函数:
W
wizardforcel 已提交
568 569 570 571 572 573

```py
def target_params_replaced(self):
        self.sess.run(self.replacing_target_parameters)
```

W
wizardforcel 已提交
574
现在,我们将定义`store_experience(self,obs,a,r,obs_)`,该函数用于将每种体验(即(状态,动作,奖励,新状态)的元组)存储在其体验缓冲区中,通过该数组可以训练主要目标,例如以下代码:
W
wizardforcel 已提交
575 576 577 578 579 580 581 582

```py
def store_experience(self,obs,a,r,obs_):
        index = self.experience_counter % self.experience_limit
        self.memory[index,:] = np.hstack((obs,[a,r],obs_))
        self.experience_counter+=1
```

W
wizardforcel 已提交
583
在这里,我们将定义`fit(self)`,该函数是通过从经验缓冲区中选择一批来训练网络,计算`q_target`的张量值,然后最小化`qeval`之间的损失(即输出)的函数。 来自主网络的数据)和`q_target`(即使用目标网络计算的 Q 值)。 我们将使用以下代码定义函数:
W
wizardforcel 已提交
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615

```py
def fit(self):
        # sample batch memory from all memory
        if self.experience_counter < self.experience_limit:
               indices = np.random.choice(self.experience_counter, size=self.batch_size)
        else:
               indices = np.random.choice(self.experience_limit, size=self.batch_size)

        batch = self.memory[indices,:]
        qt,qeval = self.sess.run([self.qt,self.qeval],feed_dict={self.st:batch[:,-self.n_features:],self.s:batch[:,:self.n_features]})

        qtarget = qeval.copy() 
        batch_indices = np.arange(self.batch_size, dtype=np.int32)
        actions = self.memory[indices,self.n_features].astype(int)
        rewards = self.memory[indices,self.n_features+1]
        qtarget[batch_indices,actions] = rewards + self.gamma * np.max(qt,axis=1)

        self.sess.run(self.train,feed_dict = {self.s:batch[:,:self.n_features],self.qtarget:qtarget})

        #increasing epsilon 
        if self.epsilon < 0.9:
                self.epsilon += 0.0002

        #replacing target network parameters with primary network parameters 
        if self.learning_counter % self.replace_target_pointer == 0:
                self.target_params_replaced()
                print("target parameters changed")

        self.learning_counter += 1
```

W
wizardforcel 已提交
616
我们已经讨论了探索与利用难题。 **ε 贪婪方法**是用于选择阈值`ε`并产生随机数的方法之一。 如果小于`ε`,我们将遵循相同的策略;如果大于`ε`,我们将随机探索行动,反之亦然。 在`epsilon_greedy(self,obs)`中,我们以动态方式实现了 ε 贪婪方法,其中在`fit(self)`函数中,我们在每个学习步骤中都增加了`ε`的值:
W
wizardforcel 已提交
617 618 619 620 621 622 623 624 625 626

```py
def epsilon_greedy(self,obs):
        #epsilon greedy implementation to choose action
        if np.random.uniform(low=0,high=1) < self.epsilon:
            return np.argmax(self.sess.run(self.qeval,feed_dict={self.s:obs[np.newaxis,:]}))
        else:
            return np.random.choice(self.n_actions)
```

W
wizardforcel 已提交
627
以下是`main`函数,该函数创建上一个 DQN 类的对象,使用 Gym 来获取`MountainCar-v0`环境,并训练智能体程序来解决问题。 像在 Q 学习中一样,在这里我们还更新了奖励值,将其作为当前位置与最低点位置(即起点)之间的绝对差值,从而使其偏离中心而最大化了奖励:
W
wizardforcel 已提交
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690

```py
if __name__ == "__main__":
      env = gym.make('MountainCar-v0')
      env = env.unwrapped
      dqn = DQN(learning_rate=0.001,gamma=0.9,n_features=env.observation_space.shape[0],n_actions=env.action_space.n,epsilon=0.0,parameter_changing_pointer=500,memory_size=5000)
      episodes = 10
      total_steps = 0

      for episode in range(episodes):
            steps = 0 
            obs = env.reset()
            episode_reward = 0
            while True:
                env.render()
                action = dqn.epsilon_greedy(obs)
                obs_,reward,terminate,_ = env.step(action)
                reward = abs(obs_[0]+0.5)
                dqn.store_experience(obs,action,reward,obs_)
                if total_steps > 1000:
                      dqn.fit()
                episode_reward+=reward
                if terminate:
                      break
                obs = obs_
                total_steps+=1
                steps+=1
            print("Episode {} with Reward : {} at epsilon {} in steps {}".format(episode+1,episode_reward,dqn.epsilon,steps))

      while True: #to hold the render at the last step when Car passes the flag
            env.render() 
```

前面的程序将打印以下内容:

```py
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
Episode 1 with Reward : 1399.25710453 at epsilon 0.5948 in steps 3974
target parameters changed
Episode 2 with Reward : 168.166352703 at epsilon 0.6762 in steps 406
target parameters changed
Episode 3 with Reward : 67.6246277944 at epsilon 0.7568 in steps 402
Episode 4 with Reward : 53.1292577484 at epsilon 0.7942 in steps 186
target parameters changed
Episode 5 with Reward : 38.90009005 at epsilon 0.818 in steps 118
Episode 6 with Reward : 60.9286778233 at epsilon 0.8738 in steps 278
target parameters changed
Episode 7 with Reward : 72.433268035 at epsilon 0.9002 in steps 257
Episode 8 with Reward : 80.7812592557 at epsilon 0.9002 in steps 251
target parameters changed
Episode 9 with Reward : 92.123978864 at epsilon 0.9002 in steps 234
Episode 10 with Reward : 38.7923903502 at epsilon 0.9002 in steps 126
```

在这里,它收敛很快,但是还取决于对动作的探索和利用以及参数和超参数的初始化。 这还将渲染一个环境,显示汽车在行驶并采取最佳路径并达到目标状态,如以下屏幕截图所示:

![](img/c662d7ff-84d0-4610-b63d-daec721d3143.png)

W
wizardforcel 已提交
691
接下来,在以下主题中,我们尝试实现一个深度 Q 网络来解决 OpenAI Gym 中的 Cartpole 问题。
W
wizardforcel 已提交
692

W
wizardforcel 已提交
693
# 用于 OpenAI Gym Cartpole 问题的深度 Q 网络
W
wizardforcel 已提交
694 695 696 697 698 699 700

**Cartpole** 是 MDP 环境中最简单的问题之一,如以下屏幕快照所示。 它由一个在水平轴上移动的推车组成,该推车的中心处固定有一根可旋转的杆。 目的是采取行动,使电杆保持接近垂直且不会向下旋转。

![](img/59c7c998-1669-4892-a27c-9b375e62a033.png)

车杆环境中的状态是一个 4 维连续空间,其中每个维如下:

W
wizardforcel 已提交
701
*   `x`:表示推车位置(最小值为 -2.4,最大值为 2.4)
W
wizardforcel 已提交
702 703 704
*   `x_dot`:表示推车速度(最小值为`-∞`
*   `theta`:显示以弧度为单位的角度(最小值为 -0.73,最大值为 0.73)
*   `theta_dot`:显示角速度(最小值为`-∞`,最大值为`∞`
W
wizardforcel 已提交
705

W
wizardforcel 已提交
706
在给定状态下的每一步,都有两种可能的动作,即推车可以向左或向右移动,并且每一步收到的奖励为 1。这里,只要杆子靠近垂直,推车在边界内。 如果发生以下情况,则剧集被视为结束:
W
wizardforcel 已提交
707

W
wizardforcel 已提交
708 709
*   极点下降超过某个角度,即超过 ±0.20944 弧度
*   推车超出框架左侧或右侧太远,即超出 ±2.4
W
wizardforcel 已提交
710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751

因此,该问题的目的是将杆保持在接近垂直的位置,而推车不会越过边界越长。

为了为 Cartpole 问题实现深层 Q 网络,我们将导入先前创建的 DQN 类。 请按照以下代码在 Cartpole 环境中实现深度 Q 网络。 如果连续 100 次试验的平均奖励大于或等于 195,则认为该问题已解决:

```py
#Importing dependencies
import gym
import numpy as np

#Importing the DQN class created preceding
from Deep_Q_Network_Mountain_Car import DQN
```

现在,我们将使用以下代码探索 Cartpole 环境:

```py
env = gym.make('CartPole-v0')
env = env.unwrapped

print(env.action_space)
print(env.observation_space)
print(env.observation_space.high)
print(env.observation_space.low)
print("Position extreme threshold value:",env.x_threshold)
print("Angle extreme threshold value:",env.theta_threshold_radians)
```

先前的打印语句输出以下内容:

```py
Making new env: CartPole-v0
Discrete(2)
Box(4,)
[ 4.80000000e+00 3.40282347e+38 4.18879020e-01 3.40282347e+38]
[ -4.80000000e+00 -3.40282347e+38 -4.18879020e-01 -3.40282347e+38]
Position extreme threshold value: 2.4
Angle extreme threshold value: 0.20943951023931953
```

在此,观察空间的高/低值遵循以下顺序(位置,速度,角度,角速度)

W
wizardforcel 已提交
752
下面的代码是我们创建 DQN 上一类的对象,使用 Gym 来获取 Cartpole-v0 环境以及训练智能体程序以解决问题的主要部分。 在这里,我们将奖励值更新为位置与极端位置之差和角度与极端极角之差的总和,因为远离极限位置,角度将变得更小,并且更接近推车的中央,因此奖励应该更高。 这样做是为了实现更好的主网络融合。 我们将使用此奖励进行学习,但是为了计算总体成功程度,我们将使用原始奖励:
W
wizardforcel 已提交
753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848

```py
dqn = DQN(learning_rate=0.01,gamma=0.9,n_features=env.observation_space.shape[0],n_actions=env.action_space.n,epsilon=0.0,parameter_changing_pointer=100,memory_size=2000)

episodes = 150
total_steps = 0
rew_ep = []

for episode in range(episodes):
    steps = 0
    obs = env.reset()
    episode_reward = 0
    while True:
        env.render()
        action = dqn.epsilon_greedy(obs)
        obs_,reward,terminate,_ = env.step(action)

        #smaller the theta angle and closer to center then better should be the reward
        x, vel, angle, ang_vel = obs_
        r1 = (env.x_threshold - abs(x))/env.x_threshold - 0.8
        r2 = (env.theta_threshold_radians - abs(angle))/env.theta_threshold_radians - 0.5
        reward = r1 + r2

        dqn.store_experience(obs,action,reward,obs_)
        if total_steps > 1000:
            dqn.fit()
        episode_reward+=reward
        if terminate:
            break
        obs = obs_
        total_steps+=1
        steps+=1
    print("Episode {} with Reward : {} at epsilon {} in steps {}".format(episode+1,episode_reward,dqn.epsilon,steps))
    rew_ep.append(episode_reward)

print("Mean over last 100 episodes are: ",np.mean(rew_ep[50:]))
while True: #to hold the render at the last step when Car passes the flag
    env.render()
```

前面的程序将打印输出,如下所示:

```py
.................
.................
.................
Episode 145 with Reward : 512.0 at epsilon 0.9002 in steps 511
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
Episode 146 with Reward : 567.0 at epsilon 0.9002 in steps 566
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
Episode 147 with Reward : 1310.0 at epsilon 0.9002 in steps 1309
Episode 148 with Reward : 22.0 at epsilon 0.9002 in steps 21
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
Episode 149 with Reward : 1171.0 at epsilon 0.9002 in steps 1170
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
target parameters changed
Episode 150 with Reward : 1053.0 at epsilon 0.9002 in steps 1052
Mean over last 100 episodes are: 248.72999999999999
```

W
wizardforcel 已提交
849
由于输出日志太长,因此在这里,我们的输出适合最近六个剧集以及最近 100 个剧集的每集平均奖励。
W
wizardforcel 已提交
850

W
wizardforcel 已提交
851
# 用于 OpenAI Gym Atari Breakout 的深度 Q 网络
W
wizardforcel 已提交
852

W
wizardforcel 已提交
853
**Breakout** 环境是 Atari 的 Nolan Bushnell,Steve Bristow 和 Steve Wozniak 团队开发的。与我们在山地车 Cartpole 中看到的状态相比,Atari Breakout 环境的状态要大得多。 或“冰湖”。 状态空间与我们在 Atari Pong 中看到的范围相似。 因此,学习收敛需要很长时间。 以下屏幕快照说明了 Atari Breakout 环境的初始图像帧:
W
wizardforcel 已提交
854 855 856 857 858

![](img/0ddf6045-7c46-45a8-b644-11756f45a966.png)

Breakout-v0 环境的屏幕截图

W
wizardforcel 已提交
859
观察空间是连续的,包含图像帧的像素值,并且动作空间是离散的,包括四个不同的动作。 每个图像帧的大小为`210 * 160 * 3`(高度为 210 像素,宽度为 160 像素,具有 3 个颜色通道,即 RGB)。 因此,我们可以拍摄灰度图像帧,因为不会丢失任何信息,并且尺寸将变为`210 * 160`。 仅拍摄状态的图像帧将不起作用,因为它无法捕获任何运动信息。 因此,我们将为每个状态堆叠四个连续的图像帧。 因此,状态大小将是`4 * 210 * 160 = 134,440`。 对于 Atari Breakout,在一定程度上降低采样率不会造成任何信息丢失。 此外,我们还可以裁剪图像框架以避免图像的不必要部分,并保留可能包含足够信息来玩游戏的重要部分。
W
wizardforcel 已提交
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880

首先,使用以下代码检查环境:

```py
import gym

env = gym.make('Breakout-v0')
s = env.reset()
print("State space shape : ",env.observation_space)
print("Action space shape : ",env.action_space)
print("Type of actions : ",env.unwrapped.get_action_meanings())
```

这将输出以下语句:

```py
State space shape : Box(210, 160, 3)
Action space shape : Discrete(4)
Type of actions : ['NOOP', 'FIRE', 'RIGHT', 'LEFT']
```

W
wizardforcel 已提交
881
因此,我们得到了状态空间和动作空间的形状,以及球拍可以采取的四种不同类型的动作,即无动作(无操作的简称),开火(上方目标砖的球),向右走, 或向左移动以阻止球下降。
W
wizardforcel 已提交
882 883 884 885 886 887 888 889 890

我们还检查示例裁剪并查看差异,如下图所示:

![](img/74724bff-3706-4ea9-990d-707020570fbf.png) ![](img/1bbe7bb3-0e3c-48ae-97e8-cb276076ab45.png)

裁剪前(左)和裁剪后(右)

游戏以下列方式进行:

W
wizardforcel 已提交
891
*   底部的球拍将球击发,击中砖块以摧毁屏幕的顶层
W
wizardforcel 已提交
892
*   击中砖块后,球反弹回来
W
wizardforcel 已提交
893
*   球拍应向左或向右移动以击中球并阻止其掉落
W
wizardforcel 已提交
894 895 896
*   如果球落到下方,也就是说,从球拍下方的屏幕上移开,则游戏结束且玩家输
*   如果球从球拍弹起,它将再次上升,从墙壁上弹起并击中更多砖块

W
wizardforcel 已提交
897
因此,目标是通过摧毁所有积木而不让球进入球拍下来赢得比赛。
W
wizardforcel 已提交
898

W
wizardforcel 已提交
899
让我们开始实现一个深层的 Q 网络,以使我们的智能体学习 Atari Breakout 的游戏。 首先,我们将使用以下代码导入必要的库:
W
wizardforcel 已提交
900 901 902 903 904 905 906 907 908 909

```py
#Importing the dependencies

import numpy as np
import tensorflow as tf
import gym
from scipy.misc import imresize
```

W
wizardforcel 已提交
910
具有以下`main`函数的`class DQN`的架构可以使用以下代码定义:
W
wizardforcel 已提交
911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939

```py
class DQN:

    def __init__(self,learning_rate,gamma,n_features,n_actions,epsilon,parameter_changing_pointer,memory_size,epsilon_incrementer):
    ....
    def add_layer(self,inputs,w_shape=None,b_shape=None,layer=None,ctivation_fn=None,c=None,isconv=False:
    ....
    def weight_variable(self,w_shape,layer,c):
    ....
    def bias_variable(self,b_shape,layer,c):
    ....
    def conv(self,inputs,w):
    ....
    def build_networks(self):
    ....
    def target_params_replaced(self):
    ....
    def store_experience(self,obs,a,r,obs_):
    ....
    def fit(self):
    ....
    def epsilon_greedy(self,obs): 
    ....

if __name__ == "__main__":
    ....
```

W
wizardforcel 已提交
940
让我们讨论 DQN 类及其参数,它包含一个深度 Q 网络的架构:
W
wizardforcel 已提交
941

W
wizardforcel 已提交
942
*   `__init__(self,learning_rate,gamma,n_features,n_actions,epsilon,parameter_changing_pointer,memory_size)`:分配超参数的默认构造器,例如:
W
wizardforcel 已提交
943 944 945 946
    *   `learning_rate`
    *   `gamma`:即折扣系数
    *   `n_feature`:状态下的要素数量,即状态下的尺寸数
    *   `epsilon`:利用或探索行为的ε贪婪条件的阈值
W
wizardforcel 已提交
947
    *   `parameter_changing_pointer`:一个整数值(例如`n`),指定在每`n`次迭代之后,将主网络的参数复制到目标网络
W
wizardforcel 已提交
948
    *   `memory_size`:体验回复的最大长度
W
wizardforcel 已提交
949 950 951 952 953
*   `add_layer(self,inputs,w_shape=None,b_shape=None,layer=None,activation_fn=None,c=None,isconv=False)`:在神经网络中创建层的函数
*   `weight_variable(self,w_shape,layer,c)`:创建重量参数的函数
*   `bias_variable(self,b_shape,layer,c)`:创建偏置参数的函数
*   `conv(self,inputs,w)`:对图像帧执行卷积运算的函数
*   `build_networks()`:此函数用于使用 TensorFlow 创建主要网络和目标网络
W
wizardforcel 已提交
954 955 956 957 958
*   `target_params_replaced(self)`:用于将目标网络参数替换为主网络参数
*   `store_experience(self,obs,a,r,obs_)`:帮助存储经验,即(状态,动作,奖励,新状态)的元组
*   `fit(self)`:用于训练我们的深度 Q 网络
*   `epsilon_greedy(self,obs)`:它可以帮助我们针对给定的观察状态选择正确的操作,即按照现有策略利用操作或随机探索新操作

W
wizardforcel 已提交
959
现在,使用以下代码定义`__init__ default`构造器:
W
wizardforcel 已提交
960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988

```py
def __init__(self,learning_rate,gamma,n_features,n_actions,epsilon,parameter_changing_pointer,
             memory_size,epsilon_incrementer):

        tf.reset_default_graph()
        self.learning_rate = learning_rate
        self.gamma = gamma
        self.n_features = n_features
        self.n_actions = n_actions
        self.epsilon = epsilon
        self.batch_size = 32
        self.experience_counter = 0
        self.epsilon_incrementer = epsilon_incrementer
        self.experience_limit = memory_size
        self.replace_target_pointer = parameter_changing_pointer
        self.learning_counter = 0
        self.memory = [] #np.zeros([self.experience_limit,4]) #for experience replay

        self.build_networks()
        p_params = tf.get_collection('primary_network_parameters')
        t_params = tf.get_collection('target_network_parameters')
        self.replacing_target_parameters = [tf.assign(t,p) for t,p in zip(t_params,p_params)]

        self.sess = tf.Session()
        self.sess.run(tf.global_variables_initializer())

```

W
wizardforcel 已提交
989
以下代码定义了`add_layer`函数,该函数通过提供`isconv`的布尔参数来帮助根据卷积的要求创建不同的层或全连接层,如果`isconv``true`,则表示是卷积层:
W
wizardforcel 已提交
990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007

```py
def add_layer(self,inputs,w_shape=None,b_shape=None,layer=None,activation_fn=None,c=None,isconv=False):
        w = self.weight_variable(w_shape,layer,c)
        b = self.bias_variable(b_shape,layer,c)
        eps = tf.constant(value=0.000001, shape=b.shape)
        if isconv:
            if activation_fn is None:
                return self.conv(inputs,w)+b+eps
            else:
                h_conv = activation_fn(self.conv(inputs,w)+b+eps) 
                return h_conv
        if activation_fn is None:
            return tf.matmul(inputs,w)+b+eps
        outputs = activation_fn(tf.matmul(inputs,w)+b+eps)
        return outputs
```

W
wizardforcel 已提交
1008
接下来,我们具有`weight_variable``bias_variable`函数。 以下代码用于定义权重参数:
W
wizardforcel 已提交
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026

```py
def weight_variable(self,w_shape,layer,c):
        return tf.get_variable('w'+layer,w_shape,initializer=tf.contrib.layers.xavier_initializer(),
                                     dtype=tf.float32,collections=c)
```

定义偏置参数的代码:

```py
def bias_variable(self,b_shape,layer,c):
        return tf.get_variable('b'+layer,b_shape,initializer=tf.contrib.layers.xavier_initializer(),
                                     dtype=tf.float32,collections=c)
```

现在让我们定义`conv(self,inputs,w)`,该函数调用 TensorFlow 的`tf.nn.conv2d`函数并采用:

*   输入为二维向量
W
wizardforcel 已提交
1027 1028
*   **权重**:形状的重量`[patch_size,patch_size,input_vector_depth,output_vector_depth]`
*   **跨步**:以`[1,x_movement,y_movement,1]`形式出现的列表,其中:
W
wizardforcel 已提交
1029 1030
    *   `x_movement`:定义水平移动补丁的步数
    *   `y_movement`:定义在垂直方向上移动的色块的步数
W
wizardforcel 已提交
1031
*   **填充**`SAME``VALID`(我们在第 1 章,“深度学习–架构和框架”中讨论了此和有效的填充)
W
wizardforcel 已提交
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041

我们将使用以下代码定义函数:

```py
def conv(self,inputs,w):
        #strides [1,x_movement,y_movement,1]
        #stride[0] = stride[3] = 1
        return tf.nn.conv2d(inputs,w,strides=[1,1,1,1],padding='SAME') 
```

W
wizardforcel 已提交
1042
现在,让我们定义`build_networks(self)`。 它是在以下情况下建立主要和目标网络的函数:
W
wizardforcel 已提交
1043 1044 1045 1046 1047 1048

*`variable_scope`下创建主要网络参数,即:`primary_network`和集合`primary_network_parameters`
*`variable_scope`下创建目标网络参数,即:`target_network` 和集合`target_network_parameters`
*   两者具有相同的结构,即:
    *   卷积层 1
    *   卷积层 2
W
wizardforcel 已提交
1049 1050
    *   全连接层 1 
    *   全连接层 2 
W
wizardforcel 已提交
1051
    *   使用的激活函数:ReLU
W
wizardforcel 已提交
1052

W
wizardforcel 已提交
1053
该函数还有助于:
W
wizardforcel 已提交
1054

W
wizardforcel 已提交
1055
*   计算主网络输出的 Q 值与目标网络输出的 Q 值之间的损失
W
wizardforcel 已提交
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137

我们可以使用亚当优化器将这种损失降到最低。

现在,我们已经了解了该函数,让我们对其进行定义:

```py
def build_networks(self):
        #primary network
        shape = [None] + self.n_features
        self.s = tf.placeholder(tf.float32,shape)
        self.qtarget = tf.placeholder(tf.float32,[None,self.n_actions])

        with tf.variable_scope('primary_network'):
            c = ['primary_network_parameters', tf.GraphKeys.GLOBAL_VARIABLES]
            #first convolutional layer
            with tf.variable_scope('convlayer1'):
                l1 = self.add_layer(self.s,w_shape=[5,5,4,32],b_shape=[32],layer='convL1',activation_fn=tf.nn.relu,c=c,isconv=True)

            #first convolutional layer
            with tf.variable_scope('convlayer2'):
                l2 = self.add_layer(l1,w_shape=[5,5,32,64],b_shape=[64],layer='convL2',activation_fn=tf.nn.relu,c=c,isconv=True)

            #first fully-connected layer
            l2 = tf.reshape(l2,[-1,80*80*64])
            with tf.variable_scope('FClayer1'):
                l3 = self.add_layer(l2,w_shape=[80*80*64,128],b_shape=[128],layer='fclayer1',activation_fn=tf.nn.relu,c=c)

            #second fully-connected layer
            with tf.variable_scope('FClayer2'):
                self.qeval = self.add_layer(l3,w_shape=[128,self.n_actions],b_shape=[self.n_actions],layer='fclayer2',c=c)

        with tf.variable_scope('loss'):
                self.loss = tf.reduce_mean(tf.squared_difference(self.qtarget,self.qeval))

        with tf.variable_scope('optimizer'):
                self.train = tf.train.AdamOptimizer(self.learning_rate).minimize(self.loss)

        #target network
        self.st = tf.placeholder(tf.float32,shape)

        with tf.variable_scope('target_network'):
            c = ['target_network_parameters', tf.GraphKeys.GLOBAL_VARIABLES]
            #first convolutional layer
            with tf.variable_scope('convlayer1'):
                l1 = self.add_layer(self.st,w_shape=[5,5,4,32],b_shape=[32],layer='convL1',activation_fn=tf.nn.relu,c=c,isconv=True)

            #first convolutional layer
            with tf.variable_scope('convlayer2'):
                l2 = self.add_layer(l1,w_shape=[5,5,32,64],b_shape=[64],layer='convL2',activation_fn=tf.nn.relu,c=c,isconv=True)

            #first fully-connected layer
            l2 = tf.reshape(l2,[-1,80*80*64])
            with tf.variable_scope('FClayer1'):
                l3 = self.add_layer(l2,w_shape=[80*80*64,128],b_shape=[128],layer='fclayer1',activation_fn=tf.nn.relu,c=c)

            #second fully-connected layer
            with tf.variable_scope('FClayer2'):
                self.qt = self.add_layer(l3,w_shape=[128,self.n_actions],b_shape=[self.n_actions],layer='fclayer2',c=c)
```

现在,让我们定义`target_params_replaced(self)`,它是运行将主网络参数分配给目标网络参数的张量操作的函数:

```py
def target_params_replaced(self):
     self.sess.run(self.replacing_target_parameters)
```

现在,我们将定义`store_experience(self,obs,a,r,obs_)`函数,以将每种体验(即(状态,动作,奖励,新状态)的元组)存储在其体验缓冲区中,主要目标将在该缓冲区上进行训练:

```py
def store_experience(self,obs,a,r,obs_):
    if len(obs.shape)<3 or len(obs_.shape)<3: 
          print("Wrong shape entered : ",obs.shape,obs_.shape,len(self.memory))
    else:
          index = self.experience_counter % self.experience_limit
          if self.experience_counter < self.experience_limit:
                self.memory.append([obs,a,r,obs_])
          else:
                self.memory[index] = [obs,a,r,obs_]
    self.experience_counter+=1
```

W
wizardforcel 已提交
1138
现在,我们将定义`fit(self)`函数,以通过从经验缓冲区中选择一个批量来训练网络,计算`q_target`的张量值,然后最小化`qeval`之间的损失(即主数据库的输出) 网络)和`q_target`(即使用目标网络计算的 Q 值):
W
wizardforcel 已提交
1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168

```py
def fit(self):
        # sample batch memory from all memory
        indices = np.random.choice(len(self.memory), size=self.batch_size)
        batch = [self.memory[i] for i in indices]
        obs_nlist = np.array([i[3] for i in batch])
        obs_list = np.array([i[0] for i in batch])
        qt,qeval = self.sess.run([self.qt,self.qeval],feed_dict={self.st:obs_nlist,self.s:obs_list})

        qtarget = qeval.copy() 
        batch_indices = np.arange(self.batch_size, dtype=np.int32)
        actions = np.array([int(i[1]) for i in batch])
        rewards = np.array([int(i[2]) for i in batch])
        qtarget[batch_indices,actions] = rewards + self.gamma * np.max(qt,axis=1)

        _ = self.sess.run(self.train,feed_dict = {self.s:obs_list,self.qtarget:qtarget})
  print(self.learning_counter+1," learning done")
        #increasing epsilon 
        if self.epsilon < 0.9:
                self.epsilon += self.epsilon_incrementer

        #replacing target network parameters with primary network parameters 
        if self.learning_counter % self.replace_target_pointer == 0:
            self.target_params_replaced()
            print("target parameters changed")

        self.learning_counter += 1
```

W
wizardforcel 已提交
1169
现在,我们将定义`epsilon_greedy(self,obs)`,该函数类似于我们在 DQN 中为山地车和 Cartpole 实现的函数:
W
wizardforcel 已提交
1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199

```py
def epsilon_greedy(self,obs):
  new_shape = [1]+list(obs.shape)
  obs = obs.reshape(new_shape)
        #epsilon greedy implementation to choose action
        if np.random.uniform(low=0,high=1) < self.epsilon:
            return np.argmax(self.sess.run(self.qeval,feed_dict={self.s:obs})) #[np.newaxis,:]
        else:
            return np.random.choice(self.n_actions)
```

在类之外,我们有一个函数`preprocessing_image`,该函数用于预处理以下参数:

*   裁剪图像
*   将其转换为灰度
*   对图像进行下采样
*   归一化图像

我们将使用以下代码定义函数:

```py
def preprocessing_image(s):
    s = s[31:195]  #cropping
    s = s.mean(axis=2)  #converting to greyscale
    s = imresize(s,size=(80,80),interp='nearest')  #downsampling
    s = s/255.0  #normalizing
    return s 
```

W
wizardforcel 已提交
1200
以下代码定义了`main`函数,该函数创建 DQN 的上一类的对象,使用`gym`来获取`Breakout-v0`环境,并训练智能体程序解决问题:
W
wizardforcel 已提交
1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253

```py
if __name__ == "__main__":
    env = gym.make('Breakout-v0')
    env = env.unwrapped
    epsilon_rate_change = 0.9/500000.0
    dqn = DQN(learning_rate=0.0001,
              gamma=0.9,
              n_features=[80,80,4],
              n_actions=env.action_space.n,
              epsilon=0.0,
              parameter_changing_pointer=100,
              memory_size=50000,
              epsilon_incrementer=epsilon_rate_change)

    episodes = 100000
    total_steps = 0

    for episode in range(episodes):
        steps = 0

        obs = preprocessing_image(env.reset())
        s_rec = np.stack([obs]*4,axis=0)
        s = np.stack([obs]*4,axis=0)
        s = s.transpose([1,2,0])
        episode_reward = 0
        while True:
            env.render()
            action = dqn.epsilon_greedy(s)
            obs_,reward,terminate,_ = env.step(action)
            obs_ = preprocessing_image(obs_)

            a = s_rec[1:]
            a = a.tolist()
            a.append(obs_)
            s_rec = np.array(a)

            s_ = s_rec.transpose([1,2,0])
            dqn.store_experience(s,action,reward,s_)
            if total_steps > 1999 and total_steps%500==0:
                dqn.fit()
            episode_reward+=reward
            if terminate:
                break
            s = s_
            total_steps+=1
            steps+=1
        print("Episode {} with Reward : {} at epsilon {} in steps {}".format(episode+1,episode_reward,dqn.epsilon,steps))

    while True: #to hold the render at the last step when Car passes the flag
        env.render()
```

W
wizardforcel 已提交
1254
由于权重参数很多,因此在普通计算机上进行收敛需要花费大量时间,而运行 GPU 的计算机运行成本很高。 但是,要见证在普通计算机上融合的可能性,请运行代码 5 到 6 个小时,以查看智能体情况如何好转。 我建议,如果价格合理,请在带有 GPU 的计算机上运行它。 无论如何,前面的`main`函数的示例输出将如下所示:
W
wizardforcel 已提交
1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285

```py
....
....
....
Episode 992 with Reward : 0.0 at epsilon 0.0008766 in steps 174
Episode 993 with Reward : 2.0 at epsilon 0.0008766 in steps 319
(488, ' learning done')
Episode 994 with Reward : 0.0 at epsilon 0.0008784 in steps 169
Episode 995 with Reward : 1.0 at epsilon 0.0008784 in steps 228
Episode 996 with Reward : 1.0 at epsilon 0.0008784 in steps 239
(489, ' learning done')
Episode 997 with Reward : 4.0 at epsilon 0.0008802 in steps 401
(490, ' learning done')
Episode 998 with Reward : 0.0 at epsilon 0.000882 in steps 171
Episode 999 with Reward : 4.0 at epsilon 0.000882 in steps 360
(491, ' learning done')
Episode 1000 with Reward : 0.0 at epsilon 0.0008838 in steps 171
Episode 1001 with Reward : 1.0 at epsilon 0.0008838 in steps 238
(492, ' learning done')
Episode 1002 with Reward : 1.0 at epsilon 0.0008856 in steps 249
Episode 1003 with Reward : 1.0 at epsilon 0.0008856 in steps 232
....
....
....
```

尝试使用不同的参数以更好地收敛。

# 蒙特卡罗树搜索算法

W
wizardforcel 已提交
1286
**蒙特卡洛树搜索****MCTS**)是一种规划算法,是在出现人工窄智能问题时做出最佳决策的一种方法。 MCTS 致力于解决问题的预先规划方法。
W
wizardforcel 已提交
1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299

在诸如 **minimax****游戏树**之类的早期算法未能显示出具有复杂问题的结果之后,MCTS 算法变得越来越重要。 那么,是什么使 MCTS 与过去的决策算法(例如 minimax)不同并且更好呢?

让我们首先讨论什么是 minimax。

# Minimax 和游戏树

**Minimax** 是 IBM Deep Blue 在 1996 年 2 月 10 日的国际象棋比赛中击败世界冠军 Gary Kasparov 的算法。 那时的胜利是一个非常重要的里程碑。 minimax 和游戏树都是有向图,其中每个节点代表游戏状态,即游戏位置,如下面的井字游戏图所示:

![](img/1d4b1704-d9ef-4d48-aaff-79d8161cc9e5.png)

井字游戏树。 顶部节点代表游戏的开始位置。 沿着树走下去导致游戏的结果位置

W
wizardforcel 已提交
1300
因此,通过搜索游戏树,由于节点和树中存在的节点和路径的组合,AI 智能体可以选择最佳的移动方式。 这对于游戏复杂度处于可接受水平的问题很有用,因为随着复杂度的增加,游戏树的大小也会增加。 例如,国际象棋的游戏树具有比宇宙中原子更多的节点。 因此,在这种情况下,仅可能进行搜索。 因此,随着复杂度的增加,极大极小的可用性和游戏树减少。
W
wizardforcel 已提交
1301

W
wizardforcel 已提交
1302
另一个很好的例子是围棋的开放式中文游戏,其棋类复杂度为`10^360`,而棋类的复杂度为`10^123`。 由于具有如此高的复杂性,minimax 无法绘制评估函数,甚至无法创建如此大的游戏树。 因此,在《深蓝》取得成功的大约 20 年后,没有算法能够掌握围棋游戏。 原因很简单,当时的最新技术(例如 minimax)无法解决诸如围棋游戏之类具有很高复杂性的问题。 而且,解决围棋需要一种更人性化的学习方式,即基于交互。
W
wizardforcel 已提交
1303

W
wizardforcel 已提交
1304
因此,Google DeepMind 的 **AlphaGo** 被认为是最先进的 AI 智能体,它能够在 2016 年使用深度强化学习成功击败 Lee Sedol,该学习用于神经网络,强化学习和蒙特卡洛树搜索。 这是第一次以人类可以实现的方式完成 AI 任务,即通过不断的交互,并通过不断的反复试验过程来获取知识。
W
wizardforcel 已提交
1305 1306 1307

# 蒙特卡罗树搜索

W
wizardforcel 已提交
1308
那么,蒙特卡罗树搜索与 minimax 的方法有何不同?它如何在高度复杂的围棋游戏中进行提前计划,围棋有大量潜在的反制动作? MCTS 建立了一个看起来像游戏树的统计树,但是游戏树或 minimax 具有游戏位置,即有向图的节点中的游戏状态,在 MCTS 中,有向图的节点是该游戏的状态数量,告诉我们成功模拟的次数(即在游戏结束时导致获胜的动作)相对于游戏状态经过的模拟总数的数量。 因此,仿真次数越多,越多的节点有机会成为仿真的一部分,从而导致收敛。 因此,每个节点的值取决于仿真次数。
W
wizardforcel 已提交
1309

W
wizardforcel 已提交
1310
收敛后,此统计树将指导 AI 智能体在每个级别上寻找最佳可能节点,并继续进行直至达到目标。 最初,统计树用于扩展,以便通过多次仿真为可能的游戏状态添加更多节点。 收集足够数量的节点后,同时开始选择更好的节点,如果节点成功实现了问题目标,则每次仿真它们的值都会增加,从而提高了实用性。
W
wizardforcel 已提交
1311

W
wizardforcel 已提交
1312
在选择策略中,MCTS 还通过在现有的,有希望的节点与可能更有希望的未探索节点之间保持平衡来纳入探索与利用的权衡。 因此,仿真次数越多,统计树越大,则节点值收敛得越准确,最优决策就越好。
W
wizardforcel 已提交
1313 1314 1315 1316 1317

MCTS 是独立于域的,不需要复杂的手写试探法。 因此,它是解决各种开放式 AI 问题的强大算法。

# SARSA 算法

W
wizardforcel 已提交
1318
**状态-动作-奖励-状态-动作****SARSA**)算法是一种基于策略的学习问题。 就像 Q 学习一样,SARSA 也是一个时差学习问题,也就是说,它会预测剧集的下一步以估计未来的回报。 SARSA 和 Q 学习之间的主要区别在于,具有最大 Q 值的动作不用于更新当前状态动作对的 Q 值。 取而代之的是,选择作为当前策略结果的操作的 Q 值,或者由于采用诸如 ε 贪婪之类的探索步骤来更新当前状态操作对的 Q 值。 SARSA 之所以得名,是因为使用五元组`Q(s, a, r, s', a')`完成了 Q 值更新,其中:
W
wizardforcel 已提交
1319

W
wizardforcel 已提交
1320
*   `s``a`:当前状态和操作
W
wizardforcel 已提交
1321
*   `r`:采取行动后观察到的奖励`a`
W
wizardforcel 已提交
1322 1323
*   `s'`:在采取操作`a`后到达下一个状态
*   `a'`:要在状态`s'`下执行的动作
W
wizardforcel 已提交
1324 1325 1326 1327 1328

SARSA 算法涉及的步骤如下:

1.  随机初始化 Q 表

W
wizardforcel 已提交
1329
2.  对于每个剧集:
W
wizardforcel 已提交
1330

W
wizardforcel 已提交
1331
    1.  对于给定状态`s`,请从 Q 表中选择操作`a`
W
wizardforcel 已提交
1332

W
wizardforcel 已提交
1333
    2.  执行动作`a`
W
wizardforcel 已提交
1334

W
wizardforcel 已提交
1335
    3.  奖励`R`和新状态`s'`
W
wizardforcel 已提交
1336

W
wizardforcel 已提交
1337
    4.  对于新状态`s'`,请从 Q 表中选择操作`a'`
W
wizardforcel 已提交
1338

W
wizardforcel 已提交
1339
    5.  通过以下操作更新当前状态操作的 Q 值,即`Q(s, a)`对:
W
wizardforcel 已提交
1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363

![](img/356ee392-4f51-400f-a5af-90f2b50ee30c.png)

SARSA 算法的伪代码如下:

```py
Create Q-table where rows represent different states and columns represent different actions

Initialize Q(s,a) arbitrarily, e.g. 0 for all states
set action value for terminal states as 0

For each episode:
    Start with the starting state that is Initialize s to start
    Choose action a for s using the policy derived from Q [e.g. -greedy, either for the given 's' which 'a' has the max Q-value or choose a random action]
    Repeat for each step in the episode:
        Take the chosen action a, observe reward R and new state s'
        Choose action a' for s' using the policy derived from Q 
        [e.g. -greedy]
        Update 

    until s is the terminal state
end
```

W
wizardforcel 已提交
1364
# 用于 OpenAI Gym 山地车问题的 SARSA 算法
W
wizardforcel 已提交
1365

W
wizardforcel 已提交
1366
让我们尝试实现先前在山地车问题中解释过的 SARSA 算法。 该程序的初始部分与先前的 Q 学习器具有相似之处。
W
wizardforcel 已提交
1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398

首先,我们将使用以下代码导入依赖项并检查山地车环境:

```py
#importing the dependencies

import gym
import numpy as np

#exploring Mountain Car environment

env_name = 'MountainCar-v0'
env = gym.make(env_name)

print("Action Set size :",env.action_space)
print("Observation set shape :",env.observation_space) 
print("Highest state feature value :",env.observation_space.high) 
print("Lowest state feature value:",env.observation_space.low) 
print(env.observation_space.shape) 
```

前面的打印语句输出以下内容:

```py
Making new env: MountainCar-v0
('Action Set size :', Discrete(3))
('Observation set shape :', Box(2,))
('Highest state feature value :', array([ 0.6 , 0.07]))
('Lowest state feature value:', array([-1.2 , -0.07]))
(2,)
```

W
wizardforcel 已提交
1399
接下来,我们将使用以下代码分配超参数,例如状态数,剧集数,学习率(初始和最小值),折扣因子伽玛,剧集中的最大步长以及 ε 贪婪的`ε`
W
wizardforcel 已提交
1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504

```py
n_states = 40  # number of states
episodes = 10 # number of episodes

initial_lr = 1.0 # initial learning rate
min_lr = 0.005 # minimum learning rate
gamma = 0.99 # discount factor
max_steps = 300
epsilon = 0.05

env = env.unwrapped
env.seed(0)         #setting environment seed to reproduce same result
np.random.seed(0)   #setting numpy random number generation seed to reproduce same random numbers
```

我们的下一个任务是创建一个函数,对连续状态空间进行离散化。 离散化是将连续状态空间观察转换为离散状态空间集。 我们将使用以下代码执行离散化:

```py
def discretization(env, obs):

    env_low = env.observation_space.low
    env_high = env.observation_space.high

    env_den = (env_high - env_low) / n_states
    pos_den = env_den[0]
    vel_den = env_den[1]

    pos_high = env_high[0]
    pos_low = env_low[0]
    vel_high = env_high[1]
    vel_low = env_low[1]

    pos_scaled = int((obs[0] - pos_low)/pos_den)  #converts to an integer value
    vel_scaled = int((obs[1] - vel_low)/vel_den)  #converts to an integer value

    return pos_scaled,vel_scaled
```

到目前为止,每个任务都与我们在 Q 学习算法中所做的相似。 现在,SARSA 的实现从初始化 Q 表并相应地更新 Q 值开始,如以下代码所示。 同样,在这里,我们将奖励值更新为当前位置与最低点(即起点)之间的绝对差值,以便它通过远离中心即最低点来最大化奖励:

```py
#Q table
#rows are states but here state is 2-D pos,vel
#columns are actions
#therefore, Q- table would be 3-D

q_table = np.zeros((n_states,n_states,env.action_space.n))
total_steps = 0
for episode in range(episodes):
   obs = env.reset()
   total_reward = 0
   # decreasing learning rate alpha over time
   alpha = max(min_lr,initial_lr*(gamma**(episode//100)))
   steps = 0

   #action for the initial state using epsilon greedy
   if np.random.uniform(low=0,high=1) < epsilon:
        a = np.random.choice(env.action_space.n)
   else:
        pos,vel = discretization(env,obs)
        a = np.argmax(q_table[pos][vel])

   while True:
      env.render()
      pos,vel = discretization(env,obs)

      obs,reward,terminate,_ = env.step(a) 
      total_reward += abs(obs[0]+0.5) 
      pos_,vel_ = discretization(env,obs)

      #action for the next state using epsilon greedy
      if np.random.uniform(low=0,high=1) < epsilon:
          a_ = np.random.choice(env.action_space.n)
      else:
          a_ = np.argmax(q_table[pos_][vel_])

      #q-table update
      q_table[pos][vel][a] = (1-alpha)*q_table[pos][vel][a] + alpha*(reward+gamma*q_table[pos_][vel_][a_])
      steps+=1
      if terminate:
          break
      a = a_
   print("Episode {} completed with total reward {} in {} steps".format(episode+1,total_reward,steps)) 
while True: #to hold the render at the last step when Car passes the flag
   env.render()  
```

前面的程序将以以下方式打印:

```py
Episode 1 completed with total reward 11167.6296185 in 36605 steps
Episode 2 completed with total reward 830.204697241 in 2213 steps
Episode 3 completed with total reward 448.46977318 in 1899 steps
Episode 4 completed with total reward 930.154976751 in 3540 steps
Episode 5 completed with total reward 6864.96292351 in 20871 steps
Episode 6 completed with total reward 677.449030827 in 3995 steps
Episode 7 completed with total reward 2994.99152751 in 7401 steps
Episode 8 completed with total reward 724.212076546 in 3267 steps
Episode 9 completed with total reward 192.502071909 in 928 steps
Episode 10 completed with total reward 213.212231118 in 786 steps
```

因此,我们已经能够成功实现山地车问题的 SARSA 算法。

W
wizardforcel 已提交
1505
# 总结
W
wizardforcel 已提交
1506

W
wizardforcel 已提交
1507
我们知道强化学习可以优化环境中智能体的回报,**马尔可夫决策过程****MDP**)是一种环境表示和数学框架,用于使用状态对决策进行建模 ,动作和奖励。 在本章中,我们了解到 Q 学习是一种无需任何转移模型即可为任何 MDP 找到最佳动作选择策略的方法。 另一方面,如果给出了转换模型,则值迭代会为任何 MDP 找到最佳的动作选择策略。
W
wizardforcel 已提交
1508

W
wizardforcel 已提交
1509
我们还学习了另一个重要的话题,称为深度 Q 网络,这是一种经过改进的 Q 学习方法,它采用深度神经网络作为函数逼近器来在不同环境中进行泛化,这与特定于环境的 Q 表不同。 此外,我们还学会了在 OpenAI Gym 环境中实现 Q 学习,深度 Q 网络和 SARSA 算法。 先前显示的大多数实现可能在具有更好的超参数值和更多训练集的情况下效果更好。
W
wizardforcel 已提交
1510 1511

在下一章中,我们将详细介绍著名的异步优势参与者批评算法。