本章提供了简明教程使用 
TensorFlow 
2.x从头开始实现高级强化学习算法和智能

体,包括构建 
Deep-Q-Networks(DQN)、Double 
Deep 
Q-Networks(DDQN)、 


Double 
Dueling 
Deep 
Q-Networks(DDDQN)、Deep 
Recurrent 
Q-Neworks(DRQN)、Asynchronous 
Advantage 
Actor-Critic(A3C)、Proximal 
Policy 
Optimization(
PPO)以及 
Deep 
Deterministic 
Policy 
Gradients(DDPG)的方法。
在本章中将会讨论以下内容: 


.实现 
Deep 
Q学习算法、DQN和 
Double-DQN智能体; 
.实现 
Dueling 
DQN智能体; 
.实现 
Dueling 
Double 
DQN算法和 
DDDQN智能体; 
.实现深度递归 
Q学习算法和 
DRQN智能体; 
.实现异步优势行动者-评论家算法和 
A3C智能体; 
.实现近端策略优化算法和 
PPO智能体; 
.实现深度确定性策略梯度算法和 
DDPG智能体。 
3.1技术要求
本书的代码已经在 
Ubuntu 
18.04和 
Ubuntu 
20.04上进行了广泛的测试,而且可以
在安装了 
Python 
3.6+的 
Ubuntu后续版本中正常工作。在安装 
Python 
3.6的情况下,
搭配每项内容开始时列出的必要 
Python工具包,本书的代码也同样可以在 
Windows和 
macOS 
X上运行。建议读者创建和使用一个命名为 
tf2rl-cookbook的 
Python虚拟环境来
安装工具包以及运行本书的代码。推荐读者安装 
Miniconda或 
Anaconda来管理 
Python
虚拟环境。 


3.2实现 
Deep 
Q学习算法、DQN和 
Double-DQN智能体 
DQN智能体采用深度神经网络来学习 
Q值函数。 
DQN是一种针对离散动作空间环
境和问题的强有力的算法,并当在 
Atari游戏中取得了成功时,DQN成为了深度强化学习


历史上的一个重要的里程碑。 


Double-DQN智能体使用了两种相同的深度神经网络,它们的更新方式不同,因此也
具有不同的权重。第二个神经网络是之前某个时间(通常从上一个回合开始)的主神经网
络的副本。

通过本节,可以使用 
TensorFlow 
2.x从头开始实现一个完整的 
DQN和 
Double-DQN
智能体,该智能体能够在任何离散动作空间的强化学习环境中进行训练。 


3.2.1前期准备
为了完成本节内容,需要激活命名为 
tf2rl-cookbook的 
Python/Conda虚拟环境并在
命令行运行 
pip 
install 
-r 
requirements.txt。如果运行下面的导入语句时没有出现问题,就
可以准备开始了: 


import 
argparse 
from 
datetime 
import 
datetime 
import 
os 
import 
random 
from 
collections 
import 
deque 


import 
gym 
import 
numpy 
as 
np 
import 
tensorflow 
as 
tf 
from 
tensorflow.keras.layers 
import 
Dense, 
Input 


3.2.2实现步骤 
DQN智能体包含以下部分,即回放缓冲区、DQN类、Agent类和 
train()函数。使用 
TensorFlow 
2.x执行以下步骤从头开始实现上述每个部分,从而构建完整的 
DQN智能体。

