离线强化学习——BCQ论文代码笔记

BCQ —— Batch-Constrained Deep Reinforcement Learning

论文:https://arxiv.org/pdf/1812.02900.pdf

代码:https://github.com/sfujim/BCQ

提出背景

如果以标准的off-policy的方式(例如DQN和DDPG)在一个与要训练的agent毫不相关的数据集上训练的话,往往效果不好,会产生Extrapolation Error(外推误差)。意思就是,如果你用要训练的agent去在一个这个agent完全没接触过的固定数据集上,用传统的off-policy算法训练的话,效果会不好。

Extrapolation Error

论文提出了一种名为Extrapolation Error的误差。  

产生这种误差的原因可能有一下几点:  

1.Absent Data

固定的数据集往往无法包含所有的<state,action>对,这样就不好估计未在数据集中出现过的<state,action>的Q(s,a)。之后在使用训练好的agent去实操时,对于这种Q(s,a)的评估会有较大偏差。比如要训练一个AI去玩QQ飞车,而提供的固定数据集中没有Q(弯道,漂移)这种pair。在训练完后,agent可能永远不会在弯道做出漂移这种操作,因为它会错误地估计在弯道进行漂移操作之后的累计reward。

2.Model Bias

这个不是很理解,模型当然有偏差啊。

3.Training Mismatch

即使有充足的数据,由于用的数据不是用要训练的agent采集的,用数据集里的<state,action>pair预估Q_value时会很不准确。是因为采样的这些数据,肯定不是随机采样的,是由一个agent按照它的Q_value function去采样的,它选择Q_value大的action。而这些<state,action>对于要训练的agent来说,可能是十分陌生的,于是会造成Q_value预估不准确,而且是经常预估不准确,导致训练效果不好。

验证实验

1.Final Buffer

用DDPG算法训练一个agent边采样边学100w步,加入高斯噪声保证充分探索。这样就得到了一个很大的数据集,几乎包含了所有<state,action>pair。再用这个数据集以off-policy的方法去训练agent。  

结果发现即使数据集充分大,off-policy训练出的agent的Average情况也没behavior agent好。Q_value的估计也不是很稳定。

2.Concurrent

用DDPG算法训练一个agent边采样边学100w步,加入高斯噪声保证充分探索。同时有另一个agent和它一起用采样的数据学,也就是用相同的数据。相当于一个是用自己采样出的buffer里的数据去学,一个是用别人采样的数据去学。两个agent唯一的区别就是初始参数不一样,用的数据完全一样。

发现即使是用的一样的数据,用自己采样出来的数据学也好一些。

3.Imitation

用训练好的DDPG agent去采样,不探索,全选最佳动作。这样就得到了一个全是专家轨迹的数据集,几乎包含了所有<state,action>pair。再用这个数据集去训练off-policy agent。

用专家数据学的话,由于采样出每一步得到的期望都很高,所以模型会过分高估Q_value,认为怎么都会得到很多reward。我觉得这就像监督学习二分类问题里,如果数据正类负类数量要均衡。
 

BCQ大致思路

用VAE去根据state来生成action,由于VAE看到的都是出现过的<state,action>pair,所以VAE根据state去生成的action会和之前VAE看见过的相似性很高。当然只用VAE来生成action当然不行,这样会缺乏探索性,BCQ里的Actor是一个扰动网络,将输入的state和action加入扰动,提高action的多样性。

算法流程

看伪代码各种符号特别复杂,还是看代码来的直接一些。

Actor

这里的Actor就是perturbation network $\xi$ ,它的参数是\theta  

Actor的输入是state和action,输出扰动后的action。

扰动网络的目的在于提供action的多样性

