09.md 28.4 KB
Newer Older
W
wizardforcel 已提交
1 2
# 用深度循环 Q 网络玩世界末日

W
wizardforcel 已提交
3
在上一章中,我们介绍了如何使用**深度 Q 网络****DQN**)构建代理以玩 Atari 游戏。 我们利用神经网络来近似 Q 函数,使用了**卷积神经网络****CNN**)来理解输入游戏画面,并利用过去的四个游戏画面来更好地理解当前的游戏状态。 在本章中,我们将学习如何利用**循环神经网络****RNN**)来提高 DQN 的性能。 我们还将研究**马尔可夫决策过程****MDP**)的部分可观察之处,以及如何使用**深度循环 Q 网络****DRQN**)。 接下来,我们将学习如何构建一个代理来使用 DRQN 玩《毁灭战士》游戏。 最后,我们将看到 DRQN 的一种变体,称为**深度注意力循环 Q 网络****DARQN**),它增强了 DRQN 架构的注意力机制。
W
wizardforcel 已提交
4 5 6 7 8

在本章中,您将学习以下主题:

*   DRQN
*   部分可观察的 MDP
W
wizardforcel 已提交
9
*   DRQN 的架构
W
wizardforcel 已提交
10 11 12 13 14
*   如何建立代理以使用 DRQN 玩《毁灭战士》游戏
*   DARQN

# DRQN

W
wizardforcel 已提交
15
那么,当我们在 Atari 游戏中以人为水平执行 DQN 时,为什么我们需要 DRQN? 为了回答这个问题,让我们理解**部分可观察的马尔可夫决策过程****POMDP**)的问题。 当我们可获得的关于环境的信息有限时,该环境称为部分可观察的 MDP。 到目前为止,在前面的章节中,我们已经看到了一个完全可观察的 MDP,在其中我们了解所有可能的动作和状态-尽管该代理可能不知道转移和奖励的可能性,但它对环境有完整的了解,例如,冰冻的湖泊环境,我们清楚地知道了环境的所有状态和行为; 我们轻松地将该环境建模为一个完全可观察的 MDP。 但是大多数现实世界环境只能部分观察到。 我们看不到所有状态。 考虑代理学习如何在现实环境中行走; 显然,代理将不会完全了解环境,它将无法获得任何信息。 在 POMDP 中,状态仅提供部分信息,但是将有关过去状态的信息保留在内存中可能会帮助代理更好地了解环境的性质并改善策略。 因此,在 POMDP 中,我们需要保留有关先前状态的信息,以便采取最佳措施。
W
wizardforcel 已提交
16

W
wizardforcel 已提交
17
为了回顾我们在前几章中学到的知识,请考虑以下所示的乒乓游戏。 仅通过查看当前的游戏屏幕,我们就可以知道球的位置,但是我们还需要知道球的运动方向和球的速度,以便采取最佳行动。 但是,仅查看当前的游戏屏幕并不能告诉我们球的方向和速度:
W
wizardforcel 已提交
18 19 20

![](img/00287.jpeg)

W
wizardforcel 已提交
21
为了克服这个问题,我们将不仅仅考虑当前的游戏屏幕,而将使用过去的四个游戏屏幕来了解球的方向和速度。 这就是我们在 DQN 中看到的。 我们将过去的四个游戏屏幕以及当前的游戏屏幕作为输入输入到卷积层,并接收该状态下所有可能动作的 Q 值。 但是,您认为仅使用过去的四个屏幕将有助于我们了解不同的环境吗? 在某些环境下,我们甚至可能需要过去的 100 个游戏屏幕来更好地了解当前游戏状态。 但是,堆叠过去的`n`游戏画面会减慢我们的训练过程,而且还会增加我们的经验回放缓冲区的大小。
W
wizardforcel 已提交
22

W
wizardforcel 已提交
23
因此,只要需要,我们就可以利用 RNN 的优势来理解和保留有关先前状态的信息。 在第 7 章“深度学习基础知识”中,我们了解了如何通过保留,忘记和更新所需的信息,将**长短期记忆循环神经网络****LSTM RNN**)用于生成文本以及了解文本的上下文。 我们将通过扩展 LSTM 层来修改 DQN 架构,以了解先前的信息。 在 DQN 架构中,我们用 LSTM RNN 替换了第一卷积后全连接层。 这样,我们也可以解决部分可观察性的问题,因为现在我们的代理可以记住过去的状态并可以改进策略。
W
wizardforcel 已提交
24

