其他分享
首页 > 其他分享> > 强化学习:DDPG到MADDPG

强化学习:DDPG到MADDPG

作者:互联网

目录

策略梯度(Policy Gradient)

行动器-评判器方法(Actor-Critic)

Deterministic Policy Gradient

on-policy和off-policy

DPG

Deep Deterministic Policy Gradient

Multi-agent Deep Deterministic Policy Gradient

多智能体强化学习背景

MADDPG

MADDPG的实现

参考文献


本文主要关注于强化学习中基于策略梯度的方法。首先会简要介绍什么是策略梯度,什么是Actor-Critic算法,以及DPG算法,之后会重点讲述DDPG(Deep Deterministic Policy Gradient)算法,以及DDPG的多智能体版本:MADDG(Multi-agent Deep Deterministic Policy Gradient)。

策略梯度(Policy Gradient)

与基于策略梯度(Policy Based)的方法相对应的,是基于动作价值函数(Value Based)的方法。我们熟悉的DQN(Deep Q Learning)就是典型的基于动作价值函数的强化学习方法。它们都是先学习动作价值函数,然后根据估计的动作价值函数来选择动作,如果没有这个动作价值函数,那么策略也不会存在。但是Value Based的方法也存在一些问题,首先,Value Based的方法很难处理连续动作的问题,因为Value Based的方法是根据动作价值函数q(s,a),寻找能使得q最大的a,如果a是一个连续空间下的动作,这个问题就会很难处理。另外,Value Based的方法也很难输出一个随机策略,因为我们最后选择动作是去选择q(s,a)最大的,而不是基于一个a的概率,因此,我们期望能有一种方法,可以不经过价值函数,直接输出动作的概率,这就是Policy Based的方法。

如上所述,我们要学习的策略,可以形式化地表达为\pi(a|s,\theta)=Pr \{A_t = a | S_t = s, \theta_t = \theta\},也就是在t时刻,状态s下选择a动作的概率。我们讨论的策略参数θ的学习方法都是基于某种性能度量J(θ)的梯度,这些梯度是度量J(θ)对策略参数的梯度,我们的目标是最大化性能指标,所以它们的更新近似于J的梯度上升。 

                                                                                                                                        \theta_{t+1} = \theta_{t} + \alpha \widehat{\Delta J(\theta_{t} )} 

所有符合这个框架的方法都称为策略梯度法,不管它们是否还同时学习一个近似的价值函数。

我们基于策略梯度定理 \nabla J(\boldsymbol{\theta}) \propto \sum_{s} \mu(s) \sum_{a} q_{\pi}(s, a) \nabla \pi(a \mid s, \theta)来对参数进行优化。

有了对参数的优化方法,下面的问题就是这个策略函数\pi(a|s,\theta)具体应当是怎样的。我们可以简单看一下,在离散的情况下,策略梯度函数是怎样的。我们可以根据一个指数柔性最大化分布\pi(a \mid s, \boldsymbol{\theta}) \doteq \frac{e^{h(s, a, \boldsymbol{\theta})}}{\sum_{b} e^{h(s, b, \boldsymbol{\theta})}}选择。在这里h可以是任意的函数,它既可以是一个复杂的神经网络,也可以是一个简单的线性组合。在这里,我提一个问题,连续性的动作,我们是怎么基于\pi(a \mid s, \boldsymbol{\theta})来进行策略选择的?粗看这个公式,它输出的是在特定环境下,某个动作a出现的概率,这看起来好像和Q(s,a)没有啥区别,面对无限的连续空间,我们依然找不到那个最大的概率,那我们是怎么选择动作的呢?

想要进一步了解策略梯度算法的可以去学习REINFORCE蒙特卡洛策略梯度算法

我们对策略梯度的基本内容介绍到此为止,接下来我们看一种同时学习策略和价值函数的方法——Actor-Critic

行动器-评判器方法(Actor-Critic)

基于策略梯度的算法,比如蒙特卡洛策略梯度算法,都是基于回合更新的,学习比较缓慢,因此我们考虑使用时序差分的方法来消除这些不便。因此,我们可以考虑用一个Actor(继承策略梯度的方法)来学习策略,和一个Critic(继承Value based)的方法来实现单步更新,Critic就是在做对策略的评估,也就是Q_{w}(s, a) \approx Q^{\pi_{\theta}}(s, a)。基于这种想法,策略梯度的更新公式如下所示

                                                                                                                                    \nabla_{\theta} J(\theta) \approx \mathbb{E}_{\pi_{\theta}}\left[\nabla_{\theta} \log \pi_{\theta}(s, a) Q_{w}(s, a)\right]