(1)创建一个参数解析器处理脚本的配置输入: 
parser 
= 
argparse.ArgumentParser(prog="TFRLCookbookCh3DQN") 
parser.add_argument("env 
, default="CartPolev0") 
parser.add_argument("lr", 
type=float, default=0.005) 
parser.add_argument("batch_
size", type=int, default=256) 
parser.add_argument("gamma", 
type=float, default=0.95) 
parser.add_argument("eps", 
type=float, default=1.0) 
parser.add_argument("eps_
decay", type=float, default=0.995) 
parser.add_argument("eps_
min", type=float, default=0.01) 
parser.add_argument("logdir", 
default="logs") 
args = parser.parse_args()

(2)创建一个 
Tensorboard日志,记录智能体在训练时的有用统计信息: 

logdir 
= 
os.path.join( 
args.logdir, 
parser.prog, 
args.env, 
datetime.now().strftime("%Y%m%d%
H%M%S") 


) 
print(f"Saving training logs to:{logdir}") 
writer 
= 
tf.summary.create_file_writer(logdir)


(3)实现一个 
ReplayBuffer类: 
class 
ReplayBuffer: 
def 
__init__(self, 
capacity=10000): 
self.buffer 
= 
deque(maxlen=capacity) 


def 
store(self, 
state, 
action, 
reward, 
next_state, 


done): 
self.buffer.append([state, 
action, 
reward, 
next_state, 
done]) 


def 
sample(self): 
sample 
= 
random.sample(self.buffer, 
args.batch_size) 
states, 
actions, 
rewards, 
next_states, 
done 
= 
\ 
map(np.asarray, 
zip(*sample)) 
states 
= 
np.array(states).reshape( 
args.batch_size, 
1) 
next_states 
= 
np.array(next_states).\ 
reshape(args.batch_size, 
1) 
return 
states, 
actions, 
rewards, 
next_states, 
done 


def 
size(self): 
return 
len(self.buffer)


(4)使用 
TensorFlow 
2.x定义深度神经网络的 
DQN类: 
class 
DQN: 


def 
__init__(self, 
state_dim, 
aciton_dim): 
self.state_dim 
= 
state_dim 
self.action_dim 
= 
aciton_dim 
self.epsilon 
= 
args.eps 


self.model 
= 
self.nn_model() 



def 
nn_model(self): 
model 
= 
tf.keras.Sequential( 


[ 
Input((self.state_dim,)), 
Dense(32, 
activation="relu"), 
Dense(16, 
activation="relu"), 
Dense(self.action_dim), 


] 
) 
model.compile(loss="mse", 


optimizer=Adam(args.lr)) 
return 
model


(5)为了从 
DQN中获得预测和动作,实现 
predict()函数和 
get_ 
action()函数: 
def 
predict(self, 
state): 
return 
self.model.predict(state) 


def 
get_action(self, 
state): 
state 
= 
np.reshape(state, 
[1, 
self.state_dim]) 
self.epsilon 
*= 
args.eps_decay 
self.epsilon 
= 
max(self.epsilon, 
args.eps_min) 
q_value 
= 
self.predict(state)[0] 
if 
np.random.random() 
< 
self.epsilon: 


return 
random.randint(0, 
self.action_dim 
1) 
return 
np.argmax(q_value) 
def 
train(self, 
states, 
targets): 
self.model.fit(states, 
targets, 
epochs=1)

(6)当其他部分实现后,就开始实现 
Agent类: 
class 
Agent: 


def 
__init__(self, 
env): 
self.env 
= 
env 
self.state_dim 
= 
\ 
self.env.observation_space.shape[0] 
self.action_dim 
= 
self.env.action_space.n 


self.model 
= 
DQN(self.state_dim, 
self.action_dim) 
self.target_model 
= 
DQN(self.state_dim, 
self.action_dim) 
self.update_target() 


self.buffer 
= 
ReplayBuffer() 



def 
update_target(self): 
weights 
= 
self.model.model.get_weights() 
self.target_model.model.set_weights(weights)

(7) 
Deep 
Q-学习算法的关键是 
Q学习的更新和经验回放(experience 
replay): 
def 
replay_experience(self): 
for 
_ 
in 
range(10): 
states, 
actions, 
rewards, 
next_states, 
done=\ 


self.buffer.sample() 
targets 
= 
self.target_model.predict(states) 
next_q_values 
= 
self.target_model.\ 


predict(next_states).max(axis=1) 


targets[range(args.batch_size), 
actions] 
= 
( 
rewards 
+ 
(1 
done) 
* 
next_q_values 
* 
\ 
args.gamma 


) 
self.model.train(states, 
targets)


(8)这是一个关键步骤,即实现 
train()函数训练智能体: 
def 
train(self, 
max_episodes=1000): 
with 
writer.as_default(): 
# 
Tensorboard 
logging 


for 
ep 
in 
range(max_episodes): 
done, 
episode_reward 
= 
False, 
0 
observation 
= 
self.env.reset() 
while 
not 
done: 


action 
= 
\ 
self.model.get_action(observation) 
next_observation, 
reward, 
done, 
_ 
= 
\ 
self.env.step(action) 


self.buffer.store( 
observation, 
action, 
reward 
* 
\ 
0.01, 
next_observation, 
done 


) 
episode_reward 
+= 
reward 
observation 
= 
next_observation 


if 
self.buffer.size() 
>= 
args.batch_size: 


self.replay_experience() 
self.update_target() 
print(f"Episode#{ep} Reward:{


 episode_reward}") 
tf.summary.scalar("episode_reward", 



episode_reward, 
step=ep) 
writer.flush()

(9)创建主函数并开始训练智能体: 
if 
__name__ 
== 
"__main__": 
env 
= 
gym.make("CartPolev0") 
agent 
= 
Agent(env) 
agent.train(max_episodes=20000)

(10)可以执行以下命令在默认环境( 
CartPole-v0)中训练 
DQN智能体: 
python 
ch3deeprlagents/
1_dqn.py

(11)也可以使用以下命令行参数在任何 
OpenAI 
Gym兼容的离散动作空间的环境
中训练 
DQN智能体: 
python 
ch3deeprlagents/
1_dqn.py 
–env 
"MountainCarv0"


(12)实现 
Double 
DQN智能体,修改 
replay_ 
experience()函数以使用 
Double 
Q
学习的更新步骤,如下所示: 
def 
replay_experience(self): 
for 
_ 
in 
range(10): 
states, 
actions, 
rewards, 
next_states, 
done=\ 


self.buffer.sample() 
targets 
= 
self.target_model.predict(states) 
next_q_values 
= 
\ 


self.target_model.predict(next_states)[ 
range(args.batch_size), 
np.argmax(self.model.predict( 


next_states), 
axis=1), 
] 
targets[range(args.batch_size), 
actions] 
= 
( 


rewards 
+ 
(1 
done) 
* 
next_q_values 
* 
\ 


args.gamma 
) 
self.model.train(states, 
targets)


(13)训练 
Double 
DQN智能体,可以保存并运行使用了更新后的 
replay_experience()
函数的脚本,也可以使用本书提供的源码脚本: 
python 
ch3deeprlagents/
1_double_dqn.py 



3.2.3工作原理
根据下式对 
DQN中的权重进行更新: 


Δw = α[R+ r max 
Q.
(s ′ ,a;w).Q.
(s,a;w)].wQ.
(s,a;w) 

a 


|{z }|{z}|{z } 

s 
′的最大 
Q值 
预测 
Q值 
Q值的梯度

′

其中,Δw是 
DQN的参数(权重)的变化量, 
s是当前状态,a是当前动作,s 是下一状
态,w表示 
DQN的权重,γ是折扣因子,α是学习率。Q.
(s,a;w)表示权重为 
w的 
DQN
网络预测的给定状态 
(s)和动作 
(a)的 
Q值。

为了理解 
DQN智能体和 
Double-DQN智能体之间的区别,比较第( 
8)步( 
DQN)和
第( 
12)步( 
Double 
DQN)中的 
replay_ 
experience()方法,可以发现关键的不同在于计
算 
next_ 
q_ 
values。DQN智能体使用了所预测 
Q值的最大值(可能会高估),而 
Double 
DQN智能体使用两个不同的神经网络预测的 
Q值,以避免出现 
DQN智能体高估 
Q值的
问题。 


3.3实现 
Dueling 
DQN智能体 
Dueling 
DQN智能体通过修改后的网络结构显式估计两个量:

(1)状态值 
V(s);
(2)优势值 
A(s,a)。
状态值估计状态 
s的价值,而优势值表示在状态 
s中采取动作 
a的优势。这种将两个
量进行显式和单独估计的关键思想使 
Dueling 
DQN的性能优于 
DQN。本节将引导读者使
用 
TensorFlow 
2.x从头开始实现一个 
Dueling 
DQN智能体。 


3.3.1前期准备
为了完成本节内容,需要激活命名为 
tf2rl-cookbook的 
Python/Conda虚拟环境并在
命令行运行 
pip 
install 
-r 
requirements.txt。如果运行下面的导入语句时没有出现问题,就
可以准备开始了: 


import 
argparse 
import 
os 
import 
random 
from 
collections 
import 
deque 
from 
datetime 
import 
datetime 


import 
gym 
import 
numpy 
as 
np 
import 
tensorflow 
as 
tf 
from 
tensorflow.keras.layers 
import 
Add, 
Dense, 
Input 



from 
tensorflow.keras.optimizers 
import 
Adam 


3.3.2实现步骤 
Dueling 
DQN智能体包含以下内容,即回放缓冲区、DuelingDQN()类、Agent()类
和 
train()函数。使用 
TensorFlow 
2.x执行以下步骤从头开始实现上述每个部分,从而构
建完整的 
Dueling 
DQN智能体。

(1)创建一个参数解析器处理对脚本的命令行配置输入: 
parser 
= 
argparse.ArgumentParser(prog="TFRLCookbookCh3DuelingDQN") 
parser.add_argument("env", 
default="CartPolev0") 
parser.add_argument("lr", 
type=float, 
default=0.005) 
parser.add_argument("batch_
size", 
type=int, 
default=64) 
parser.add_argument("gamma", 
type=float, 
default=0.95) 
parser.add_argument("eps", 
type=float, 
default=1.0) 
parser.add_argument("eps_
decay", 
type=float, 
default=0.995) 
parser.add_argument("eps_
min", 
type=float, 
default=0.01) 
parser.add_argument("logdir", 
default="logs") 


args 
= 
parser.parse_args()

(2)创建一个 
Tensorboard日志,记录智能体在训练时的有用统计信息: 
logdir 
= 
os.path.join( 
args.logdir, 
parser.prog, 
args.env, 
datetime.now().strftime("%Y%m%d%
H%M%S") 


) 
print(f"Saving training logs to:{logdir}") 
writer 
= 
tf.summary.create_file_writer(logdir)


(3)实现一个 
ReplayBuffer类: 
class 
ReplayBuffer: 
def 
__init__(self, 
capacity=10000): 
self.buffer 
= 
deque(maxlen=capacity) 


def 
store(self, 
state, 
action, 
reward, 
next_state, 
done): 
self.buffer.append([state, 
action, 
reward, 
next_state, 
done]) 


def 
sample(self): 
sample 
= 
random.sample(self.buffer, 
args.batch_size) 



states, 
actions, 
rewards, 
next_states, 
done 
= 
\ 
map(np.asarray, 
zip(*sample)) 
states 
= 
np.array(states).reshape( 
args.batch_size, 
1) 
next_states 
= 
np.array(next_states).reshape( 


args.batch_size, 
1) 
return 
states, 
actions, 
rewards, 
next_states, 
done 


def 
size(self): 
return 
len(self.buffer)


(4)使用 
TensorFlow 
2.x定义深度神经网络的 
DuelingDQN类: 
class 
DuelingDQN: 


def 
__init__(self, 
state_dim, 
aciton_dim): 
self.state_dim 
= 
state_dim 
self.action_dim 
= 
aciton_dim 
self.epsilon 
= 
args.eps 


self.model 
= 
self.nn_model() 


def 
nn_model(self): 
backbone 
= 
tf.keras.Sequential( 


[ 
Input((self.state_dim,)), 
Dense(32, 
activation="relu"), 
Dense(16, 
activation="relu"), 


] 
) 
state_input 
= 
Input((self.state_dim,)) 
backbone_1 
= 
Dense(32, 
activation="relu")\ 


(state_input) 
backbone_2 
= 
Dense(16, 
activation="relu")\ 


(backbone_1) 
value_output 
= 
Dense(1)(backbone_2) 
advantage_output 
= 
Dense(self.action_dim)\ 


(backbone_2) 
output 
= 
Add()([value_output, 
advantage_output]) 
model 
= 
tf.keras.Model(state_input, 
output) 
model.compile(loss="mse", 


optimizer=Adam(args.lr)) 
return 
model 



(5)为了从 
Dueling 
DQN中获得预测和动作,实现 
predict()函数和 
get_action()函
数以及 
train()函数: 
def 
predict(self, 
state): 
return 
self.model.predict(state) 


def 
get_action(self, 
state): 
state 
= 
np.reshape(state, 
[1, 
self.state_dim]) 
self.epsilon 
*= 
args.eps_decay 
self.epsilon 
= 
max(self.epsilon, 
args.eps_min) 
q_value 
= 
self.predict(state)[0] 
if 
np.random.random() 
< 
self.epsilon: 


return 
random.randint(0, 
self.action_dim 
1) 
return 
np.argmax(q_value) 


def 
train(self, 
states, 
targets): 
self.model.fit(states, 
targets, 
epochs=1)

(6)实现 
Agent类: 
class 
Agent: 
def 
__init__(self, 
env): 
self.env 
= 
env 
self.state_dim 
= 
\ 
self.env.observation_space.shape[0] 
self.action_dim 
= 
self.env.action_space.n 


self.model 
= 
DuelingDQN(self.state_dim, 
self.action_dim) 
self.target_model 
= 
DuelingDQN(self.state_dim, 
self.action_dim) 
self.update_target() 


self.buffer 
= 
ReplayBuffer() 


def 
update_target(self): 
weights 
= 
self.model.model.get_weights() 
self.target_model.model.set_weights(weights)

(7) 
Dueling 
Deep 
Q学习算法的关键是 
Q学习的更新和经验回放: 
def 
replay_experience(self): 
for 
_ 
in 
range(10): 
states, 
actions, 
rewards, 
next_states, 
done=\ 



self.buffer.sample() 
targets 
= 
self.target_model.predict(states) 
next_q_values 
= 
self.target_model.\ 


predict(next_states).max(axis=1) 


targets[range(args.batch_size), 
actions] 
= 
( 
rewards 
+ 
(1 
done) 
* 
next_q_values 
* 
\ 
args.gamma 


) 
self.model.train(states, 
targets)


(8)实现 
train()函数训练智能体: 
def 
train(self, 
max_episodes=1000): 
with 
writer.as_default(): 


for 
ep 
in 
range(max_episodes): 
done, 
episode_reward 
= 
False, 
0 
state 
= 
self.env.reset() 
while 
not 
done: 


action 
= 
self.model.get_action(state) 
next_state, 
reward, 
done, 
_ 
= 
\ 
self.env.step(action) 


self.buffer.put(state, 
action, 
\ 
reward 
* 
0.01, 
\ 
next_state, 
done) 


episode_reward 
+= 
reward 
state 
= 
next_state 


if 
self.buffer.size() 
>= 
args.batch_size: 


self.replay_experience() 
self.update_target() 
print(f"Episode#{ep} \ 


Reward:{episode_reward}") 
tf.summary.scalar("episode_reward",\ 
episode_reward, 
step=ep)


(9)创建主函数并训练智能体: 
if 
__name__ 
== 
"__main__": 
env 
= 
gym.make("CartPolev0") 
agent 
= 
Agent(env) 
agent.train(max_episodes=20000)

(10)可以执行以下命令在默认环境( 
CartPole-v0)中训练 
Dueling 
DQN智能体: 

python 
ch3deeprlagents/
2_dueling_dqn.py

(11)也可以使用以下命令行参数在任何 
OpenAI 
Gym兼容的离散动作空间环境中
训练 
DQN智能体: 
python 
ch3deeprlagents/
2_dueling_dqn.py 
–env 
"MountainCarv0" 


3.3.3工作原理 
Dueling-DQN智能体和 
DQN智能体的区别在于神经网络的结构。图 
3.1总结了这些
区别。


图 
3.1 
DQN和 
Dueling-DQN的对比

图 
3.1(a)中所示的 
DQN具有线性体系结构,并预测单个数量 
Q(s,a),而 
DuelingDQN
在最后一层具有分叉结构,可以预测多个量。 


3.4实现 
Dueling 
Double 
DQN算法和 
DDDQN智能体 
DDDQN结合了 
Double 
Q学习和 
Dueling结构的优点。Double 
Q学习可以通过纠
正高估动作值来改进 
DQN。Dueling结构使用修改后的神经网络结构分别学习状态值函数

(V)和优势函数( 
A)。这样一种明确的分离使算法可以学习得更快,特别是当有很多动作可
供选择以及这些动作彼此非常相似时。与 
DQN智能体不同,Dueling结构使智能体即使在
某个状态中仅执行过一个动作时也可以学习,因为它可以更新和估计状态值函数,而 
DQN
智能体无法从尚未采取的动作中学习。在本书的最后,读者将实现一个完整的 
DDDQN智
能体。 

3.4.1前期准备
为了完成本节内容,需要激活命名为 
tf2rl-cookbook的 
Python/Conda虚拟环境并在
命令行运行 
pip 
install 
-r 
requirements.txt。如果运行下面的导入语句时没有出现问题,就
可以准备开始了: 


import 
argparse 
from 
datetime 
import 
datetime 
import 
os 
import 
random 
from 
collections 
import 
deque 


import 
gym 
import 
numpy 
as 
np 
import 
tensorflow 
as 
tf 
from 
tensorflow.keras.layers 
import 
Add, 
Dense, 
Input 
from 
tensorflow.keras.optimizers 
import 
Adam 


3.4.2实现步骤 
DDDQN智能体结合了 
DQN、Double 
DQN和 
Dueling 
DQN三者的思想。使用 
TensorFlow 
2.x执行以下步骤从头开始实现上述每个部分,从而构建完整的 
Dueling 
Double 
DQN智能体。

(1)创建一个参数解析器处理对脚本的命令行配置输入: 
parser 
= 
argparse.ArgumentParser(prog="TFRLCookbookCh3DuelingDoubleDQN" 


) 
parser.add_argument("env", 
default="CartPolev0") 
parser.add_argument("lr", 
type=float, 
default=0.005) 
parser.add_argument("batch_
size", 
type=int, 
default=256) 
parser.add_argument("gamma", 
type=float, 
default=0.95) 
parser.add_argument("eps", 
type=float, 
default=1.0) 
parser.add_argument("eps_
decay", 
type=float, 
default=0.995) 
parser.add_argument("eps_
min", 
type=float, 
default=0.01) 
parser.add_argument("logdir", 
default="logs") 


args 
= 
parser.parse_args()

(2)创建 
Tensorboard日志,记录智能体在训练时的有用统计信息: 
logdir 
= 
os.path.join( 
args.logdir, 
parser.prog, 
args.env, 
\ 
datetime.now().strftime("%Y%m%d%
H%M%S") 


) 



print(f"Saving training logs to:{logdir}") 
writer 
= 
tf.summary.create_file_writer(logdir)


(3)实现一个 
ReplayBuffer类: 
class 
ReplayBuffer: 
def 
__init__(self, 
capacity=10000): 
self.buffer 
= 
deque(maxlen=capacity) 


def 
store(self, 
state, 
action, 
reward, 
next_state, 
done): 
self.buffer.append([state, 
action, 
reward, 
\ 
next_state, 
done]) 


def 
sample(self): 
sample 
= 
random.sample(self.buffer, 
\ 
args.batch_size) 
states, 
actions, 
rewards, 
next_states, 
done 
= 
\ 
map(np.asarray, 
zip(*sample)) 
states 
= 
np.array(states).reshape( 
args.batch_size, 
1) 
next_states 
= 
np.array(next_states).\ 


reshape(args.batch_size, 
1) 
return 
states, 
actions, 
rewards, 
next_states, 
\ 
done 


def 
size(self): 
return 
len(self.buffer)


(4)实现 
Dueling 
DQN类,该类根据 
Dueling结构定义神经网络,在以后的步骤中
向其中添加 
Double 
DQN的更新: 
class 
DuelingDQN: 


def 
__init__(self, 
state_dim, 
aciton_dim): 
self.state_dim 
= 
state_dim 
self.action_dim 
= 
aciton_dim 
self.epsilon 
= 
args.eps 


self.model 
= 
self.nn_model() 


def 
nn_model(self): 
state_input 
= 
Input((self.state_dim,)) 
fc1 
= 
Dense(32, 
activation="relu")(state_input) 
fc2 
= 
Dense(16, 
activation="relu")(fc1) 
value_output 
= 
Dense(1)(fc2) 



advantage_output 
= 
Dense(self.action_dim)(fc2) 
output 
= 
Add()([value_output, 
advantage_output]) 
model 
= 
tf.keras.Model(state_input, 
output) 
model.compile(loss="mse", 
\ 


optimizer=Adam(args.lr)) 
return 
model

(5)为了从 
Dueling 
DQN中获得预测和动作,实现 
predict()函数和 
get_action()
函数: 
def 
predict(self, 
state): 
return 
self.model.predict(state) 


def 
get_action(self, 
state): 
state 
= 
np.reshape(state, 
[1, 
self.state_dim]) 
self.epsilon 
*= 
args.eps_decay 
self.epsilon 
= 
max(self.epsilon, 
args.eps_min) 
q_value 
= 
self.predict(state)[0] 
if 
np.random.random() 
< 
self.epsilon: 


return 
random.randint(0, 
self.action_dim 
1) 
return 
np.argmax(q_value) 
def 
train(self, 
states, 
targets): 
self.model.fit(states, 
targets, 
epochs=1) 
`~\\`

(6)实现 
Agent类: 
class 
Agent: 
def 
__init__(self, 
env): 
self.env 
= 
env 
self.state_dim 
= 
\ 
self.env.observation_space.shape[0] 
self.action_dim 
= 
self.env.action_space.n 


self.model 
= 
DuelingDQN(self.state_dim, 
self.action_dim) 
self.target_model 
= 
DuelingDQN(self.state_dim, 
self.action_dim) 
self.update_target() 


self.buffer 
= 
ReplayBuffer() 


def 
update_target(self): 
weights 
= 
self.model.model.get_weights() 



self.target_model.model.set_weights(weights)

(7) 
Dueling 
Double 
Deep 
Q学习算法最主要的部分是 
Q学习的更新和经验回放: 
def 
replay_experience(self): 
for 
_ 
in 
range(10): 
states, 
actions, 
rewards, 
next_states, 
done=\ 


self.buffer.sample() 
targets 
= 
self.target_model.predict(states) 
next_q_values 
= 
\ 


self.target_model.predict(next_states)[ 
range(args.batch_size), 
np.argmax(self.model.predict( 


next_states), 
axis=1), 
] 
targets[range(args.batch_size), 
actions] 
= 
( 


rewards 
+ 
(1 
done) 
* 
next_q_values 
* 
\ 


args.gamma 
) 
self.model.train(states, 
targets)


(8)实现 
train()函数训练智能体: 
def 
train(self, 
max_episodes=1000): 
with 
writer.as_default(): 


for 
ep 
in 
range(max_episodes): 
done, 
episode_reward 
= 
False, 
0 
observation 
= 
self.env.reset() 
while 
not 
done: 


action 
= 
\ 
self.model.get_action(observation) 
next_observation, 
reward, 
done, 
_ 
= 
\ 
self.env.step(action) 
self.buffer.store( 
observation, 
action, 
reward 
* 
\ 


0.01, 
next_observation, 
done 
) 
episode_reward 
+= 
reward 
observation 
= 
next_observation 


if 
self.buffer.size() 
>= 
args.batch_size: 


self.replay_experience() 
self.update_target() 
print(f"Episode#{ep} \ 



Reward:{episode_reward}") 


tf.summary.scalar("episode_reward", 
episode_reward, 
step=ep)

(9)创建主函数并训练智能体: 
if 
__name__ 
== 
"__main__": 
env 
= 
gym.make("CartPolev0") 
agent 
= 
Agent(env) 
agent.train(max_episodes=20000)

(10)可以执行以下命令在默认环境( 
CartPole-v0)中训练 
DQN智能体: 
python 
ch3deeprlagents/
3_dueling_double_dqn.py

(11)也可以使用以下命令行参数在任何 
OpenAI 
Gym兼容的离散动作空间环境中
训练 
Dueling 
Double 
DQN智能体: 
python 
ch3deeprlagents/
3_dueling_double_dqn.py 
–env 
"MountainCarv0" 


3.4.3工作原理 
Dueling 
Double 
DQN结构将 
Double 
DQN和 
Dueling结构的优势结合在一起。 


3.5实现深度递归 
Q学习算法和 
DRQN智能体 
DRQN使用递归神经网络学习 
Q值函数。DRQN更适合在具有部分可观测性的环境
中进行强化学习。DRQN中的循环网络层允许智能体通过整合来自观测的时间序列的信息
来学习。例如, 
DRQN智能体可以推断环境中移动对象的速度,而无须对其输入进行任何
更改(例如,不需要帧堆叠)。通过本节,可以实现一个完整的 
DRQN智能体,该智能体
随时可以在选择的强化学习环境中进行训练。 


3.5.1前期准备
为了完成本节内容,需要激活命名为 
tf2rl-cookbook的 
Python/Conda虚拟环境并在
命令行运行 
pip 
install 
-r 
requirements.txt。如果运行下面的导入语句时没有出现问题,就
可以准备开始了: 


import 
tensorflow 
as 
tf 
from 
datetime 
import 
datetime 
import 
os 
from 
tensorflow.keras.layers 
import 
Input, 
Dense, 
LSTM 
from 
tensorflow.keras.optimizers 
import 
Adam 



import 
gym 
import 
argparse 
import 
numpy 
as 
np 
from 
collections 
import 
deque 
import 
random 


3.5.2实现步骤
使用 
TensorFlow 
2.x执行以下步骤从头开始实现上述每个部分,从而构建完整的 
DRQN
智能体。

(1)创建一个参数解析器处理脚本的命令行配置输入: 
parser 
= 
argparse.ArgumentParser(prog="TFRLCookbookCh3DRQN") 
parser.add_argument("env", 
default="CartPolev0") 
parser.add_argument("lr", 
type=float, 
default=0.005) 
parser.add_argument("batch_
size", 
type=int, 
default=64) 
parser.add_argument("time_
steps", 
type=int, 
default=4) 
parser.add_argument("gamma", 
type=float, 
default=0.95) 
parser.add_argument("eps", 
type=float, 
default=1.0) 
parser.add_argument("eps_
decay", 
type=float, 
default=0.995) 
parser.add_argument("eps_
min", 
type=float, 
default=0.01) 
parser.add_argument("logdir", 
default="logs") 
args 
= 
parser.parse_args()

(2)创建 
TensorBoard日志,记录智能体在训练时的有用统计信息: 
logdir 
= 
os.path.join( 
args.logdir, 
parser.prog, 
args.env, 
\ 
datetime.now().strftime("%Y%m%d%
H%M%S") 


) 
print(f"Saving training logs to:{logdir}") 
writer 
= 
tf.summary.create_file_writer(logdir)


(3)实现 
ReplayBuffer类: 
class 
ReplayBuffer: 
def 
__init__(self, 
capacity=10000): 
self.buffer 
= 
deque(maxlen=capacity) 


def 
store(self, 
state, 
action, 
reward, 
next_state,\ 
done): 
self.buffer.append([state, 
action, 
reward, 
\ 
next_state, 
done]) 



def 
sample(self): 
sample 
= 
random.sample(self.buffer, 
args.batch_size) 
states, 
actions, 
rewards, 
next_states, 
done 
= 
\ 
map(np.asarray, 
zip(*sample)) 
states 
= 
np.array(states).reshape( 
args.batch_size, 
1) 
next_states 
= 
np.array(next_states).reshape( 


args.batch_size, 
1) 
return 
states, 
actions, 
rewards, 
next_states, 
\ 
done 


def 
size(self): 
return 
len(self.buffer)


(4)使用 
TensorFlow 
2.x定义深度神经网络的 
DRQN类: 
class 
DRQN: 


def 
__init__(self, 
state_dim, 
action_dim): 
self.state_dim 
= 
state_dim 
self.action_dim 
= 
action_dim 
self.epsilon 
= 
args.eps 


self.opt 
= 
Adam(args.lr) 
self.compute_loss 
= 
\ 
tf.keras.losses.MeanSquaredError() 
self.model 
= 
self.nn_model() 


def 
nn_model(self): 
return 
tf.keras.Sequential( 


[ 
Input((args.time_steps, 
self.state_dim)), 
LSTM(32, 
activation="tanh"), 
Dense(16, 
activation="relu"), 
Dense(self.action_dim), 


] 
)


(5)为了从 
DRQN获得预测和动作,实现 
predict()函数和 
get_action()函数: 
def 
predict(self, 
state): 
return 
self.model.predict(state) 


def 
get_action(self, 
state): 



state 
= 
np.reshape(state, 
[1, 
args.time_steps, 


self.state_dim]) 
self.epsilon 
*= 
args.eps_decay 
self.epsilon 
= 
max(self.epsilon, 
args.eps_min) 
q_value 
= 
self.predict(state)[0] 
if 
np.random.random() 
< 
self.epsilon: 


return 
random.randint(0, 
self.action_dim 
1) 
return 
np.argmax(q_value) 


def 
train(self, 
states, 
targets): 
targets 
= 
tf.stop_gradient(targets) 
with 
tf.GradientTape() 
as 
tape: 


logits 
= 
self.model(states, 
training=True) 
assert 
targets.shape 
== 
logits.shape 
loss 
= 
self.compute_loss(targets, 
logits) 


grads 
= 
tape.gradient(loss, 
self.model.trainable_variables) 
self.opt.apply_gradients(zip(grads, 
self.model.trainable_variables))

(6)实现 
Agent类: 
class 
Agent: 


def 
__init__(self, 
env): 
self.env 
= 
env 
self.state_dim 
= 
\ 


self.env.observation_space.shape[0] 
self.action_dim 
= 
self.env.action_space.n 


self.states 
= 
np.zeros([args.time_steps, 
self.state_dim]) 


self.model 
= 
DRQN(self.state_dim, 
self.action_dim) 
self.target_model 
= 
DRQN(self.state_dim, 
self.action_dim) 
self.update_target() 


self.buffer 
= 
ReplayBuffer() 


def 
update_target(self): 
weights 
= 
self.model.model.get_weights() 
self.target_model.model.set_weights(weights)