基于Pendulum的DDPG简洁示例【TF 2.X】

前言

  现在网上已有的DDPG实现多是TF 1.X时的风格,相较之下实现不够优雅,也不够简洁直观。而在使用TF 2.X实现的时候又有诸多坑在等着咱,一边填坑一边还得防止自己给自己挖坑,要是一个不小心这篇文章就没了。。。为了便于像我一样踩坑不断的小伙伴参考,在此记下实现过程中一些需要注意的地方。在试验的过程中,我还发现了TF 2.X默认开启的动态图机制会对运行速度有极大的负面影响,因此也顺便记下如何关闭动态图机制以及解决在这个过程中checkpoint manager保存模型时默认session为None的问题。

构建模型

from collections import deque
import gym
import numpy as np
import os
import pickle
import random
import tensorflow as tf
from tensorflow.keras import Input
import tensorflow.keras.backend as K
from tensorflow.keras.layers import *

tf.compat.v1.disable_eager_execution() # 关闭动态图机制

class DDPGTrainer():
    def __init__(
        self, 
        n_features, 
        n_actions, 
        sample_size=128, 
        tau=0.99, 
        gamma=0.95, 
        epsilon=1.0, 
        epsilon_decay=0.995, 
        epsilon_min=0.01, 
        a_lr=0.0001, 
        c_lr=0.001
    ):
        self.tau = tau
        self.memory_buffer = deque(maxlen=4000)
        self.sample_size = sample_size
        self.gamma = gamma 
        self.epsilon = epsilon
        self.epsilon_decay = epsilon_decay
        self.epsilon_min = epsilon_min
        self.a_lr = a_lr
        self.c_lr = c_lr
        self.n_features = n_features
        self.n_actions = n_actions
        
        self.actor, self.critic = self.build_model()
        self.target_actor, self.target_critic = self.build_model()
        self.target_actor.set_weights(self.actor.get_weights())
        self.target_critic.set_weights(self.critic.get_weights())
        
    def build_model(self):
        s_input = Input([self.n_features])
        a_input = Input([1])
        
        # actor
        x = Dense(units=40, activation='relu')(s_input)
        x = Dense(units=40, activation='relu')(x)
        x = Dense(units=1, activation='tanh')(x)
        action = Lambda(lambda x: x * self.n_actions)(x)
        actor = tf.keras.models.Model(inputs=s_input, outputs=action)
        
        # critic
        x = K.concatenate([s_input, a_input], axis=-1)
        x = Dense(40, activation='relu')(x)
        x = Dense(40, activation='relu')(x)
        q_a_value = Dense(1, activation='linear')(x)
        critic = tf.keras.models.Model(inputs=[s_input, a_input], outputs=q_a_value)
        
        actor.add_loss(-critic([s_input, action])) # 最大化Q_a,注意有个负号

        ### 只训练actor
        critic.trainable = False
        actor.compile(optimizer=tf.keras.optimizers.Adam(self.a_lr))
        critic.trainable = True
        
        ### 只训练critic
        actor.trainable = False
        critic.trainable = True # 由于actor的计算图用到critic部分,actor.trainable变化会影响critic.trainable
        critic.compile(optimizer=tf.keras.optimizers.Adam(self.c_lr), loss='mse')
        actor.trainable = True
        return actor, critic
    
    def OU(self, x, mu=0, theta=0.15, sigma=0.2):
        '''Ornstein-Uhlenbeck噪声'''
        return theta * (mu - x) + sigma * np.random.randn(1) # shape: [1]

    def choose_action(self, state):
        action = self.actor.predict(state)[0][0] # shape: []
        noise = max(self.epsilon, 0) * self.OU(action)
        action = np.clip(action + noise, -self.n_actions, self.n_actions) # shape: [1]
        return action

    def store(self, state, action, reward, next_state, done):
        sample = (state, action, reward, next_state, done)
        self.memory_buffer.append(sample)

    def update_epsilon(self):
        if self.epsilon >= self.epsilon_min:
            self.epsilon *= self.epsilon_decay

    def update_model(self):
        samples = random.sample(self.memory_buffer, self.sample_size)
        states = np.array([sample[0] for sample in samples])
        actions = np.array([sample[1] for sample in samples])
        rewards = np.array([sample[2] for sample in samples])
        next_states = np.array([sample[3] for sample in samples])
        dones = np.array([sample[4] for sample in samples])

        next_actions = self.target_actor.predict(next_states)
        q_a_next = self.target_critic.predict([next_states, next_actions]) # q_a_next.shape: [self.sample_size, 1]
        y = rewards + self.gamma * q_a_next[:, 0] * ~dones  # y.shape: [self.sample_size]
        self.critic.fit([states, actions], y[:, None], verbose=0) 
        self.actor.fit(states, verbose=0)
        
    def update_target_model(self):
        actor_weights = self.actor.get_weights()
        critic_weights = self.critic.get_weights()
        actor_target_weights = self.target_actor.get_weights()
        critic_target_weights = self.target_critic.get_weights()
        for i in range(len(actor_target_weights)):
            actor_target_weights[i] = actor_target_weights[i] * self.tau + (1 - self.tau) * actor_weights[i]
        for i in range(len(critic_target_weights)):
            critic_target_weights[i] = critic_target_weights[i] * self.tau + (1 - self.tau) * critic_weights[i]
        self.target_actor.set_weights(actor_target_weights)
        self.target_critic.set_weights(critic_target_weights)
        
    def save(self, checkpoint_path='pendulum'):
        self.ckpt_manager.save()
        with open(f'{checkpoint_path}/epsilon.pkl', 'wb') as f:
            pickle.dump(self.epsilon, f)
        
    def load(self, checkpoint_path='pendulum'):
        ckpt = tf.train.Checkpoint(
            actor=self.actor,
            critic=self.critic,
            target_actor=self.target_actor,
            target_critic=self.target_critic,
            actor_optimizer = self.actor.optimizer,
            critic_optimizer = self.critic.optimizer,
        )
        self.ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=1)
        
        if os.path.exists(f'{checkpoint_path}/epsilon.pkl'):
            with open(f'{checkpoint_path}/epsilon.pkl', 'rb') as f:
                self.epsilon = pickle.load(f)
                
        if self.ckpt_manager.latest_checkpoint:
            status = ckpt.restore(self.ckpt_manager.latest_checkpoint)
            status.run_restore_ops() # 关闭动态图后需要添加这句执行restore操作

  构建模型过程中,需要注意actor由于计算loss时用到了critic模型,在设置actor.trainable时critic.trainable也会跟着改变。另外训练过程中数据的shape要合适,比如critic.fit时真实值y.shape需要与critic的输出一致。