一个基础的Actor-Critic算法的伪代码如下图所示,Actor-Critic算法非常难收敛,有兴趣的同学可以用两个简单的神经网络,跑一跑简单的cartpole问题,你就会明白这种网络有多难收敛了,因此我们要对Actor-Critic算法进行改进,让他能真正适用于复杂的任务。

 

 

Deterministic Policy Gradient

on-policy和off-policy

在讲解DPG算法之前,先区别两个概念on-policy和off-policy。关于这个问题,在知乎问题中,已经有了很多很好的回答,我在这里简单区别一下。在强化学习中,其实存在两个策略,行为策略和目标策略。行为策略就是我们在训练过程中,与环境交互所使用的策略,而目标策略则是我们期望在训练完成后实际与环境进行交互的策略。如果我们认为行为策略和目标策略是一个策略,那么这就是on-policy的算法。但是on-policy算法很难解决“探索与利用"问题,因为我们为了实现目标策略的稳定,不会允许行为策略有太多的探索程度,因此很有可能学习到的是局部极值。因此,我们希望行为策略和目标策略是两个策略,在行为策略中我们可以多多探索,为目标策略提供一个优化的参考。(如果想要找一个on-policy和off-policy区分的例子,大家可以看时序差分中的SARSA和Q-learning的区别,这是比较典型的on-policy和off-poicy的区别)下面一个显而易见的问题就是,如何从行为策略中去学习目标策略,这里要用到一个几乎所有off-policy都要用到的方法,叫做重要性采样(具体内容可以见链接),简单来说从与原分布不同的另一个分布中采样,而对原先分布的性质进行估计的一种方法。在这里,再给大家留一个问题,前面讲到,Q-learning是一种典型的off-policy算法,但是,为什么,在我们的印象中,Q-learning从来没有用过重要性采样这种方法呢

DPG

回到正题,我们继续讲DPG算法,DPG可以on-policy,也可以off-policy,但是考虑到DPG的原理,还是应该使用off-policy的算法(原因后面会讲)。和之前的policy gradient算法相比,最主要的区别就是,之前我们输出的是动作的概率\pi(a \mid s, \boldsymbol{\theta}),而在DPG中,我们输出的动作是确定(Deterministic)的,用公式表达为a=\mu_{\theta}(s),之前的策略梯度输出的是所有动作的概率,而DPG只输出一个确定的动作,它的优点是从以前对状态动作的积分改为只对状态进行积分,需要的样本数大大减少,缺点是由于只输出一个动作,探索性大大减少,因此需要涉及一个off-policy的算法,让行为策略去更好地探索。而且由于只与状态相关,连重要性采样都不需要了,这让DPG的公式十分的漂亮。DPG的更新函数为

                                                                                                    \begin{aligned} \delta_{t} &=r_{t}+\gamma Q^{w}\left(s_{t+1}, \mu_{\theta}\left(s_{t+1}\right)\right)-Q^{w}\left(s_{t}, a_{t}\right) \\ w_{t+1} &=w_{t}+\alpha_{w} \delta_{t} \nabla_{w} Q^{w}\left(s_{t}, a_{t}\right) \\ \theta_{t+1} &=\theta_{t}+\left.\alpha_{\theta} \nabla_{\theta} \mu_{\theta}\left(s_{t}\right) \nabla_{a} Q^{w}\left(s_{t}, a_{t}\right)\right|_{a=\mu_{\theta}(s)} \end{aligned}

 

Deep Deterministic Policy Gradient

从DeepMind的DQN开始,人们开始意识到了可以使用神经网络去拟合强化学习算法中的价值函数,策略函数。在DPG提出后,DPG的深度学习版本,DDPG应运而生。

