OPENAI-Baeslines-详解(三)-DDPG中文

Zee带你看代码系列

学习强化学习,码代码的能力必须要出众,要快速入门强化学习 搞清楚其中真正的原理,读源码是一个最简单的最直接的方式。最近创建了一系列该类型文章,希望对大家有多帮助。
传送门
另外,我会将所有的文章及所做的一些简单项目,放在我的个人网页上。
水平有限,可能有理解不到位的地方,希望大家主动沟通交流。
邮箱:[email protected]

Thanks for reading, and enjoy yourself。

DDPG

DDPG 深度确定性策略梯度下降算法。论文链接。采用了Actor-Critic 架构,可以有效的处理连续域的问题。

同时,其actor的确定性动作输出,提高了采样的有效性。

Actor-Critic and DPG

强化学习算法的主要目标是去学习一个策略,来指导agent与环境交互从而得到更好的收益。策略 π θ ( a ∣ s ) \pi_{\theta}(a|s) πθ(as)是以 θ \theta θ为参数的概率分布,代表不同状态下所采用的动作的概率分布。在学习的过程中不断的改变该函数的参数 θ \theta θ,从而改变应对环境的策略,以得到更好的奖励。当策略固定时,其所遍历的状态动作概率可以表示为
p θ ( s 1 , a 1 , … , s T , a T ) ⎵ p θ ( τ ) = p ( s 1 ) ∏ t = 1 T π θ ( a t ∣ s t ) p ( s t + 1 ∣ s t , a t ) \underbrace {{p_\theta }\left( {{{\bf{s}}_1},{{\bf{a}}_1}, \ldots ,{{\bf{s}}_T},{{\bf{a}}_T}} \right)}_{{p_\theta }(\tau )} = p\left( {{{\bf{s}}_1}} \right)\prod\limits_{t = 1}^T {{\pi _\theta }} \left( {{{\bf{a}}_t}|{{\bf{s}}_t}} \right)p\left( {{{\bf{s}}_{t + 1}}|{{\bf{s}}_t},{{\bf{a}}_t}} \right) pθ(τ) pθ(s1,a1,,sT,aT)=p(s1)t=1Tπθ(atst)p(st+1st,at)
对单个状态而言,其到达概率为:
ρ π ( s ′ ) = ∫ S ∑ t = 1 ∞ γ t − 1 p 1 ( s ) p ( s → s ′ , t , π ) d s \rho^{\pi}(s')=\int_{\mathcal{S}} \sum_{t=1}^{\infty} \gamma^{t-1} p_{1}(s) p\left(s \rightarrow s^{\prime}, t, \pi\right) \mathrm{d} s ρπ(s)=St=1γt1p1(s)p(ss,t,π)ds
那么在策略 π θ ( a ∣ s ) \pi_{\theta}(a|s) πθ(as)下得到的期望收益可以表示为:
J ( π θ ) = ∫ S ρ π ( s ) ∫ A π θ ( s , a ) r ( s , a ) d a d s = E s ∼ ρ π , a ∼ π θ [ r ( s , a ) ] \begin{aligned} J\left(\pi_{\theta}\right) &=\int_{\mathcal{S}} \rho^{\pi}(s) \int_{\mathcal{A}} \pi_{\theta}(s, a) r(s, a) \mathrm{d} a \mathrm{d} s \\ &=\mathbb{E}_{s \sim \rho^{\pi}, a \sim \pi_{\theta}}[r(s, a)] \end{aligned} J(πθ)=Sρπ(s)Aπθ(s,a)r(s,a)dads=Esρπ,aπθ[r(s,a)]
实际上 DDPG是DPG算法利用深度神经网络去逼进 策略 π θ ( a ∣ s ) \pi_{\theta}(a|s) πθ(as)和期望 Q Q Q Q Q Q函数的更新 需要与DQN类似:
Q ∗ ( s , a ) = Q ( s , a ) + α ( r + γ max ⁡ a ′ Q ( s ′ , a ′ ) − Q ( s , a ) ) Q^{*}(s, a)=Q(s, a)+\alpha\left(r+\gamma \max _{a^{\prime}} Q\left(s^{\prime}, a^{\prime}\right)-Q(s, a)\right) Q(s,a)=Q(s,a)+α(r+γamaxQ(s,a)Q(s,a))
所以 Q Q Q函数更新的loss可以表示为:
L ( θ ) = E [ ( r + γ max ⁡ a ′ Q ( s ′ , a ′ ; θ ) − Q ( s , a ; θ ) ) 2 ] L(\theta)=E\left[\left(r+\gamma \max _{a^{\prime}} Q\left(s^{\prime}, a^{\prime} ; \theta\right)-Q(s, a ; \theta)\right)^{2}\right] L(θ)=E[(r+γamaxQ(s,a;θ)Q(s,a;θ))2]
这样我们需要2组神经网络,其中一组 用来生成现在的状态S和动作A 另一组 用于生成 未来 Q Q Q函数估值 Q ( s ′ , a ′ ; θ ) Q\left(s^{\prime}, a^{\prime} ; \theta\right) Q(s,a;θ) 一组用于更新当前 Q ( s , a ; θ ) Q(s, a ; \theta) Q(s,a;θ)网络。

这个图盗的 ,但是原图在哪 我忘记了。
OPENAI-Baeslines-详解(三)-DDPG中文_第1张图片

调用DDPG

根据OPENAI-Baeslines-详解(一)中,需要在learning中传入的DDPG的参数。

在DDPG进行学习的时候,分为多个epoch。

每个epoch 中 有进行 多个cycles ,每个cycles ,进行rollout次采样 、train_steps次训练和eval_steps次评估。

total_step = epochs * epoch_cycles* rollout

总步数= 总回合数 * 每个回合的循环运行次数 * 每个回合与环境交互的次数。

一个回合 不等于 一个episode 。

由于可以使用多个环境并行采样,所以 在一个cycle中 多个环境同时采样,每个环境都采样rollout次,无论这个环境是否done。

有可能这个环境已经提前done了 ,他也要继续采样,到rollout次结束。

训练步数和 评估步数是不算在其中的

  • 必备参数
network, env,
seed=None,
 
 # 总步数 和总回合数 只能存在一个
 # 若两个都不存在,那么epoch为500 
total_timesteps=None,             # 总步数
nb_epochs=None,                   # 总回合数 
nb_epoch_cycles=20,               # 每个回合的循环运行次数
nb_rollout_steps=100,             # 每个回合与环境交互的次数
nb_train_steps=50,                # 每个回合训练次数
nb_eval_steps=100,                # 每个回合的评估次数
eval_env=None, 			    # 在每个回合训练完成之后,开始测试环境的步数。

render=False,                     # 是否显示交互
render_eval=False,
noise_type='adaptive-param_0.2',

  • 超参数
gamma=0.99,
critic_l2_reg=1e-2,                 # critic正则化约束
actor_lr=1e-4,                      # actor 学习率
critic_lr=1e-3,                     # critic 学习率
tau=0.01,                           # 软切换 的参数
**network_kwarg                     # 网络参数
  • 技巧参数参数
reward_scale=1.0,                   # 奖励的剪裁
normalize_returns=False,            # 
normalize_observations=True,        # 是否对噪声归一化
popart=False,                       # 自适应Q值剪裁
clip_norm=None,			    # 将输出的模裁剪到一定范围内
#如果输出的为t 那么操作为t * clip_norm / l2norm(t)
batch_size=64, # per MPI worker
param_noise_adaption_interval=50,3

观察baseline中的输出

再运行程序的最后会得到progress.csv 输出的结果分为三个方面:

  • 样本的输出

    • ‘ret_rms_mean’,‘ret_rms_std’
    • ‘obs_rms_mean’,‘obs_rms_std’ # 固定样本的观察的 均值 和 方差
    • ‘reference_Q_mean’,‘reference_Q_std’
      固定样本的Q值的 均值 和 方差 由样本的动作和状态 经由 critic 直接进行计算。
    • ‘reference_actor_Q_mean’,‘reference_actor_Q_std’
      固定样本的Q值 的 均值 和 方差 由样本状态 经由actor 输出动作 然后 给critic 进行计算。
    • ‘reference_action_mean’ ‘reference_action_std’
      动作均值和方差
    • ‘reference_perturbed_action_mean’ ‘reference_perturbed_action_std’
      加入噪声之后的动作均值和方差
  • 本次epoch 的样本的输出

    • ‘rollout/return’ # 从训练开始到现在的奖励均值
    • ‘rollout/return_std’ # 从训练开始到现在的奖励方差
    • ‘rollout/return_history’ # 100步 奖励均值
    • ‘rollout/return_history_std’ # 100步 奖励方差
    • ‘rollout/episode_steps’ # 从训练开始到现在的 每个episode 的步数。
    • ‘rollout/actions_mean’ # 从训练开始到现在的动作平均
    • ‘rollout/Q_mean’ # 从训练开始到现在的Q值平均
  • 总共的

    • ‘train/loss_actor’ # 本epoch 的actor的loss

    • ‘train/loss_critic’ # 本epoch 的critic的loss

    • ‘train/param_noise_distance’ # 本epoch 的actor的loss

    • ‘total/duration’ # 总共持续的时间

    • ‘total/steps_per_second’ # 每一步所花的时间

    • ‘total/episodes’ # 总共完成的回合数

    • ‘rollout/episodes’

      一个epoch完成的回合数 episodes(这边有一个小bug,弟124行的 epoch_episodes = 0 应该在for循环下面)
    • ‘rollout/actions_std’ # 动作平均

Baseline 中的DDPG

DDPG文件夹下包含以下5个文件:

  • ddpg 主要程序 主要是 runner
  • ddpg—learner DDPG算法核心 主要是生成agent
  • memory 记忆库
  • models 神经网络
  • noise 增加噪声

DDPG 主程序

初始化

建立网络 63~65行

memory = Memory(limit=int(1e6), action_shape=env.action_space.shape, observation_shape=env.observation_space.shape) # 创建记忆库
critic = Critic(network=network, **network_kwargs) # critic 网络
actor = Actor(nb_actions, network=network, **network_kwargs) # actor 网络

67~84行 创建noise模型 ,noise 主要作用是用于增大探索

89行 调用ddpg—learner 创建agent 并开始循环与环境交互。

这里可以同时对多个环境 进行探索。

每个循环 有 epoch 、cycle、

每个epoch 需要有多个cycle 每个 cycle 中 rollout_step 次与环境交互 train_step 次进行训练。


for epoch in range(nb_epochs):   
    for cycle in range(nb_epoch_cycles):

与环境交换阶段

        # reset环境
        if nenvs > 1:     
            agent.reset()
        for t_rollout in range(nb_rollout_steps):   
            # 输出动作
            action, q, _, _ = agent.step(obs, apply_noise=True, compute_Q=True)
         	  # 动作都是归一化在-1到1之间
            new_obs, r, done, info = env.step(max_action * action)  

            t += 1
            if rank == 0 and render:
                env.render()
            episode_reward += r
            episode_step += 1

            # 存进memory
            epoch_actions.append(action)
            epoch_qs.append(q)
            agent.store_transition(obs, action, r, new_obs, done) 
            # 新旧 状态更新
            obs = new_obs
            for d in range(len(done)):  # 对每一个agent进行reset
                if done[d]:
                    if nenvs == 1:
                        agent.reset()

训练阶段

for t_train in range(nb_train_steps):
   	# 噪声更新
	if memory.nb_entries >= batch_size and t_train % param_noise_adaption_interval == 0:
      distance = agent.adapt_param_noise()
      epoch_adaptive_distances.append(distance)
     # agent 训练
   	cl, al = agent.train()

##### ddpg—learner

该类下,主要包含了各种DDPG中所需要包含的操作,包括利用状态值的actor 和critic 的 前向传播

、保存数据到经验池、从经验池提取数据 进行 后向传播训练、噪声的增加以及初始化等工作。

1、创建target—net、及其更新函数

创建target—network 120-126行

target_actor = copy(actor)          
target_actor.name = 'target_actor'
self.target_actor = target_actor

target_critic = copy(critic)
target_critic.name = 'target_critic'
self.target_critic = target_critic

创建 target—net的更新

# 先创建单个网络函数   36行定义的函数
def get_target_updates(vars, target_vars, tau)
# 返回的是两组操作op,一组是硬更新 一组是软更新。
# 每组更新都是一个对每一个参数 进行 更新。
	return tf.group(*init_updates), tf.group(*soft_updates)

# 2个网络的更新函数   149行 class 中定义的函数	
def setup_target_network_updates(self)
	可以得到self.target_init_updates  self.target_soft_updates 
2、actor 和critic 的 前向传播

128行 首先需要创建loss 以及 创建actor 与 critic之间的链接

# actor
self.actor_tf = actor(normalized_obs0)
# critic 输入中的动作位置 为placeholder  
self.normalized_critic_tf = critic(normalized_obs0, self.actions)
self.critic_tf = denormalize(tf.clip_by_value(self.normalized_critic_tf,self.return_range[0], self.return_range[1]), self.ret_rms)

# critic 输入中的动作位置 为actor的输出
self.normalized_critic_with_actor_tf = critic(normalized_obs0, self.actor_tf, reuse=True)
self.critic_with_actor_tf =denormalize(tf.clip_by_value(self.normalized_critic_with_actor_tf, self.return_range[0], self.return_range[1]), self.ret_rms)

# target Q值计算
Q_obs1 = denormalize(target_critic(normalized_obs1, target_actor(normalized_obs1)), self.ret_rms)
self.target_Q = self.rewards + (1. - self.terminals1) * gamma * Q_obs1

259行 step 函数 是在每次交互过程中 ,根据当前状态 前向传输。根据当前状态 求取动作和Q值

def step(self, obs, apply_noise=True, compute_Q=True):
	feed_dict = {self.obs0: U.adjust_shape(self.obs0, [obs])}# 送入数据
	# 利用网络计算动作和Q值 
      action, q = self.sess.run([actor_tf, self.critic_with_actor_tf],	feed_dict=feed_dict)  
      # 之后是为了增加噪声
      noise = self.action_noise()
	action += noise
      action = np.clip(action, self.action_range[0], self.action_range[1])
3、反向传播

172 行 创建actor 网络训练

∇ θ J ( π θ ) \nabla_{\theta} J\left(\pi_{\theta}\right) θJ(πθ) Q Q Q对actor的参数求导数。

采用的是 利用action 的输出作为输入的critic的输出

因为经验回放 更新actor的时候是对当前actor的参数求导,所以必须对当前actor输入state 然后求得action 再将此时的action和state 送入critic ,并最后得到Q值 来更新 actor 参数。

def setup_actor_optimizer(self):
	self.actor_loss = -tf.reduce_mean(self.critic_with_actor_tf)# Q值
	self.actor_grads = U.flatgrad(self.actor_loss, self.actor.trainable_vars, clip_norm=self.clip_norm) # 计算梯度
      self.actor_optimizer = MpiAdam(var_list=self.actor.trainable_vars,beta1=0.9, beta2=0.999, epsilon=1e-08)

183 行 创建critic 网络训练

更新critic的时候,从经验库中取得的数据,其reward 是当时state-action所得到的,而此时critic网络参数经由多次训练之后,发生了非常大的变化, 所以必须用当前的网络在计算一遍Q值然后,利用当前target 网络Q值和 当前 main 网络Q值 加上当时的reward 重新计算。

def setup_critic_optimizer(self):
 	normalized_critic_target_tf = tf.clip_by_value(normalize(self.critic_target, self.ret_rms), self.return_range[0], self.return_range[1])
	self.critic_loss = tf.reduce_mean(tf.square(self.normalized_critic_tf - normalized_critic_target_tf))
	# 187-196 在这里会对critic的loss 增加 l2 约束。
      self.critic_grads = U.flatgrad(self.critic_loss, self.critic.trainable_vars, clip_norm=self.clip_norm)  # 计算梯度 
      self.critic_optimizer = MpiAdam(var_list=self.critic.trainable_vars,
            beta1=0.9, beta2=0.999, epsilon=1e-08) # 反向训练      

289 行 train

def train(self):
	# 经验池随机采样
	batch = self.memory.sample(batch_size=self.batch_size)
	ops = [self.actor_grads, self.actor_loss, self.critic_grads, self.critic_loss]
      # 根据采样数据重新计算Q值等。
      actor_grads, actor_loss, critic_grads, critic_loss = self.sess.run(ops, feed_dict={
            self.obs0: batch['obs0'],
            self.actions: batch['actions'],
            self.critic_target: target_Q,
        })
      # 训练328 行
	self.actor_optimizer.update(actor_grads, stepsize=self.actor_lr)
      self.critic_optimizer.update(critic_grads, stepsize=self.critic_lr)
4、噪声

噪声主要是为了增加action的探索作用。噪声主要有两种 一种是 静态参数的 一种是 动态参数(未使用)

噪声的生成主要是通过首先对actor 进行 copy (155行函数)

def setup_param_noise(self, normalized_obs0):
	param_noise_actor = copy(self.actor)
     self.perturbed_actor_tf = param_noise_actor(normalized_obs0)
     

然后对copy后的actor的输出增加噪声

#50 行
def get_perturbed_actor_updates(actor, perturbed_actor, param_noise_stddev):
	# 增加均值为零 方差为param_noise_stddev的 高斯噪声
	updates.append(tf.assign(perturbed_var, var + tf.random_normal(tf.shape(var), mean=0., stddev=param_noise_stddev)))

执行增加噪声, 在step函数中直接选择

#259行 
if self.param_noise is not None and apply_noise:
    actor_tf = self.perturbed_actor_tf  # 注意 这里只选择了参数固定的噪声。
else:
    actor_tf = self.actor_tf

其他函数 def reset(self):# 初始化噪声

5、功能函数
# 初始化 将所有网络初始化、优化器初始化、硬更新一次target网络
def initialize(self, sess):
# 软更新target_net
def update_target_net(self):

# 通过从 数据库中采样数据并得到所有结果的函数
def setup_stats(self):
def get_stats(self):

你可能感兴趣的:(baseline)