W
wizardforcel 已提交
25
# DRQN 的架构
W
wizardforcel 已提交
26

W
wizardforcel 已提交
27
接下来显示 DRQN 的架构。 它类似于 DQN,但是我们用 LSTM RNN 替换了第一卷积后全连接层,如下所示:
W
wizardforcel 已提交
28 29 30

![](img/00288.gif)

W
wizardforcel 已提交
31
因此,我们将游戏屏幕作为卷积层的输入。 卷积层对图像进行卷积并生成特征映射。 然后将生成的特征映射传递到 LSTM 层。 LSTM 层具有用于保存信息的内存。 LSTM 层保留有关重要的先前游戏状态的信息,并根据需要随时间步长更新其内存。 穿过全连接层后,它将输出`Q`值。 因此,与 DQN 不同,我们不直接估计`Q(s[t], a[t])`。 相反,我们估算`Q(h[t], a[t])`,其中`h[t]`是网络在上一个时间步长返回的输入。 即,`h[t] = LSTM(h[t-1], o[t])`。 当我们使用 RNN 时,我们通过时间的反向传播来训练我们的网络。
W
wizardforcel 已提交
32

W
wizardforcel 已提交
33
等待。 经验回放缓冲区如何? 在 DQN 中,为避免相关的经验,我们使用了经验回放,该经验存储了游戏的转移,并使用了随机的一组经验来训练网络。 对于 DRQN,我们将整个情节存储在经验缓冲区中,并从随机的情节批次中随机采样`n`个步骤。 因此,通过这种方式,我们既可以适应随机化,又可以适应另一种实际的经验。
W
wizardforcel 已提交
34 35 36

# 训练特工玩《毁灭战士》

W
wizardforcel 已提交
37
毁灭战士是一款非常受欢迎的第一人称射击游戏。 游戏的目标是杀死怪物。 末日是部分可观察的 MDP 的另一个示例,因为智能体(玩家)的视角限制为 90 度。 该代理对其余环境一无所知。 现在,我们将看到如何使用 DRQN 来训练我们的经纪人玩《毁灭战士》。
W
wizardforcel 已提交
38

