深度强化学习-DDPG算法原理与代码
引言
1 DDPG算法简介
2 DDPG算法原理
2.1 经验回放
2.2 目标网络
2.2.1 算法更新过程
2.2.2 目标网络的更新
2.2.3 引入目标网络的目的
2.3 噪声探索
3 DDPG算法伪代码
4 代码实现
5 实验结果
6 结论
引言
Deep Deterministic Policy Gradient (DDPG)算法是DeepMind团队提出的一种专门用于解决连续控制问题的在线式(on-line)深度强化学习算法,它其实本质上借鉴了Deep Q-Network (DQN)算法里面的一些思想。本文就带领大家了解一下这个算法,论文和代码的链接见下方。
论文:https://arxiv.org/pdf/1509.02971.pdf
代码:https://github.com/indigoLovee/DDPG
喜欢的话请点个star哦!
1 DDPG算法简介
在DDPG算法之前,我们在求解连续动作空间问题时,主要有两种方式:一是对连续动作做离散化处理,然后再利用强化学习算法(例如DQN)进行求解。二是使用Policy Gradient (PG)算法 (例如Reinforce) 直接求解。但是对于方式一,离散化处理在一定程度上脱离了工程实际;对于方式二,PG算法在求解连续控制问题时效果往往不尽人意。为此,DDPG算法横空出世,在许多连续控制问题上取得了非常不错的效果。
DDPG算法是Actor-Critic (AC) 框架下的一种在线式深度强化学习算法,因此算法内部包括Actor网络和Critic网络,每个网络分别遵从各自的更新法则进行更新,从而使得累计期望回报最大化。
2 DDPG算法原理
DDPG算法将确定性策略梯度算法和DQN算法中的相关技术结合在一起,之前我们在讲DQN算法时,详细说明了其中的两个重要的技术:经验回放和目标网络。具体而言,DDPG算法主要包括以下三个关键技术:
(1)经验回放:智能体将得到的经验数据
放入Replay Buffer中,更新网络参数时按照批量采样。
(2)目标网络:在Actor网络和Critic网络外再使用一套用于估计目标的Target Actor网络和Target Critic网络。在更新目标网络时,为了避免参数更新过快,采用软更新方式。
(3)噪声探索:确定性策略输出的动作为确定性动作,缺乏对环境的探索。在训练阶段,给Actor网络输出的动作加入噪声,从而让智能体具备一定的探索能力。
2.1 经验回放
经验回放就是一种让经验概率分布变得稳定的技术,可以提高训练的稳定性。经验回放主要有“存储”和“回放”两大关键步骤:
存储:将经验以
形式存储在经验池中。
回放:按照某种规则从经验池中采样一条或多条经验数据。
从存储的角度来看,经验回放可以分为集中式回放和分布式回放:
集中式回放:智能体在一个环境中运行,把经验统一存储在经验池中。
分布式回放:多个智能体同时在多个环境中运行,并将经验统一存储在经验池中。由于多个智能体同时生成经验,所以能够使用更多资源的同时更快地收集经验。
从采样的角度来看,经验回放可以分为均匀回放和优先回放:
均匀回放:等概率从经验池中采样经验。
优先回放:为经验池中每条经验指定一个优先级,在采样经验时更倾向于选择优先级更高的经验。一般的做法是,如果某条经验(例如经验
)的优先级为
,那么选取该经验的概率为:

优先回放可以具体参照这篇论文 :优先经验回放
经验回放的优点:
1.在训练Q网络时,可以打破数据之间的相关性,使得数据满足独立同分布,从而减小参数更新的方差,提高收敛速度。
2.能够重复使用经验,数据利用率高,对于数据获取困难的情况尤其有用。
经验回放的缺点:
无法应用于回合更新和多步学习算法。但是将经验回放应用于Q学习,就规避了这个缺点。
代码中采用集中式均匀回放,具体如下:
import numpy as np class ReplayBuffer: def __init__(self, max_size, state_dim, action_dim, batch_size): self.mem_size = max_size self.batch_size = batch_size self.mem_cnt = 0 self.state_memory = np.zeros((self.mem_size, state_dim)) self.action_memory = np.zeros((self.mem_size, action_dim)) self.reward_memory = np.zeros((self.mem_size, )) self.next_state_memory = np.zeros((self.mem_size, state_dim)) self.terminal_memory = np.zeros((self.mem_size, ), dtype=np.bool) def store_transition(self, state, action, reward, state_, done): mem_idx = self.mem_cnt % self.mem_size self.state_memory[mem_idx] = state self.action_memory[mem_idx] = action self.reward_memory[mem_idx] = reward self.next_state_memory[mem_idx] = state_ self.terminal_memory[mem_idx] = done self.mem_cnt += 1 def sample_buffer(self): mem_len = min(self.mem_size, self.mem_cnt) batch = np.random.choice(mem_len, self.batch_size, replace=False) states = self.state_memory[batch] actions = self.action_memory[batch] rewards = self.reward_memory[batch] states_ = self.next_state_memory[batch] terminals = self.terminal_memory[batch] return states, actions, rewards, states_, terminals def ready(self): return self.mem_cnt >= self.batch_size
2.2 目标网络
由于DDPG算法是基于AC框架,因此算法中必然含有Actor和Critic网络。另外每个网络都有其对应的目标网络,所以DDPG算法中包括四个网络,分别是Actor网络
,Critic网络
,Target Actor网络
和Target Critic网络
。本节主要介绍一下DDPG算法的更新过程,目标网络的更新方式以及引入目标网络的目的
2.2.1 算法更新过程
算法更新主要更新的是Actor和Critic网络的参数,其中Actor网络通过最大化累积期望回报来更新
,Critic网络通过最小化评估值与目标值之间的误差来更新
。在训练阶段,我们从Replay Buffer中采样一个批次的数据,假设采样到的一条数据为
,Actor和Critic网络更新过程如下。
Critic网络更新过程:利用Target Actor网络计算出状态
下的动作:

这里需要注意:计算出动作后不需要加入噪声。然后利用Target Critic网络计算出状态动作对
的目标值:

接着利用 Critic网络计算出状态动作对
的评估值:

最后利用梯度下降算法最小化评估值和期望值之间的差值
,从而对Critic网络中的参数进行更新:

上述过程其实和DQN算法非常类似。
Actor网络更新过程:利用Actor网络计算出状态
下的动作:

这里需要注意:计算出动作后不需要加入噪声。然后利用Critic网络计算出状态动作对
的评估值(即累积期望回报):

最后利用梯度上升算法最大化累积期望回报
(代码实现是采用梯度下降算法优化
,其实本质上都是一样的),从而对Actor网络中的参数进行更新。
至此我们就完成了对Actor和Critic网络的更新。
2.2.2 目标网络的更新
对于目标网络的更新,DDPG算法中采用软更新方式,也可以称为指数平均移动 (Exponential Moving Average, EMA)。即引入一个学习率(或者成为动量)
,将旧的目标网络参数和新的对应网络参数做加权平均,然后赋值给目标网络:
Target Actor网络更新过程:

Target Critic网络更新过程:

学习率(动量)
,通常取值0.005。
2.2.3 引入目标网络的目的
我们在前面提到过,引入目标网络的目的其实和DQN算法的想法是一样的。由于目标网络中的参数是通过软更新的方式来缓慢更新的,因此它的输出会更加稳定,利用目标网络来计算目标值自然也会更加稳定,从而进一步保证Critic网络的学习过程更加平稳。试想,如果直接使用Critic网络来计算目标值

那么由于Critic网络在不断更新,网络波动剧烈,自然目标值
的变化也很剧烈。在学习过程中,让Critic网络的评估值追逐一个变化剧烈的目标,很容易出现网络震荡,从而导致整个学习过程坍塌。
上述是一种目的,其实还有另外一个目的。当使用Critic网络来计算目标值时(如上式所示),它其实本质上是一种自举 (Bootstrapping) 的过程。然后让
不断逼近
,很容易导致网络过估计。因为当
出现过估计时,会将其回传至
,导致该项也出现了过估计,从而形成了一种正反馈,最终导致整个网络出现过估计。
自举 (Bootstrapping)
表示在当前值函数的计算过程中,会利用到后续的状态值函数或动作值函数,即利用到后续的状态或者状态动作对。
那么过估计会出现什么问题呢?如果过估计是均匀的,对于最终的决策不会造成影响;但是如果不均匀,对于最终的决策会产生很大影响。我们举个栗子吧,大家很容易就能明白。

上图中我们假设有三个动作,每个动作的实际动作价值依次是200,100和230,显然动作3的动作价值是最高的,智能体会选择动作3。如果网络出现过估计,并且是均匀的,假设过估计的量是100,那么网络评估出来的动作价值就依次是300,200和330,显然动作3的动作价值依然是最高的,智能体依旧会选择动作3。因此,均匀过估计对于最终的决策并不会产生影响。

同样我们假设有三个动作,每个动作的实际动作价值依次是200,100和230,显然动作3的动作价值是最高的,智能体会选择动作3。如果网络出现不均匀过估计,评估出来的动作价值依次是280,300和240,此时显然动作2的动作价值是最高的,智能体会选择动作2。但是实际上动作2的真实动作价值是最低的,即该动作是最差的。因此,不均匀过估计对于最终的决策会产生很大的影响。
然而实际上网络的过估计是非均匀的,因此需要避免这个问题,本质上就是要解决Bootstrapping问题。采用目标网络后就能解决这个问题

此时我们再让
逼近目标值
时,就已经不再是自举了(大家可以对照自举的含义仔细观察一下)。
2.3 噪声探索
探索对于智能体来说是至关重要的,而确定性策略“天生”就缺乏探索能力,因此我们需要人为地给输出的动作上加入噪声,从而让智能体具备探索能力。在DDPG算法中,作者采用Ornstein Uhlenbeck过程作为动作噪声。Ornstein Uhlenbeck过程是用下列随机微分方程定义的 (以一维的情况为例):

其中
,
,
是参数(
是标准Brownain运动。当初始扰动是在原点的单点分布(即限定
),并且
时,上述方程的解为

(证明:将
代入
,化简可得
。将此式从0积到
,得
。当
且
时化简可得结果。)
这个解得均值为0,方差为
,协方差为

(证明:由于均值为0,所以
。另外,Ito Isometry告诉我们
,所以
,进一步化简可得结果。)
对于
总有
,所以