Metro1998 / hppo-in-traffic-signal-control

34 stars 2 forks source link

hppo算法更新时连续动作空间为什么不计算交叉熵? #4

Closed huang312 closed 6 months ago

huang312 commented 6 months ago

您好,非常感谢您分享的hppo算法的代码!我从中学到了很多。 但我在阅读代码时有一些地方没有看明白,希望能和您讨论一下。

1.第一个问题是在PPO_Hybrid类中的compute_loss_pi方法中,计算连续动作网络的loss时,没有用到该网络的交叉熵,请问为什么要这样计算呢?

    def compute_loss_pi(self, data):
        obs, act_dis, act_con, adv, _, logp_old_dis, logp_old_con, _ = data

        logp_dis, logp_con, dist_entropy_dis, dist_entropy_con = self.agent.get_logprob_entropy(obs, act_dis, act_con)
        logp_con = logp_con.gather(1, act_dis.view(-1, 1)).squeeze()
        # dist_entropy = dist_entropy.gather(1, ptr.view(-1, 1)).squeeze()
        ratio_dis = torch.exp(logp_dis - logp_old_dis)
        ratio_con = torch.exp(logp_con - logp_old_con)
        clip_adv_dis = torch.clamp(ratio_dis, 1 - self.eps_clip, 1 + self.eps_clip) * adv
        clip_adv_con = torch.clamp(ratio_con, 1 - self.eps_clip, 1 + self.eps_clip) * adv
        loss_pi_dis = - (torch.min(ratio_dis * adv, clip_adv_dis) + self.coeff_entropy * dist_entropy_dis).mean()  # TODO
        # 这里计算连续动作网络的loss时似乎没有计算连续动作网络的entropy
        loss_pi_con = - (torch.min(ratio_con * adv, clip_adv_con)).mean()

2.第二个问题也是在上面这个方法中,我看到连续动作的对数概率计算如下,我的理解是连续动作输出了所有的离散动作对应的连续动作的值,然后通过这种方式来获得最终输出的连续动作的值,这个理解正确吗?连续动作网络可以只输出一个值作为智能体的连续动作输出吗?

 logp_con = logp_con.gather(1, act_dis.view(-1, 1)).squeeze()

3.第三个问题是关于hppo算法的实现的,我感觉这个实现中连续动作空间和离散动作空间似乎是完全独立的两个网络,而我对论文的理解是两个网络共用相同的state encoder网络,请问为什么要这样做,另外这样做神经网络是不是不能获得两种动作之间的联系?

4.第四个问题想请教一下我目前遇到的一个问题。我希望使用hppo算法,然后我的连续动作空间中的一部分值有一个限制,就是他们之和需要小于等于1,这种情况该如何设计?我目前的想法是,连续动作网络输出后单独把这些值找出来通过一个softmax再输出

希望能得到您的

Metro1998 commented 6 months ago

1.参照博客 https://iclr-blog-track.github.io/2022/03/25/ppo-implementation-details/ ,第十条的第三小点,但是这个我也没有很足的把握,在continuous actor 中把交叉熵去掉是为了更高的性能,但是可能会牺牲掉一定的泛化性,这个加与不加可以自己斟酌。

  1. 假设说你的离散动作有5个,那么你的continuous actor的heads数量也是5个,每一个对应着相应的离散动作,他的实际意义是,假设我选择了某个离散动作,那么气相对应的连续动作应该是什么。因此,从这个角度,在训练的时候,假设有一个transition (obs, action_discrete, action_continuous, reward, next_obs) 它反向传导的时候就之应该训练到continous actor 中 action discrete 所对应的head(当然上面的mlp也会训练到)。 3.参照博客 https://iclr-blog-track.github.io/2022/03/25/ppo-implementation-details/ ,第十三条,独立的encoder往往会有更好的效果,直觉上来讲 approximate state value 以及 action 可能会关注观测不同层面的东西。 4.其实没太懂你的意思,建议保留原本的输出,具体的动作后期再放缩(包括你说的限制),不要把为了适应自己环境的放缩放在网络中。
huang312 commented 6 months ago

感谢回复!但是我还有一些不懂的地方

1.第一个问题我明白了

2.第二个问题我明白你说的意思了。那在这个例子中连续动作网络的heads数量可以设为1吗,这样的话,训练的时候不同的离散动作训练的是一个head?不知道这样是否合理?

3.第三个问题我可能没说清楚,我不是说actor网络和value网络要share,而是说discrete actor 和 continuous actor是不是要share?这个仓库中的discrete actor 和 continuous actor是独立的,但是我理解hppo论文中应该是share的?

4.这里说的“后期”指的什么时候呢,是输入到环境之前吗,还是放在choose_action中?那在buffer中存储的动作也应该是网络原本的输出吗?

huang312 commented 6 months ago

另外想请问一下这里,在ActorCritic_Hybrid类中的act方法中,action_con的输出维度似乎是action_dim,但我理解这里连续动作应该只输出一个维度的动作?

    def act(self, state):
        state_value = self.critic.forward(state)

        action_probs = self.actor_dis(state)
        dist_dis = Categorical(action_probs)
        action_dis = dist_dis.sample()
        logprob_dis = dist_dis.log_prob(action_dis)

        mean = self.actor_con(state)
        std = torch.clamp(F.softplus(self.log_std), min=0.01, max=0.6)
        dist_con = Normal(mean, std)
        action_con = dist_con.sample()
        logprob_con = dist_con.log_prob(action_con)

        return state_value, action_dis, action_con, logprob_dis, logprob_con
Metro1998 commented 6 months ago

不好意思,最近在忙大论文。

  1. 参考HPPO原文第三节中关于Fig3的描述:The parallel actors perform action-selection and parameter-selection separately: one discrete actor network learns a stochastic policy πθdto select the discrete action a and one continuous actor network learns a stochastic policy πθc to choose the continuous parameters xa1 , xa2 , . . . , xak for all discrete actions. 这也就说明你离散动作的选择有几个你的连续动作就要有相应的heads。
  2. emmmm,这个问题其实是性能导向型的,也就是说哪个性能好用哪个,这个你得自己尝试。HPPO原文确实画了share的网络结构,但是它没有强调网络结构复用的必要性(一定得这么设计)。另一个角度来讲无论是dis_actor 还是 con_actor它们都是approximator,share或者seperate只是实现手段的区别不存在理论上的问题
  3. 老实说具体我也不是很懂,关于加入约束。一般的做法是由网络输出的都是其最原本的分布,在加入到环境的时候进行线性操作。
  4. 我其实将这个操作留到了环境中 like true_con = action_con[action_dis](不好意思我在act中没有体现出来就导致有点迷惑), 在2中,我已经解释过了,actor_con最原本的输出的维度是action_dim,需要由被选中的action_dis作为index。
Metro1998 commented 6 months ago

另外想请问一下这里,在ActorCritic_Hybrid类中的act方法中,action_con的输出维度似乎是action_dim,但我理解这里连续动作应该只输出一个维度的动作?

    def act(self, state):
        state_value = self.critic.forward(state)

        action_probs = self.actor_dis(state)
        dist_dis = Categorical(action_probs)
        action_dis = dist_dis.sample()
        logprob_dis = dist_dis.log_prob(action_dis)

        mean = self.actor_con(state)
        std = torch.clamp(F.softplus(self.log_std), min=0.01, max=0.6)
        dist_con = Normal(mean, std)
        action_con = dist_con.sample()
        logprob_con = dist_con.log_prob(action_con)

        return state_value, action_dis, action_con, logprob_dis, logprob_con

应该是我的代码出了问题,可以参考issue 5