我们参考从DQN到DDQN的过程,在DDPG中,一共存在四个网络,Actor和Critic各有两个,Actor当前网络,Actor目标网络,Critic当前网络,Critic目标网络。这四个网络的作用分别是(来自强化学习(十六)深度确定性策略性梯度

1.  Actor当前网络:负责策略网络参数θ的迭代更新,负责根据当前状态S选择当前动作A,用于和环境交互生成S′,R

2. Actor目标网络:负责根据经验回放池中采样的下一状态S′选择最优下一动作A′。网络参数θ′定期从θ复制。

3. Critic当前网络:负责价值网络参数w的迭代更新,负责计算当前Q值Q(S,A,w)。目标Q值yi=R+γQ′(S′,A′,w′)。

4. Critic目标网络:负责计算目标Q值中的Q′(S′,A′,w′)部分。网络参数w′定期从w复制。

另外,关于网络更新的问题,DDPG采用的是一种"soft-update"的方式,每次当前网络到目标网络的更新只更新一点点,即

                                                                                                                                               \begin{array}{c} w^{\prime} \leftarrow \tau w+(1-\tau) w^{\prime} \\ \theta^{\prime} \leftarrow \tau \theta+(1-\tau) \theta^{\prime} \end{array}

在DPG中我们讲到了DPG方法缺乏探索性,因此我们在训练过程中,给动作加个噪声,也就是A = A + N 的方式来提高探索的随机性。

对于Critic当前网络,我们的损失函数和DQN类似,都是均方误差。

                                                                                                    J(w)=\frac{1}{m} \sum_{j=1}^{m}\left(y_{j}-Q\left(\phi\left(S_{j}\right), A_{j}, w\right)\right)^{2}

对于Actor当前网络,它的损失函数第一眼看上去有点奇怪,是\left.J(\theta)=-\frac{1}{m} \sum_{j=1}^{m} Q(s_{i}, a_{i}, w\right),但是这个损失函数从表达式上是比较好理解的,Q越大,代表我们行为所受的评价越高,对应的损失就越小。

具体DDPG的算法我们可以后面在讲MADDPG的时候再看。

Multi-agent Deep Deterministic Policy Gradient

下面开始讲本篇博文的重头戏,MADDPG。

多智能体强化学习背景

可能我在其他很多文章中已经介绍过很多次,这里还是要首先讲一讲多智能体强化学习的背景。在现实生活中,多智能体的场景是很常见的,也就是说,在一个环境中,会有多个智能体同时与环境交互,智能体本身的行为也会作为环境的一部分去影响其他智能体的学习。在这种环境中,我们想要实现一个强化学习算法是非常困难的事情,因为环境实在是太动态了。如果我们使用纯粹的集中式学习的方法,那么我们会面对一个指数增长的复杂空间,如果我们使用完全独立的算法,比如每个智能体都用DQN训练,在需要合作的问题上我们几乎不可能取得好的效果。面对这种问题,目前的主要解决方案有两种,一种是通过让智能体间进行通讯,另一种就是“集中训练,分布运行”,在训练时我们假设有一个上帝可以观测全局的情况,在实际运行时把这个上帝去掉,MADDPG就是这样一种算法。

MADDPG

上面这个结构图(以两个智能体为例)已经很清楚说明了整个算法的架构。每一个智能体都有一个Critic和一个Actor,Critic能获得全局信息(全局的观察o1,o2,...,on)和全局动作(a1,a2,...,an)。而Actor只能根据本地的观察来做出动作,其实讲到这里整个算法大概就差不多了。但是在这篇论文中,作者针对多智能体的特点,还提出了一些策略来改善表现。

(1)估计其他智能体的策略

每个智能体维护n-1个策略逼近函数\hat{\mu}_{\phi_i^j},其逼近代价为对数代价函数,并且加上策略的熵,其代价函数可以写为

L\left(\phi_{i}^{j}\right)=-E_{o_{j}, a_{j}}\left[\log \hat{\mu}_{\phi_{i}^{j}}\left(a_{j} \mid o_{j}\right)+\lambda H\left(\hat{\mu}_{\phi_{i}^{j}}\right)\right]

只要最小化上述代价函数,就能得到其他智能体策略的逼近。

(2)智能体的策略集成

在多智能体环境中,因为每个智能体的策略都在改变。作者提到在强对抗环境中,这会导致很严重的问题,某个智能体可能会过拟合一个针对其他智能体的“好”策略,但是这种策略是非常脆弱的,很可能其他智能体的策略稍微一改变,这个智能体的策略就崩溃了。因此,作者提出了一种策略集成的方法。第i个智能体的策略\mu_i具有K个子策略的集合构成,在每个episode中只使用一个子策略,在整个训练过程中,我们最大化策略集合的整体奖励。

MADDPG算法伪代码截图如下,这个算法和MADDPG是一致的,除了Critic使用的是全局信息。前面做了很多理论上的铺垫,讲了很多的前置知识,但是其实如果只是单看这个算法的话,也是完全看得懂的,它的公式和算法流程并不复杂。如果有实现DQN的基础,这个算法应该是不难实现的。

MADDPG的实现

下面来讲讲MADDPG的实现(只实现了全局的Critic,集成策略等还没有实现)

(1)Actor和Critic的网络结构

# Actor被设定为一个三层全连接神经网络,输出为(-1,1)
class Actor(nn.Module):

    def __init__(self, state_dim, action_dim, n_hidden_1, n_hidden_2):
        super(Actor, self).__init__()
        self.layer1 = nn.Sequential(nn.Linear(state_dim, n_hidden_1), nn.ReLU(True))
        self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2), nn.ReLU(True))
        self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, action_dim), nn.Tanh())

    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        return x


