上篇文章介绍了强化学习——Actor-Critic算法详解加实战 介绍了Actor-Critic,本篇文章将介绍 DDPG 算法,DDPG 全称是 Deep Deterministic Policy Gradient(深度确定性策略梯度算法) 其中 PG 就是前面介绍的 Policy Gradient,在强化学习10——Policy Gradient 推导 已经讨论过,那什么是确定性策略梯度呢?
一、确定性策略
与确定性策略对应的是随机性策略,就是神经网络输出的是动作的分布,在确定每一步动作时,需要得到的策略分布进行采样,对于某些高纬的连续值动作,频繁的在高维空间对动作进行采样,是很耗费计算能力的。
同样,对于DQN算法,其只适用于低维、离散动作的问题,对于连续动作问题,DQN要计算所有可能动作的概率,并计算可能的动作的价值,动作的数量随着自由度的数量呈指数增长,那需要非常的样本量与计算量,所以就有了确定性策略来简化这个问题。
作为随机策略,在相同的策略,在同一个状态处,采用的动作是基于一个概率分布的,即是不确定的。而确定性策略就简单的多,即使用相同的策略,在同一状态下,动作是唯一确定的:
a
t
=
μ
(
s
∣
θ
μ
)
a_t = \mu(s|\theta^\mu)
at=μ(s∣θμ)
二、DDPG
首先要注意一点,DDPG从名字上像一个策略梯度(PG)算法,但是其实它更接近DQN,或者说DDPG是使用的 Actor-Critic 架构来解决DQN不能处理连续动作控制问题的一个算法,这点一定要注意。下面来详细解释为什么这么说
1、从 Q-Learning 到 DQN
先回忆下Q-Learning的算法流程,在 强化学习4——时序差分控制算法 中已经详细介绍过Q-Learning算法:首先基于状态
S
t
S_t
St,用
ϵ
−
\epsilon-
ϵ−贪婪法选择到动作
A
t
A_t
At 并执行,进入状态
S
t
+
1
S_{t+1}
St+1,并得到奖励
R
t
R_{t}
Rt,然后利用得到的
<
S
,
A
,
R
,
S
′
>
<S,A,R,S'>
<S,A,R,S′> 来更新Q表格,注意在更新Q表格时,基于状态
S
t
+
1
S_{t+1}
St+1 使用贪心策略选择
A
′
A'
A′ 。
A
′
=
m
a
x
a
′
Q
(
S
′
,
a
′
)
A' = max_{a'} Q(S',a')
A′=maxa′Q(S′,a′)
也就是选择使
Q
(
S
t
+
1
,
a
)
Q(S_{t+1}, a)
Q(St+1,a) 最大的
a
a
a 来作为
A
′
A'
A′ 来更新价值函数。对应到上图中就是在图下方的三个黑圆圈动作中选择一个使
Q
(
S
′
,
a
)
Q(S', a)
Q(S′,a) 最大的动作作为
A
′
A'
A′ 。
由于Q-Learning 使用Q表格存储每个状态的所有动作价值,所以面对连续状态既如果状态非常多的情况就不能胜任,所有就用函数逼近的方法,使用神经网络来代替 Q 表格,其余流程不变,这样就得到了DQN算法。在强化学习7——DQN算法详解 中已经详细介绍过 DQN算法,下面就简单回忆下DQN算法流程:
可以看出,DQN用神经网络代替Q表格,loss 函数就是神经网络当前的输出与target之间的差距,然后对损失函数求导更新网络 参数。
2、从DQN 到DDPG
而target的计算方式为 r + γ m a x a ′ q ^ ( s ′ , a ′ , w ) r + \gamma\;max_{a'}\hat{q}(s',a',w) r+γmaxa′q^(s′,a′,w) ,即从下一个状态使用max函数选一个最大的动作 Q,当具有有限数量的离散动作值时,计算max不是问题,因为可以对所有动作计算其Q值(这也能够得到能够最大化Q值的动作)。但是当动作空间连续时,却不能穷举所有的可能,这也是DQN不能处理连续动作控制任务的原因。
那么该如何解决这个问题呢?前面知道,DQN用神经网络解决了Q-Learning不能解决的连续状态空间问题。那可不可以也使用一个神经网络来代替
m
a
x
a
Q
(
s
,
a
)
max_aQ(s,a)
maxaQ(s,a) 呢?当然是可以的,DDPG就是这样做的,即用一个函数来代替这个过程:
m
a
x
a
Q
(
s
,
a
)
≈
Q
(
s
,
μ
(
s
∣
θ
)
)
max_aQ(s,a) \approx Q(s,\mu(s|\theta))
maxaQ(s,a)≈Q(s,μ(s∣θ))
其中的
θ
\theta
θ 就是策略网络的参数,根据前面讲过的AC算法,可以联想到使用策略网络充当 Actor,使用DQN中的价值网络充当 Critic(注意这里估计的是 Q值,而不是 V值,不要和AC算法搞混了),Actor部分不使用带 Reward 的加权梯度更新(PG算法更新方式),而是使用Critic网络对action 的梯度对 actor更新。
DDPG既然来自DQN,那当然也有经验回放与双网络,所以在 Critic 部分增加一个目标网络去计算目标 Q值,但是还需要选取动作用来估计目标Q值,目前已经有一个Actor策略网络,用类似的做法,再增加一个Actor目标网络,所以DDPG算法一共包含了四个网络,分别是:Actor当前网络,Actor目标网络,Critic当前网络,Critic目标网络,2个Actor网络的结构相同,2个Critic网络的结构相同,这四个网络的功能如下:
- Actor当前网络:负责策略网络参数 θ \theta θ 的迭代更新,负责根据当前状态 S 选择当前动作 A,用于和环境交互生成 S’和 R。
- Actor目标网络:负责根据经验回放池中采样的下一状态 S’ 选择最优下一动作 A’,网络参数 θ \theta θ 定期从 θ \theta θ 复制。
- Critic当前网络:负责价值网络参数 w w w的迭代更新,输出当前Q值 Q ( S , A , w ) Q(S,A,w) Q(S,A,w) 。
- Critic目标网络:负责输出 Q ′ ( S ′ , A ′ , w ′ ) Q'(S',A',w') Q′(S′,A′,w′) ,从而计算目标Q值(td-target),网络参数 w ′ w' w′ 定期从 w w w 复制。
注意:Critic 网络的输入有两个:动作和状态,需要一起 输入到 Critic 中
值得一提的是,DDPG从当前网络到目标网络的复制和之前讲到了DQN不一样。在DQN中是直接把将当前Q网络的参数复制到目标Q网络,即
w
′
=
w
w' = w
w′=w,这样的更新方式为硬更新,与之对应的是软更新,DDPG就是使用的软更新,即每次参数只更新一点点,即:
w
′
←
τ
w
+
(
1
−
τ
)
w
′
θ
←
τ
θ
+
(
1
−
τ
)
θ
′
w' \leftarrow \tau w + (1-\tau)w' \\ \theta \leftarrow \tau \theta + (1-\tau)\theta'
w′←τw+(1−τ)w′θ←τθ+(1−τ)θ′
其中
τ
\tau
τ 是更新系数,一般取的比较小。同时,为了学习过程可以增加一些随机性,探索潜在的更优策略,通过Ornstein-Uhlenbeck process(OU过程)为action添加噪声,最终输出的动作A为:
A
=
π
θ
(
s
)
+
O
U
n
o
i
s
e
A = \pi_\theta(s) + OU_{noise}
A=πθ(s)+OUnoise
对于损失函数,Critic部分和DQN类似,都是使用均方误差:
J
(
w
)
=
1
m
∑
i
=
1
m
(
y
i
−
Q
(
S
i
,
A
i
,
w
)
)
2
J(w) = \frac{1}{m}\sum_{i=1}^m(y_i - Q(S_i, A_i, w))^2
J(w)=m1i=1∑m(yi−Q(Si,Ai,w))2
对于Actor部分的损失函数,原论文定义的比较复杂,这里采取一种简单的方式来理解,由于Actor 的作用是输出一个动作 A,希望这个动作 A输入到 Critic 后,可以获得最大的 Q 值。 所以Actor的损失可以简单的理解为得到的反馈Q值越大损失越小,得到的反馈Q值越小损失越大,因此只要对状态估计网络返回的Q值取个负号即可,即:
J
(
θ
)
=
−
1
m
∑
i
=
1
m
Q
(
s
i
,
a
i
,
w
)
J(\theta) = -\frac{1}{m}\sum_{i=1}^mQ(s_i, a_i, w)
J(θ)=−m1i=1∑mQ(si,ai,w)
关于DDPG算法完整的流程如下:
![asY5ng.png](https://i-blog.csdnimg.cn/blog_migrate/8fe065cfd6dcc05517394916329b089d.png)
三、代码实现
代码使用 Pendulum-v0
连续环境,采用 tensorflow
学习框架
1、搭建网络
Actor 网络
def get_actor(input_state_shape):
input_layer = tl.layers.Input(input_state_shape)
layer = tl.layers.Dense(n_units=64, act=tf.nn.relu)(input_layer)
layer = tl.layers.Dense(n_units=64, act=tf.nn.relu)(layer)
layer = tl.layers.Dense(n_units=action_dim, act=tf.nn.tanh)(layer)
layer = tl.layers.Lambda(lambda x: action_range * x)(layer)
return tl.models.Model(inputs=input_layer, outputs=layer)
Actor 网络输入状态 ,输出动作,注意的是,连续环境的动作一般都有一个范围,这个范围在环境中已经定以好,使用 action_bound = env.action_space.high
即可获取。
如果 actor 输出的动作超出范围会导致程序异常,所以在网络末端使用 tanh
函数把输出映射到 [-1.0, 1.0]之间。然后使用 lamda
表达式,把动作映射到相应的范围。
Critic 网络
def get_critic(input_state_shape, input_action_shape):
state_input = tl.layers.Input(input_state_shape)
action_input = tl.layers.Input(input_action_shape)
layer = tl.layers.Concat(1)([state_input, action_input])
layer = tl.layers.Dense(n_units=64, act=tf.nn.relu)(layer)
layer = tl.layers.Dense(n_units=64, act=tf.nn.relu)(layer)
layer = tl.layers.Dense(n_units=1, name='C_out')(layer)
return tl.models.Model(inputs=[state_input, action_input], outputs=layer)
在DDPG中把状态和动作同时输入到 Critic 网络中,去估计 Q ( s , a ) Q(s,a) Q(s,a) 。所以定义两个输入层,然后连接起来,最后模型的输入部分定义两个输入。
2、主流程
for episode in range(TRAIN_EPISODES):
state = env.reset()
for step in range(MAX_STEPS):
if RENDER: env.render()
# Add exploration noise
action = agent.get_action(state)
state_, reward, done, info = env.step(action)
agent.store_transition(state, action, reward, state_)
if agent.pointer > MEMORY_CAPACITY:
agent.learn()
state = state_
if done: break
可以看到,DDPG流程与DQN基本相同,重置状态,然后选择动作,与环境交互,获得S’ ,R后,把数据保存起来。如多数据量足够,就对数据 进行抽样,更新网络参数。然后开始更新 s,开始下一步循环。
这里重点看一下 get_action()
函数:
def get_action(self, s, greedy=False):
a = self.actor(np.array([s], dtype=np.float32))[0]
if greedy:
return a
return np.clip(
np.random.normal(a, self.var), -self.action_range, self.action_range)
get_action()
函数用以选取一个动作,然后与环境交互。为了更好的探索环境,在训练过程中为动作加入噪声,原始的DDPG作者推荐加入与时间相关的OU噪声,但是更近的结果表明高斯噪声表现地更好。由于后者更为简单,因此其更为常用。
这里就采用的后者,为动作添加高斯噪声:Actor 输出的 a
作为一个正态分布的平均值,然后加上参数 VAR
,作为正太分布的方差,就可以构造一个正态分布。然后从正太分布中随机选取一个动作 ,由于正太分布是有很大概率采样到平均值附近的点,所以利用这个方法,就可以实现一定的探索行为。此外,也可以控制 VAR
的大小,来控制探索概率的大小。
当测试的时候,选取动作时就不需要探索,因为这时 Actor 要输入有最大 Q
值的动作,直接输出动作就可以。所以在get_action()
函数中用 一个参数 greedy
来控制这两种情况。
3、网络更新
Critic 更新
如上图所示,Critic 部分的更新和 DQN 是一样的,使用td-error来更新。用目标网络构造 target
,然后和当前网络输出的 q
计算MSE损失,然后更新网络参数。
with tf.GradientTape() as tape:
actions_ = self.actor_target(states_)
q_ = self.critic_target([states_, actions_])
target = rewards + GAMMA * q_
q_pred = self.critic([states, actions])
td_error = tf.losses.mean_squared_error(target, q_pred)
critic_grads = tape.gradient(td_error, self.critic.trainable_weights)
self.critic_opt.apply_gradients(zip(critic_grads, self.critic.trainable_weights))
Actor 更新
with tf.GradientTape() as tape:
actions = self.actor(states)
q = self.critic([states, actions])
actor_loss = -tf.reduce_mean(q) # maximize the q
actor_grads = tape.gradient(actor_loss, self.actor.trainable_weights)
self.actor_opt.apply_gradients(zip(actor_grads, self.actor.trainable_weights))
DDPG采用梯度上升法,Actor作用就是输出一个动作,这个动作输入Critic
网络能得到最大的 Q
值。由于和梯度下降方向相反,所以需要在 loss 函数前面加上负号。
完整代码地址: 强化学习——DDPG算法Tensorflow2.0实现 还望随手给个star
,再次不胜感激
四、总结
DDPG通过借鉴AC的架构,在DQN算法的基础上引入了Actor网络,解决了连续控制问题,可以看做是DQN在连续问题上的改进算法。
下篇会介绍DDPG的进化版本的算法,就是TD3算法。
参考:
知乎专栏:白话强化学习