class Actor(nn.Module):

    def __init__(self, state_dim, action_dim, max_action, phi=0.05):

        super(Actor, self).__init__()

        self.l1 = nn.Linear(state_dim + action_dim, 400)

        self.l2 = nn.Linear(400, 300)

        self.l3 = nn.Linear(300, action_dim)

       

        self.max_action = max_action

        self.phi = phi




    def forward(self, state, action): #在action基础上做扰动

        a = F.relu(self.l1(torch.cat([state, action], 1)))

        a = F.relu(self.l2(a))

        a = self.phi * self.max_action * torch.tanh(self.l3(a)) #使用tanh激活函数将值映射到(-1,1)

        #再乘上action最大值和扰动因子得到扰动大小a

        #用a+action得到扰动后的action

        return (a + action).clamp(-self.max_action, self.max_action) #使用裁剪,控制范围在(-max_action,max_action)

Critic

这里的Critic就是 $Q$ 网络,有$Q_1$$Q_2$ 两套参数。

就是常用一个双Q trick,让Q_value的预测更准。

Critic的输入是state和action,输出两个Q值。

class Critic(nn.Module):

    def __init__(self, state_dim, action_dim):

        super(Critic, self).__init__()

        self.l1 = nn.Linear(state_dim + action_dim, 400)

        self.l2 = nn.Linear(400, 300)

        self.l3 = nn.Linear(300, 1)



        self.l4 = nn.Linear(state_dim + action_dim, 400)

        self.l5 = nn.Linear(400, 300)

        self.l6 = nn.Linear(300, 1)




    def forward(self, state, action):

        q1 = F.relu(self.l1(torch.cat([state, action], 1)))

        q1 = F.relu(self.l2(q1))

        q1 = self.l3(q1)



        q2 = F.relu(self.l4(torch.cat([state, action], 1)))

        q2 = F.relu(self.l5(q2))

        q2 = self.l6(q2)

        return q1, q2 #两个Q网络




    def q1(self, state, action):

        q1 = F.relu(self.l1(torch.cat([state, action], 1)))

        q1 = F.relu(self.l2(q1))

        q1 = self.l3(q1)

        return q1

VAE

VAE在使用时,只会用到decode方法,输入是state,让z随机,然后输出action。

class VAE(nn.Module):

    def __init__(self, state_dim, action_dim, latent_dim, max_action, device):

        super(VAE, self).__init__()

        self.e1 = nn.Linear(state_dim + action_dim, 750) #encoder第一层

        self.e2 = nn.Linear(750, 750) #encoder第二层



        self.mean = nn.Linear(750, latent_dim)

        self.log_std = nn.Linear(750, latent_dim)



        self.d1 = nn.Linear(state_dim + latent_dim, 750) #decoder第一层

        self.d2 = nn.Linear(750, 750) #decoder第二层

        self.d3 = nn.Linear(750, action_dim) #decoder第三层



        self.max_action = max_action

        self.latent_dim = latent_dim

        self.device = device



    def decode(self, state, z=None): #根据state与z生成action

        # 如果从VAE采样,就把z裁剪到(-0.5,0.5)

        if z is None:# 如果z未指定,则通过mean、std当场采样

            z = torch.randn((state.shape[0], self.latent_dim)).to(self.device).clamp(-0.5,0.5) #(batch_size,latent_dim)

        a = F.relu(self.d1(torch.cat([state, z], 1)))

        a = F.relu(self.d2(a))



        return self.max_action * torch.tanh(self.d3(a)) #(batch_size,action_dim)

   

    def forward(self, state, action):

        # 输入state、真实样本action

        z = F.relu(self.e1(torch.cat([state, action], 1)))

        z = F.relu(self.e2(z))

        #z用来算mean和log_std

        mean = self.mean(z) #mean,后面用来算KL loss

        # Clamped for numerical stability

        log_std = self.log_std(z).clamp(-4, 15)  #为什么是[-4,15]?



        std = torch.exp(log_std) #后面用来算KL loss

        z = mean + std * torch.randn_like(std)

        #torch.randn_like是产生和输入tensor形状一样的满足N~(0,1)分布的随机数

        #z ~ N(mean,std)  

        #z.shape (batch,latent_dim)



       

        u = self.decode(state, z) # (batch_size,action_dim)

        # 根据state和z重建action

        return u, mean, std

Train