# Critic被设定为一个三层全连接神经网络,输出为一个linear值(这里不使用tanh函数是因为原始的奖励没有取值范围的限制)
class Critic(nn.Module):

    def __init__(self, state_dim, action_dim, n_hidden_1, n_hidden_2):
        super(Critic, self).__init__()
        self.layer1 = nn.Sequential(nn.Linear(state_dim + action_dim, n_hidden_1), nn.ReLU(True))
        self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2), nn.ReLU(True))
        self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, 1))

    def forward(self, sa):
        sa = sa.reshape(sa.size()[0], sa.size()[1] * sa.size()[2])  # 对于传入的s(环境)和a(动作)要展平
        x = self.layer1(sa)
        x = self.layer2(x)
        x = self.layer3(x)
        return x

(2)定义DDPGAgent和DDPG的集合

class DDPGAgent(object):

    def __init__(self, index, memory_size, batch_size, gamma, state_global, action_global, local=False):
        self.hidden = 200
        self.memory = Memory(memory_size)
        self.state_dim = state_global[index]
        self.action_dim = action_global[index]
        self.Actor = Actor(self.state_dim, self.action_dim, self.hidden, self.hidden).to(device)
        # local决定是用局部信息还是全局信息,也决定是DDPG算法还是MADDPG算法
        if not local:
            self.Critic = Critic(sum(state_global), sum(action_global), self.hidden, self.hidden).to(device)
        else:
            self.Critic = Critic(self.state_dim, self.action_dim, self.hidden, self.hidden).to(device)
        self.Actor_target = Actor(self.state_dim, self.action_dim, self.hidden, self.hidden).to(device)
        if not local:
            self.Critic_target = Critic(sum(state_global), sum(action_global), self.hidden, self.hidden).to(device)
        else:
            self.Critic_target = Critic(self.state_dim, self.action_dim, self.hidden, self.hidden).to(device)
        self.critic_train = torch.optim.Adam(self.Critic.parameters(), lr=LR_C)
        self.actor_train = torch.optim.Adam(self.Actor.parameters(), lr=LR_A)
        self.loss_td = nn.MSELoss()
        self.batch_size = batch_size
        self.gamma = gamma
        self.tau = 0.5
        self.local = local

    # 输出确定行为
    def act(self, s):
        return self.Actor(s)

    # 输出带噪声的行为
    def act_prob(self, s):
        a = self.Actor(s)
        noise = torch.normal(mean=0.0, std=torch.Tensor(size=([len(a)])).fill_(0.03)).to(device)
        a_noise = a + noise
        return a_noise