W
wizardforcel 已提交
39
代替 OpenAI Gym,我们将使用 ViZDoom 软件包来模拟 Doom 环境以训练我们的代理。 要了解有关 ViZDoom 软件包的更多信息,[请访问其官方网站](http://vizdoom.cs.put.edu.pl/)。 我们可以使用以下命令简单地安装 ViZDoom:
W
wizardforcel 已提交
40 41 42 43 44 45 46

```py
pip install vizdoom
```

ViZDoom 提供了许多 Doom 方案,可以在软件包文件夹`vizdoom/scenarios`中找到这些方案。

W
wizardforcel 已提交
47
# 基本 Doom 游戏
W
wizardforcel 已提交
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121

在开始之前,让我们通过看一个基本示例来熟悉`vizdoom`环境:

1.  让我们加载必要的库:

```py
from vizdoom import *
import random
import time
```

2.`DoomGame`创建一个实例:

```py
game = DoomGame() 
```

3.  众所周知,ViZDoom 提供了很多 Doom 方案,让我们加载基本方案:

```py
game.load_config("basic.cfg")
```

4.  `init()`方法使用场景初始化游戏:

```py
game.init()
```

5.  现在,让我们定义一个带有热编码的`actions`的代码:

```py
shoot = [0, 0, 1]
left = [1, 0, 0]
right = [0, 1, 0]
actions = [shoot, left, right]
```

6.  现在,让我们开始玩游戏:

```py
no_of_episodes = 10

for i in range(no_of_episodes): 

    # for each episode start the game
    game.new_episode()

    # loop until the episode is over
    while not game.is_episode_finished():

        # get the game state
        state = game.get_state()
        img = state.screen_buffer

        # get the game variables
        misc = state.game_variables

```

```py
        # perform some action randomly and receive reward
        reward = game.make_action(random.choice(actions))

        print(reward)

    # we will set some time before starting the next episode
    time.sleep(2)
```

运行程序后,可以看到如下输出:

![](img/00289.jpeg)

W
wizardforcel 已提交
122
# DRQN 的 Doom
W
wizardforcel 已提交
123

W
wizardforcel 已提交
124
现在,让我们看看如何利用 DRQN 算法来训练我们的特工玩《毁灭战士》。 我们为成功杀死怪物分配正面奖励,为失去生命,自杀和失去弹药(子弹)分配负面奖励。 您可以在[这里](https://github.com/sudharsan13296/Hands-On-Reinforcement-Learning-With-Python/blob/master/09.%20Playing%20Doom%20Game%20using%20DRQN/9.5%20Doom%20Game%20Using%20DRQN.ipynb)获得 Jupyter 笔记本的完整代码及其解释。 本节中使用的代码的权利归于 [Luthanicus](https://github.com/Luthanicus/losaltoshackathon-drqn)
W
wizardforcel 已提交
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 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 229 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 269 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 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324

首先,让我们导入所有必需的库:

```py
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from vizdoom import *
import timeit
import math
import os
import sys
```

现在,让我们定义`get_input_shape`函数,以在卷积层卷积后计算输入图像的最终形状:

```py
def get_input_shape(Image,Filter,Stride):
    layer1 = math.ceil(((Image - Filter + 1) / Stride))

    o1 = math.ceil((layer1 / Stride))

    layer2 = math.ceil(((o1 - Filter + 1) / Stride))

    o2 = math.ceil((layer2 / Stride))

    layer3 = math.ceil(((o2 - Filter + 1) / Stride))

    o3 = math.ceil((layer3 / Stride))

    return int(o3)
```

现在,我们将定义`DRQN`类,该类实现了 DRQN 算法。 检查每行代码之前的注释以了解它:

```py
class DRQN():
    def __init__(self, input_shape, num_actions, initial_learning_rate):

        # first, we initialize all the hyperparameters

        self.tfcast_type = tf.float32

        # shape of our input, which would be (length, width, channels)
        self.input_shape = input_shape 

        # number of actions in the environment
        self.num_actions = num_actions

        # learning rate for the neural network
        self.learning_rate = initial_learning_rate

        # now we will define the hyperparameters of the convolutional neural network 

        # filter size
        self.filter_size = 5

        # number of filters
        self.num_filters = [16, 32, 64]

        # stride size
        self.stride = 2

        # pool size
        self.poolsize = 2 

        # shape of our convolutional layer
        self.convolution_shape = get_input_shape(input_shape[0], self.filter_size, self.stride) * get_input_shape(input_shape[1], self.filter_size, self.stride) * self.num_filters[2]

        # now, we define the hyperparameters of our recurrent neural network and the final feed forward layer

        # number of neurons 
        self.cell_size = 100

        # number of hidden layers
        self.hidden_layer = 50

        # drop out probability
        self.dropout_probability = [0.3, 0.2]

        # hyperparameters for optimization
        self.loss_decay_rate = 0.96
        self.loss_decay_steps = 180

        # initialize all the variables for the CNN

        # we initialize the placeholder for input whose shape would be (length, width, channel)
        self.input = tf.placeholder(shape = (self.input_shape[0], self.input_shape[1], self.input_shape[2]), dtype = self.tfcast_type)

        # we will also initialize the shape of the target vector whose shape is equal to the number of actions
        self.target_vector = tf.placeholder(shape = (self.num_actions, 1), dtype = self.tfcast_type)

        # initialize feature maps for our corresponding 3 filters
        self.features1 = tf.Variable(initial_value = np.random.rand(self.filter_size, self.filter_size, input_shape[2], self.num_filters[0]),
                                     dtype = self.tfcast_type)

        self.features2 = tf.Variable(initial_value = np.random.rand(self.filter_size, self.filter_size, self.num_filters[0], self.num_filters[1]),
                                     dtype = self.tfcast_type)

        self.features3 = tf.Variable(initial_value = np.random.rand(self.filter_size, self.filter_size, self.num_filters[1], self.num_filters[2]),
                                     dtype = self.tfcast_type)

        # initialize variables for RNN
        # recall how RNN works from chapter 7

        self.h = tf.Variable(initial_value = np.zeros((1, self.cell_size)), dtype = self.tfcast_type)

        # hidden to hidden weight matrix
        self.rW = tf.Variable(initial_value = np.random.uniform(
                                            low = -np.sqrt(6\. / (self.convolution_shape + self.cell_size)),
                                            high = np.sqrt(6\. / (self.convolution_shape + self.cell_size)),
                                            size = (self.convolution_shape, self.cell_size)),
                              dtype = self.tfcast_type)

        # input to hidden weight matrix
        self.rU = tf.Variable(initial_value = np.random.uniform(
                                            low = -np.sqrt(6\. / (2 * self.cell_size)),
                                            high = np.sqrt(6\. / (2 * self.cell_size)),
                                            size = (self.cell_size, self.cell_size)),
                              dtype = self.tfcast_type)

        # hidden to output weight matrix

        self.rV = tf.Variable(initial_value = np.random.uniform(
                                            low = -np.sqrt(6\. / (2 * self.cell_size)),
                                            high = np.sqrt(6\. / (2 * self.cell_size)),
                                            size = (self.cell_size, self.cell_size)),
                              dtype = self.tfcast_type)
        # bias
        self.rb = tf.Variable(initial_value = np.zeros(self.cell_size), dtype = self.tfcast_type)
        self.rc = tf.Variable(initial_value = np.zeros(self.cell_size), dtype = self.tfcast_type)

        # initialize weights and bias of feed forward network

        # weights
        self.fW = tf.Variable(initial_value = np.random.uniform(
                                            low = -np.sqrt(6\. / (self.cell_size + self.num_actions)),
                                            high = np.sqrt(6\. / (self.cell_size + self.num_actions)),
                                            size = (self.cell_size, self.num_actions)),
                              dtype = self.tfcast_type)

        # bias
        self.fb = tf.Variable(initial_value = np.zeros(self.num_actions), dtype = self.tfcast_type)

        # learning rate
        self.step_count = tf.Variable(initial_value = 0, dtype = self.tfcast_type)
        self.learning_rate = tf.train.exponential_decay(self.learning_rate, 
                                                   self.step_count,
                                                   self.loss_decay_steps,
                                                   self.loss_decay_steps,
                                                   staircase = False)

        # now let us build the network

        # first convolutional layer
        self.conv1 = tf.nn.conv2d(input = tf.reshape(self.input, shape = (1, self.input_shape[0], self.input_shape[1], self.input_shape[2])), filter = self.features1, strides = [1, self.stride, self.stride, 1], padding = "VALID")
        self.relu1 = tf.nn.relu(self.conv1)
        self.pool1 = tf.nn.max_pool(self.relu1, ksize = [1, self.poolsize, self.poolsize, 1], strides = [1, self.stride, self.stride, 1], padding = "SAME")

        # second convolutional layer
        self.conv2 = tf.nn.conv2d(input = self.pool1, filter = self.features2, strides = [1, self.stride, self.stride, 1], padding = "VALID")
        self.relu2 = tf.nn.relu(self.conv2)
        self.pool2 = tf.nn.max_pool(self.relu2, ksize = [1, self.poolsize, self.poolsize, 1], strides = [1, self.stride, self.stride, 1], padding = "SAME")

        # third convolutional layer
        self.conv3 = tf.nn.conv2d(input = self.pool2, filter = self.features3, strides = [1, self.stride, self.stride, 1], padding = "VALID")
        self.relu3 = tf.nn.relu(self.conv3)
        self.pool3 = tf.nn.max_pool(self.relu3, ksize = [1, self.poolsize, self.poolsize, 1], strides = [1, self.stride, self.stride, 1], padding = "SAME")

        # add dropout and reshape the input
        self.drop1 = tf.nn.dropout(self.pool3, self.dropout_probability[0])
        self.reshaped_input = tf.reshape(self.drop1, shape = [1, -1])

        # now we build the recurrent neural network, which takes the input from the last layer of the convolutional network
        self.h = tf.tanh(tf.matmul(self.reshaped_input, self.rW) + tf.matmul(self.h, self.rU) + self.rb)
        self.o = tf.nn.softmax(tf.matmul(self.h, self.rV) + self.rc)

        # add drop out to RNN
        self.drop2 = tf.nn.dropout(self.o, self.dropout_probability[1])

        # we feed the result of RNN to the feed forward layer
        self.output = tf.reshape(tf.matmul(self.drop2, self.fW) + self.fb, shape = [-1, 1])
        self.prediction = tf.argmax(self.output)

        # compute loss
        self.loss = tf.reduce_mean(tf.square(self.target_vector - self.output))

        # we use Adam optimizer for minimizing the error
        self.optimizer = tf.train.AdamOptimizer(self.learning_rate)

        # compute gradients of the loss and update the gradients
        self.gradients = self.optimizer.compute_gradients(self.loss)
        self.update = self.optimizer.apply_gradients(self.gradients)

        self.parameters = (self.features1, self.features2, self.features3,
                           self.rW, self.rU, self.rV, self.rb, self.rc,
                           self.fW, self.fb)

```

W
wizardforcel 已提交
325
现在,我们定义`ExperienceReplay`类以实现经验回放缓冲区。 我们将智能体的所有经验(即状态,动作和奖励)存储在经验回放缓冲区中,并且我们抽取了这一小批量经验来训练网络:
W
wizardforcel 已提交
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 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 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 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 506 507 508 509 510 511 512 513 514 515 516 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

```py
class ExperienceReplay():
    def __init__(self, buffer_size):

        # buffer for holding the transition 
        self.buffer = [] 

        # size of the buffer
        self.buffer_size = buffer_size

    # we remove the old transition if the buffer size has reached it's limit. Think off the buffer as a queue, when the new
    # one comes, the old one goes off

    def appendToBuffer(self, memory_tuplet):
        if len(self.buffer) > self.buffer_size: 
            for i in range(len(self.buffer) - self.buffer_size):
                self.buffer.remove(self.buffer[0]) 
        self.buffer.append(memory_tuplet) 

    # define a function called sample for sampling some random n number of transitions 

    def sample(self, n):
        memories = []

        for i in range(n):
            memory_index = np.random.randint(0, len(self.buffer)) 
            memories.append(self.buffer[memory_index])
        return memories
```

现在,我们定义`train`函数来训练我们的网络:

```py
def train(num_episodes, episode_length, learning_rate, scenario = "deathmatch.cfg", map_path = 'map02', render = False):

    # discount parameter for Q-value computation
    discount_factor = .99

    # frequency for updating the experience in the buffer
    update_frequency = 5
    store_frequency = 50

    # for printing the output
    print_frequency = 1000

    # initialize variables for storing total rewards and total loss
    total_reward = 0
    total_loss = 0
    old_q_value = 0

    # initialize lists for storing the episodic rewards and losses 
    rewards = []
    losses = []

    # okay, now let us get to the action!

    # first, we initialize our doomgame environment
    game = DoomGame()

    # specify the path where our scenario file is located
    game.set_doom_scenario_path(scenario)

    # specify the path of map file
    game.set_doom_map(map_path)

    # then we set screen resolution and screen format
    game.set_screen_resolution(ScreenResolution.RES_256X160) 
    game.set_screen_format(ScreenFormat.RGB24)

    # we can add particles and effects we needed by simply setting them to true or false
    game.set_render_hud(False)
    game.set_render_minimal_hud(False)
    game.set_render_crosshair(False)
    game.set_render_weapon(True)
    game.set_render_decals(False)
    game.set_render_particles(False)
    game.set_render_effects_sprites(False)
    game.set_render_messages(False)
    game.set_render_corpses(False)
    game.set_render_screen_flashes(True)

    # now we will specify buttons that should be available to the agent
    game.add_available_button(Button.MOVE_LEFT)
    game.add_available_button(Button.MOVE_RIGHT)
    game.add_available_button(Button.TURN_LEFT)
    game.add_available_button(Button.TURN_RIGHT)
    game.add_available_button(Button.MOVE_FORWARD)
    game.add_available_button(Button.MOVE_BACKWARD)
    game.add_available_button(Button.ATTACK)

    # okay, now we will add one more button called delta. The preceding button will only 
    # work like keyboard keys and will have only boolean values.

    # so we use delta button, which emulates a mouse device which will have positive and negative values
    # and it will be useful in environment for exploring

    game.add_available_button(Button.TURN_LEFT_RIGHT_DELTA, 90)
    game.add_available_button(Button.LOOK_UP_DOWN_DELTA, 90)

    # initialize an array for actions
    actions = np.zeros((game.get_available_buttons_size(), game.get_available_buttons_size()))
    count = 0
    for i in actions:
        i[count] = 1
        count += 1
    actions = actions.astype(int).tolist()

    # then we add the game variables, ammo, health, and killcount
    game.add_available_game_variable(GameVariable.AMMO0)
    game.add_available_game_variable(GameVariable.HEALTH)
    game.add_available_game_variable(GameVariable.KILLCOUNT)

    # we set episode_timeout to terminate the episode after some time step
    # we also set episode_start_time which is useful for skipping initial events

    game.set_episode_timeout(6 * episode_length)
    game.set_episode_start_time(10)
    game.set_window_visible(render)

    # we can also enable sound by setting set_sound_enable to true
    game.set_sound_enabled(False)

    # we set living reward to 0, which rewards the agent for each move it does even though the move is not useful
    game.set_living_reward(0)

    # doom has different modes such as player, spectator, asynchronous player, and asynchronous spectator

    # in spectator mode humans will play and agent will learn from it.
    # in player mode, the agent actually plays the game, so we use player mode.

    game.set_mode(Mode.PLAYER)

    # okay, So now we, initialize the game environment
    game.init()

    # now, let us create instance to our DRQN class and create our both actor and target DRQN networks
    actionDRQN = DRQN((160, 256, 3), game.get_available_buttons_size() - 2, learning_rate)
    targetDRQN = DRQN((160, 256, 3), game.get_available_buttons_size() - 2, learning_rate)

    # we will also create an instance to the ExperienceReplay class with the buffer size of 1000
    experiences = ExperienceReplay(1000)

    # for storing the models
    saver = tf.train.Saver({v.name: v for v in actionDRQN.parameters}, max_to_keep = 1)

    # now let us start the training process
    # we initialize variables for sampling and storing transitions from the experience buffer
    sample = 5
    store = 50

    # start the tensorflow session
    with tf.Session() as sess:

        # initialize all tensorflow variables

        sess.run(tf.global_variables_initializer())

        for episode in range(num_episodes):

            # start the new episode
            game.new_episode()

            # play the episode till it reaches the episode length
            for frame in range(episode_length):

                # get the game state
                state = game.get_state()
                s = state.screen_buffer

                # select the action
                a = actionDRQN.prediction.eval(feed_dict = {actionDRQN.input: s})[0]
                action = actions[a]

                # perform the action and store the reward
                reward = game.make_action(action)

                # update total reward
                total_reward += reward

                # if the episode is over then break
                if game.is_episode_finished():
                    break

                # store the transition to our experience buffer
                if (frame % store) == 0:
                    experiences.appendToBuffer((s, action, reward))

                # sample experience from the experience buffer 
                if (frame % sample) == 0:
                    memory = experiences.sample(1)
                    mem_frame = memory[0][0]
                    mem_reward = memory[0][2]

                    # now, train the network
                    Q1 = actionDRQN.output.eval(feed_dict = {actionDRQN.input: mem_frame})
                    Q2 = targetDRQN.output.eval(feed_dict = {targetDRQN.input: mem_frame})

                    # set learning rate
                    learning_rate = actionDRQN.learning_rate.eval()

                    # calculate Q value
                    Qtarget = old_q_value + learning_rate * (mem_reward + discount_factor * Q2 - old_q_value) 

                    # update old Q value
                    old_q_value = Qtarget

                    # compute Loss
                    loss = actionDRQN.loss.eval(feed_dict = {actionDRQN.target_vector: Qtarget, actionDRQN.input: mem_frame})

                    # update total loss
                    total_loss += loss

                    # update both networks
                    actionDRQN.update.run(feed_dict = {actionDRQN.target_vector: Qtarget, actionDRQN.input: mem_frame})
                    targetDRQN.update.run(feed_dict = {targetDRQN.target_vector: Qtarget, targetDRQN.input: mem_frame})

            rewards.append((episode, total_reward))
            losses.append((episode, total_loss))

            print("Episode %d - Reward = %.3f, Loss = %.3f." % (episode, total_reward, total_loss))

            total_reward = 0
            total_loss = 0
```

让我们训练`10000`剧集,其中每个剧集的长度为`300`

```py

train(num_episodes = 10000, episode_length = 300, learning_rate = 0.01, render = True)
```

运行该程序时,可以看到如下所示的输出,还可以看到我们的特工如何通过情节学习:

![](img/00290.jpeg)

# DARQN

W
wizardforcel 已提交
565
我们通过添加可捕获时间依赖性的循环层来改进 DQN 架构,我们将其称为 DRQN。 您认为我们可以进一步改善我们的 DRQN 架构吗? 是。 通过在卷积层之上添加关注层,我们可以进一步改善我们的 DRQN 架构。 那么,关注层的功能是什么? 注意意味着单词的字面意思。 注意机制广泛用于图像字幕,对象检测等。 考虑神经网络为图像添加字幕的任务; 为了理解图像中的内容,网络必须注意图像中的特定对象以生成字幕。
W
wizardforcel 已提交
566 567 568

类似地,当我们在 DRQN 中添加关注层时,我们可以选择并关注图像的小区域,最终这会减少网络中的参数数量,并减少训练和测试时间。 与 DRQN 不同,DARQN 中的 LSTM 层不仅存储了先前的状态信息以采取下一个最佳操作,而且还保留了先前的状态信息。 它还存储用于确定下一个图像焦点的信息。

W
wizardforcel 已提交
569
# DARQN 的架构
W
wizardforcel 已提交
570

W
wizardforcel 已提交
571
DARQN 的架构如下所示:
W
wizardforcel 已提交
572 573 574

![](img/00291.gif)

W
wizardforcel 已提交
575
它由三层组成; 卷积层,注意力层和 LSTM 循环层。 游戏屏幕作为图像被馈送到卷积网络。 卷积网络处理图像并生成特征映射。 然后,要素贴图会进入关注层。 注意层将它们转换为向量,并产生它们的线性组合,称为上下文向量。 然后将上下文向量以及先前的隐藏状态传递到 LSTM 层。 LSTM 层提供两个输出; 一方面,它提供 Q 值来决定在某种状态下要执行的动作;另一方面,它可以帮助注意力网络确定在下一个时间步中要关注的图像区域,从而可以生成更好的上下文向量。 。
W
wizardforcel 已提交
576 577 578

注意有两种类型:

W
wizardforcel 已提交
579
*   **软注意力**:我们知道,卷积层产生的特征映射将作为输入提供给关注层,然后生成上下文向量。 轻描淡写地,这些上下文向量只是卷积层产生的所有输出(特征映射)的加权平均值。 根据功能的相对重要性选择权重。
W
wizardforcel 已提交
580
*   **硬注意力**:硬注意力,根据某些位置选择策略`π`,我们仅关注图像在特定时间步长`t`上的特定位置。 该策略由神经网络表示,其权重是策略参数,网络的输出是位置选择概率。 但是,硬注意力不比软注意力好多少。
W
wizardforcel 已提交
581 582 583 584 585

# 概要

在本章中,我们学习了如何使用 DRQN 记住有关先前状态的信息,以及它如何克服部分可观察的 MDP 问题。 我们已经看到了如何训练我们的特工使用 DRQN 算法玩《毁灭战士》游戏。 我们还了解了 DARQN 作为 DRQN 的改进,它在卷积层的顶部增加了一个关注层。 在此之后,我们看到了两种类型的注意力机制: 即软硬关注。

W
wizardforcel 已提交
586
在下一章第 10 章,“异步优势演员评论家网络”中,我们将学习另一种有趣的深度强化学习算法,称为异步优势演员评论家网络。
W
wizardforcel 已提交
587 588 589 590 591 592 593

# 问题

问题列表如下:

1.  DQN 和 DRQN 有什么区别?
2.  DQN 的缺点是什么?
W
wizardforcel 已提交
594
3.  我们如何在 DQN 中设置经验回放?
W
wizardforcel 已提交
595 596 597 598 599 600 601 602 603
4.  DRQN 和 DARQN 有什么区别?
5.  为什么我们需要 DARQN?
6.  注意机制有哪些不同类型?
7.  我们为什么要在《毁灭战士》中设定生存奖励?

# 进一步阅读

考虑以下内容,以进一步了解您的知识:

W
wizardforcel 已提交
604 605 606
*   [**DRQN 论文**](https://arxiv.org/pdf/1507.06527.pdf)
*   [**使用 DRQN 玩 FPS 游戏**](https://arxiv.org/pdf/1609.05521.pdf)
*   [**DARQN 论文**](https://arxiv.org/pdf/1512.01693.pdf)