训练模型并保存

  在静态图下,为了能够使用checkpoint manager保存模型,需要在构建模型之前创建一个默认session,否则checkpoint manager保存过程会报错session为None,之后如果不需要再保存模型可关闭session。具体代码如下

session = tf.compat.v1.InteractiveSession() # 关闭动态图后,ckpt_manager.save()需要有默认的session
env = gym.make('Pendulum-v0')
model = DDPGTrainer(env.observation_space.shape[0], env.action_space.high[0])
model.load() # 如果没有则不会加载
try:
    for episode in range(200):
        next_state = env.reset()
        reward_sum = 0
        for step in range(200):
            env.render()
            state = next_state
            action = model.choose_action(state[None])
            next_state, reward, done, _ = env.step(action)
            reward_sum += reward
            model.store(state, action, reward, next_state, done)

            if len(model.memory_buffer) > model.sample_size:
                model.update_model()
                model.update_target_model()
                model.update_epsilon()
        print(f'episode{episode} total reward: {reward_sum}')
    model.save()
finally:
    env.close()
session.close()

  训练效果如下
在这里插入图片描述
完整代码(Jupyter notebook)

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,DQN算法是一种比较经典的深度强化学习算法。Pendulum-v1是一个经典的强化学习环境,其中智能体需要控制一个单摆来保持竖直。下面是DQN算法在Pendulum-v1环境中的实现步骤: 1. 确定状态空间、动作空间和奖励函数:在Pendulum-v1环境中,状态空间为一个三维向量,包括单摆的角度、角速度和角加速度。动作空间为一个连续的动作,即施加的扭矩大小。奖励函数为当前状态下的负平方误差。 2. 构建深度神经网络:使用深度神经网络作为Q函数的估计器。神经网络的输入是当前状态,输出是每个动作的Q值。 3. 初始化经验回放缓存:使用经验回放机制来平衡数据的相关性和效率。将所有的经验数据存储在一个缓存池中,每次训练时从中随机采样一批数据进行训练。 4. 进行训练:在每个时间步中,智能体根据当前状态选择一个动作,使用选择的动作与环境进行交互,得到下一个状态和奖励,将这些经验加入经验回放缓存中。然后从经验回放缓存中随机采样一批数据进行训练,并更新深度神经网络的参数。 5. 执行策略:在每个时间步中,根据当前状态和深度神经网络的参数计算出每个动作的Q值,选择具有最大Q值的动作执行。 6. 调整超参数:根据实验效果调整超参数,如神经网络的结构、学习率、折扣因子等。 以上就是DQN算法在Pendulum-v1环境中的实现步骤,需要注意的是,由于动作空间是连续的,所以需要采用一些技巧来处理。比如可以使用深度确定性策略梯度(DDPG)算法来解决连续动作空间的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值