class MADDPG(object):
    def __init__(self, n, state_global, action_global, gamma, memory_size):
        self.n = n
        self.gamma = gamma
        self.memory = Memory(memory_size)
        self.agents = [DDPGAgent(index, 1600, 400, 0.5, state_global, action_global) for index in range(0, n)]

    def update_agent(self, sample, index):
        observations, actions, rewards, next_obs, dones = sample
        curr_agent = self.agents[index]
        curr_agent.critic_train.zero_grad()
        all_target_actions = []
        # 根据局部观测值输出动作目标网络的动作
        for i in range(0, self.n):
            action = curr_agent.Actor_target(next_obs[:, i])
            all_target_actions.append(action)
        action_target_all = torch.cat(all_target_actions, dim=0).to(device).reshape(actions.size()[0],
                                                                                    actions.size()[1],
                                                                                    actions.size()[2])
        target_vf_in = torch.cat((next_obs, action_target_all), dim=2)
        # 计算在目标网络下,基于贝尔曼方程得到当前情况的评价
        target_value = rewards[:, index] + self.gamma * curr_agent.Critic_target(target_vf_in).squeeze(dim=1)
        vf_in = torch.cat((observations, actions), dim=2)
        actual_value = curr_agent.Critic(vf_in).squeeze(dim=1)
        # 计算针对Critic的损失函数
        vf_loss = curr_agent.loss_td(actual_value, target_value.detach())

        vf_loss.backward()
        curr_agent.critic_train.step()

        curr_agent.actor_train.zero_grad()
        curr_pol_out = curr_agent.Actor(observations[:, index])
        curr_pol_vf_in = curr_pol_out
        all_pol_acs = []
        for i in range(0, self.n):
            if i == index:
                all_pol_acs.append(curr_pol_vf_in)
            else:
                all_pol_acs.append(self.agents[i].Actor(observations[:, i]).detach())
        vf_in = torch.cat((observations,
                           torch.cat(all_pol_acs, dim=0).to(device).reshape(actions.size()[0], actions.size()[1],
                                                                            actions.size()[2])), dim=2)
        # DDPG中针对Actor的损失函数
        pol_loss = -torch.mean(curr_agent.Critic(vf_in))
        pol_loss.backward()
        curr_agent.actor_train.step()

    def update(self, sample):
        for index in range(0, self.n):
            self.update_agent(sample, index)

    def update_all_agents(self):
        for agent in self.agents:
            soft_update(agent.Critic_target, agent.Critic, agent.tau)
            soft_update(agent.Actor_target, agent.Actor, agent.tau)

    def add_data(self, s, a, r, s_, done):
        self.memory.add(s, a, r, s_, done)

    # 存储模型
    def save_model(self, episode):
        for i in range(0, self.n):
            model_name_c = "Critic_Agent" + str(i) + "_" + str(episode) + ".pt"
            model_name_a = "Actor_Agent" + str(i) + "_" + str(episode) + ".pt"
            torch.save(self.agents[i].Critic_target, 'model_tag/' + model_name_c)
            torch.save(self.agents[i].Actor_target, 'model_tag/' + model_name_a)

    # 加载模型
    def load_model(self, episode):
        for i in range(0, self.n):
            model_name_c = "Critic_Agent" + str(i) + "_" + str(episode) + ".pt"
            model_name_a = "Actor_Agent" + str(i) + "_" + str(episode) + ".pt"
            self.agents[i].Critic_target = torch.load("model_tag/" + model_name_c)
            self.agents[i].Critic = torch.load("model_tag/" + model_name_c)
            self.agents[i].Actor_target = torch.load("model_tag/" + model_name_a)
            self.agents[i].Actor = torch.load("model_tag/" + model_name_a)

这里面涉及到一个soft-update,也就是参数的软更新的问题。

# 在DDPG中,训练网络的参数不是直接复制给目标网络的,而是一个软更新的过程,也就是 v_new = (1-tau) * v_old + tau * v_new
def soft_update(net_target, net, tau):
    for target_param, param in zip(net_target.parameters(), net.parameters()):
        target_param.data.copy_(target_param.data * (1.0 - tau) + param.data * tau)

具体的训练代码我就不在这里贴了,代码在https://github.com/caozixuan/RL_Learning/tree/master/maddpg_torch 目前实现上还有许多问题,还需要进一步改进。

参考文献

《强化学习》第2版,Sutton

刘建平pinard强化学习专栏

理解策略梯度算法

强化学习,on-policy和off-policy有什么区别

强化学习论文笔记Deterministic Policy Gradient

强化学习入门——MADDPG算法

CONTINUOUS CONTROL WITH DEEP REINFORCEMENT LEARNING​​​​​​​

Multi-Agent Actor-Critic for Mixed Cooperative-Competitive Environments

 

标签:dim,策略,MADDPG,self,Actor,Critic,强化,DDPG,hidden
来源: https://blog.csdn.net/caozixuan98724/article/details/110854007