def train(self, replay_buffer, iterations, batch_size=100):

    for it in range(iterations):

        # 从replay_buffer中随机采样出batch_size个时间步的数据

        state, action, next_state, reward, not_done = replay_buffer.sample(batch_size)



        # 训练VAE

        recon, mean, std = self.vae(state, action)  #

        recon_loss = F.mse_loss(recon, action) # VAE重建loss

        KL_loss = -0.5 * (1 + torch.log(std.pow(2)) - mean.pow(2) - std.pow(2)).mean() #KL loss

        vae_loss = recon_loss + 0.5 * KL_loss # VAE loss

        self.vae_optimizer.zero_grad()

        vae_loss.backward()

        self.vae_optimizer.step()

用VAE的encoder算出mean和std,然后用mean和std在正太分布中采样出z,再用z和state去重建action。  

VAE的loss由重建loss和KL loss组成。  

        # 训练Critic

        with torch.no_grad():

            # Duplicate next state 10 times

            next_state = torch.repeat_interleave(next_state, 10, 0) #在第0维重复10次

            # 输入的next_state维度(batch_size,state_dim),重复10遍变成(batch_size*10,state_dim)



            # Compute value of perturbed actions sampled from the VAE

            target_Q1, target_Q2 = self.critic_target(next_state, self.actor_target(next_state, self.vae.decode(next_state)))

            # 维度(batch_size*10,)



            # Soft Clipped Double Q-learning

            target_Q = self.lmbda * torch.min(target_Q1, target_Q2) + (1. - self.lmbda) * torch.max(target_Q1, target_Q2)

            # Take max over each action sampled from the VAE

            target_Q = target_Q.reshape(batch_size, -1).max(1)[0].reshape(-1, 1)

            # 取10个生成动作样本中最大的,最终维度(batch_size,)



            #根据bellman公式,算出target_Q

            target_Q = reward + not_done * self.discount * target_Q



        current_Q1, current_Q2 = self.critic(state, action) #算当前Q_value

        # current_Q和这个tartget_Q越接近越好

        critic_loss = F.mse_loss(current_Q1, target_Q) + F.mse_loss(current_Q2, target_Q)



        self.critic_optimizer.zero_grad()

        critic_loss.backward()

        self.critic_optimizer.step()

1.用VAE根据next_state生成n(代码里是10)个的action

2.然后把next_state和这些action输入到Target_Actor里,生成扰动后的action。  

3.在把扰动后的action和next_state输入到Target_Critic去算出target Q_value,并在n个Q_value里选最大的。  

如果n足够大,就有点像Q-learning了。这里用了采样的方式来近似 argmax_a,n越大,采样越多就越能模拟整个的action space,最后选最大的就越接近真正的argmax。  

当n=1,\xi=0时,就像behavioral cloning了,每次选择的动作就是VAE采样出来的动作,而vae采样出的动作又很大概率是由之前出现过的<state,action>pair,所以agent会偏向于模仿数据集里的<state,action>pair。

4.当前Q_value就用state和action来计算。  

5.之后更新当前的Critic


 

        # Pertubation Model / Action Training

        #获得扰动后的action 训练扰动网络 这里的actor是做扰动作用

        sampled_actions = self.vae.decode(state) #用的是vae中随机的z

        perturbed_actions = self.actor(state, sampled_actions) #扰动后action



        # 用DPG的方式update Actor

        actor_loss = -self.critic.q1(state, perturbed_actions).mean()

        #这个<state,action>pair在critic里的评分越高越好

       



        self.actor_optimizer.zero_grad()

        actor_loss.backward()

        self.actor_optimizer.step()




        # Update Target Networks

        for param, target_param in zip(self.critic.parameters(), self.critic_target.parameters()):

            target_param.data.copy_(self.tau * param.data + (1 - self.tau) * target_param.data)



        for param, target_param in zip(self.actor.parameters(), self.actor_target.parameters()):

            target_param.data.copy_(self.tau * param.data + (1 - self.tau) * target_param.data)

用vae根据state采样出action  

把state和action输入到扰动网络里输出扰动后的action  

用critic评估state扰动后的action的Q值,越大越好  

最后再用移动平均法更新actor和critic的target network

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值