本专栏按照 https://lilianweng.github.io/lil-log/2018/04/08/policy-gradient-algorithms.html 顺序进行总结 。
文章目录
- 原理解析
-
- 问题建模
- 算法架构图
- 学习过程
- 模型训练框架
- 模型特点
- 算法实现
-
- 总体流程
- 代码实现
MADDPG\color{red}MADDPGMADDPG :[ paper | code ]
原理解析
从单个智能体的角度来看,环境是非平稳的,因为其他智能体的策略在很快地更新并且一直是未知的。MADDPG是一个经过重新设计的演员评论家算法,专门用于处理这种不断变化的环境以及智能体之间的互动。
算法是 DDPG 算法在多智能体系统下的自然扩展,属于中心化训练,去中心化执行的算法框架。
改进之处: 在 Q值函数的建模过程中,通过在输入端引入从其他智能体当前策略采样出的动作作为额外信息,来解决多智能体场景下的环境非平稳问题。
问题建模
- 共有 NNN 个智能体
- 公共的状态空间为 SSS
- 每个智能体拥有自己的动作空间 A1,…,ANA_1,…,A_NA1?,…,AN? 以及观察空间 O1,…,ONO_1,…,O_NO1?,…,ON?
- 状态转移函数包括所有的状态、动作以及观察空间 T:S×A1×…AN?ST:S×A1×…AN?ST:S×A1×…AN?S。
- 每个智能体自己的随机策略仅仅用到属于自己的观察以及动作:πθi:Oi×Ai?[0,1]π_{θi}:Oi×Ai?[0,1]πθi?:Oi×Ai?[0,1] ,一个给定其自身观察下关于动作的概率分布;或者一个确定性策略:μθi:Oi?Aiμ_{θi}:Oi?Aiμθi?:Oi?Ai 。
令o?=o1,…,oN,μ?=μ1,…,μN\vec{o}=o_{1}, \ldots, o_{N}, \vec{\mu}=\mu_{1}, \ldots, \mu_{N}o=o1?,…,oN?,μ?=μ1?,…,μN? 并且策略是由 θ?=θ1,…,θN\vec{\theta}=\theta_{1}, \dots, \theta_{N}θ=θ1?,…,θN? 参数化的。
MADDPG中的评论家为第 iii 个智能体(每个智能体)学习一个中心化的动作-值函数 Qiμ?(o?,a1,…,aN)Q_{i}^{\vec{\mu}}\left(\vec{o}, a_{1}, \ldots, a_{N}\right)Qiμ??(o,a1?,…,aN?),其中 a1∈A1,…,aN∈ANa_{1} \in \mathcal{A}_{1}, \ldots, a_{N} \in \mathcal{A}_{N}a1?∈A1?,…,aN?∈AN? 是所有智能体的动作。每一个 Qiμ?,i=1,…,NQ_{i}^{\vec{\mu}},\;i=1, \dots, NQiμ??,i=1,…,N 都是独立学习的,因而每个智能体可以拥有任意形式的回报函数,包括竞争环境中相互冲突的回报函数。同时,每个智能体各自的演员,也是独立探索以及独立更新策略参数 θiθ_iθi?
演员更新:
?θiJ(θi)=Eo?,a?D[?aiQiμ?(o?,a1,…,aN)?θiμθi(oi)∣ai=μθi(oi)]\nabla_{\theta_{i}} J\left(\theta_{i}\right)=\mathbb{E}_{\vec{o}, a \sim D}\left[\nabla_{a_{i}} Q_{i}^{\vec{\mu}}\left(\vec{o}, a_{1}, \ldots, a_{N}\right) \nabla_{\theta_{i}} \mu_{\theta_{i}}\left.\left(o_{i}\right)\right|_{a_{i}=\mu_{\theta_{i}}\left(o_{i}\right)}\right]?θi??J(θi?)=Eo,a?D?[?ai??Qiμ??(o,a1?,…,aN?)?θi??μθi??(oi?)∣ai?=μθi??(oi?)?]
其中 D 表示经验回放缓冲,包含大量轨迹样本 (o?,a1,…,aN,r1,…,rN,o?′)\left(\vec{o}, a_{1}, \ldots, a_{N}, r_{1}, \ldots, r_{N}, \vec{o}^{\prime}\right)(o,a1?,…,aN?,r1?,…,rN?,o′) —— 给定当前联合观察 o?\vec{o}o,每个智能体分别执行动作 a1,…,aNa_{1}, \dots, a_{N}a1?,…,aN? 后获取各自的回报 r1,…,rNr_{1}, \dots, r_{N}r1?,…,rN? , 并转移到下一个联合观察 o?′\vec{o}^{\prime}o′
评论家更新:
L(θi)=Eo?,a1,…,aN,r1,…,rN,o?’[(Qiμ?(o?,a1,…,aN)?y)2]其中 y=ri+γQiμ?’(o?’,a’1,…,a’N)∣a’j=μ’θj; TD目标值!\begin{aligned} \mathcal{L}(\theta_i) &= \mathbb{E}_{\vec{o}, a_1, \dots, a_N, r_1, \dots, r_N, \vec{o}’}[ (Q^{\vec{\mu}}_i(\vec{o}, a_1, \dots, a_N) - y)^2 ] & \\ \text{其中 } y &= r_i + \gamma Q^{\vec{\mu}’}_i (\vec{o}’, a’_1, \dots, a’_N) \rvert_{a’_j = \mu’_{\theta_j}} & \scriptstyle{\text{; TD目标值!}} \end{aligned}L(θi?)其中 y?=Eo,a1?,…,aN?,r1?,…,rN?,o’?[(Qiμ??(o,a1?,…,aN?)?y)2]=ri?+γQiμ?’?(o’,a’1?,…,a’N?)∣a’j?=μ’θj????; TD目标值!?
其中, μ?′\vec{\mu}^{\prime}μ?′ 是延迟软更新参数的目标策略。
【每一个Critic更新参数时,需要知道所有Actor的 (s,a=μ(s),r,snext,a′=μ′(s))(s, a=μ(s), r, s_next, a'=μ'(s))(s,a=μ(s),r,sn?ext,a′=μ′(s)), 其中 a′a'a′ 来自其Target Policy】
为了缓解环境中竞争或协作关系的智能体之间由于相互作用所带来的高方差,MADDPG提出了另外一个技术——策略集成:
- 为单个智能体训练 KKK 个策略;
- 随机选取一个策略用以轨迹采样;
- 使用 KKK 个策略的集成梯度来进行参数更新。
总之,MADDPG在DDPG之上添加了三个额外部分,使其适应多智能体环境:
- 中心化评论家+去中心化演员;
- 智能体能够使用估计的其他智能体的策略来进行学习;
- 策略集成能够很好的减小方差。
算法架构图
模型由多个DDPG网络组成,每个网络学习policy πππ (Actor) 和 action value Q (Critic);同时具有target network,用于Q-learning的off-policy学习。
学习过程
- 整体如图,采样收集数据即执行部分是分别进行的,训练学习是统一进行的。
- 各个Actor收集数据 (s,a=μ(s),r,snext,a′=μ′(s))(s, a=μ(s), r, s_next, a'=μ'(s))(s,a=μ(s),r,sn?ext,a′=μ′(s)),并存入Replay Buffer,当缓存池数量大于预热阈值时,开始学习。
- 每个Actor分别更新policy πππ 参数,与DDPG一样,只需要当前 (s,a=μ(s))(s, a=μ(s))(s,a=μ(s))。
- 每个Critic分别更新action value Q参数,注意每个Critic都能看到所有的Actor收集的数据,更新参数时会考虑所有Actor生成的数据,即优化的是每个Critic对全局的贡献最大。
- 重复2,3,4,直至收敛。
模型训练框架
训练过程采取集中训练、分散执行的方式,即每个智能体根据自身策略得到当前状态执行的动作,并与环境交互得到经验存入自身的经验缓存池。待所有智能体与环境交互后,每个智能体从经验池中随机抽取经验训练各自的神经网络。
为加速智能体的学习过程,Critic 网络的输入要包括其他智能体的观察状态和采取的动作,通过最小化损失以更Critic 网络参数。而后通过梯度下降法计算更新动作网络的参数。
多智能体强化学习一个顽固的问题是由于每个智能体的策略都在更新迭代导致环境针对一个特定的智能体是动态不稳定的。这种情况在竞争任务下尤其严重,经常会出现一个智能体针对其竞争对手过拟合出一个强策略。但是这个强策略是非常脆弱的,也是我们希望得到的,因为随着竞争对手策略的更新改变,这个强策略很难去适应新的对手策略。
模型特点
- 通过基于Actor-Critic的DDPG作为基本结构,解决多智能体问题。
- 独立地采样,统一地学习。
- 通过所有Actor的数据更新Q的值,让系统比较平稳地优化。
- 框架没有对环境做限制,每个Agent可以有自己的Reward机制,并决定着整体是协作或是竞争。
- 对于每个Agent,测试时只需要当前Actor的数据进行预测;训练和测试输入数据不一致,这是个创新点。
算法实现
总体流程
略
代码实现
详情请见:https://github.com/xuehy/pytorch-maddpg
from model import Critic, Actor
import torch as th
from copy import deepcopy
from memory import ReplayMemory, Experience
from torch.optim import Adam
from randomProcess import OrnsteinUhlenbeckProcess
import torch.nn as nn
import numpy as np
from params import scale_rewarddef soft_update(target, source, t):for target_param, source_param in zip(target.parameters(),source.parameters()):target_param.data.copy_((1 - t) * target_param.data + t * source_param.data)def hard_update(target, source):for target_param, source_param in zip(target.parameters(),source.parameters()):target_param.data.copy_(source_param.data)class MADDPG:def __init__(self, n_agents, dim_obs, dim_act, batch_size,capacity, episodes_before_train):self.actors = [Actor(dim_obs, dim_act) for i in range(n_agents)]self.critics = [Critic(n_agents, dim_obs,dim_act) for i in range(n_agents)]self.actors_target = deepcopy(self.actors)self.critics_target = deepcopy(self.critics)self.n_agents = n_agentsself.n_states = dim_obsself.n_actions = dim_actself.memory = ReplayMemory(capacity)self.batch_size = batch_sizeself.use_cuda = th.cuda.is_available()self.episodes_before_train = episodes_before_trainself.GAMMA = 0.95self.tau = 0.01self.var = [1.0 for i in range(n_agents)]self.critic_optimizer = [Adam(x.parameters(),lr=0.001) for x in self.critics]self.actor_optimizer = [Adam(x.parameters(),lr=0.0001) for x in self.actors]if self.use_cuda:for x in self.actors:x.cuda()for x in self.critics:x.cuda()for x in self.actors_target:x.cuda()for x in self.critics_target:x.cuda()self.steps_done = 0self.episode_done = 0def update_policy(self):# do not train until exploration is enoughif self.episode_done <= self.episodes_before_train:return None, NoneByteTensor = th.cuda.ByteTensor if self.use_cuda else th.ByteTensorFloatTensor = th.cuda.FloatTensor if self.use_cuda else th.FloatTensorc_loss = []a_loss = []for agent in range(self.n_agents):transitions = self.memory.sample(self.batch_size)batch = Experience(*zip(*transitions))non_final_mask = ByteTensor(list(map(lambda s: s is not None,batch.next_states)))# state_batch: batch_size x n_agents x dim_obsstate_batch = th.stack(batch.states).type(FloatTensor)action_batch = th.stack(batch.actions).type(FloatTensor)reward_batch = th.stack(batch.rewards).type(FloatTensor)# : (batch_size_non_final) x n_agents x dim_obsnon_final_next_states = th.stack([s for s in batch.next_statesif s is not None]).type(FloatTensor)# for current agentwhole_state = state_batch.view(self.batch_size, -1)whole_action = action_batch.view(self.batch_size, -1)self.critic_optimizer[agent].zero_grad()current_Q = self.critics[agent](whole_state, whole_action)non_final_next_actions = [self.actors_target[i](non_final_next_states[:,i,:]) for i in range(self.n_agents)]non_final_next_actions = th.stack(non_final_next_actions)non_final_next_actions = (non_final_next_actions.transpose(0,1).contiguous())target_Q = th.zeros(self.batch_size).type(FloatTensor)target_Q[non_final_mask] = self.critics_target[agent](non_final_next_states.view(-1, self.n_agents * self.n_states),non_final_next_actions.view(-1,self.n_agents * self.n_actions)).squeeze()# scale_reward: to scale reward in Q functionstarget_Q = (target_Q.unsqueeze(1) * self.GAMMA) + (reward_batch[:, agent].unsqueeze(1) * scale_reward)loss_Q = nn.MSELoss()(current_Q, target_Q.detach())loss_Q.backward()self.critic_optimizer[agent].step()self.actor_optimizer[agent].zero_grad()state_i = state_batch[:, agent, :]action_i = self.actors[agent](state_i)ac = action_batch.clone()ac[:, agent, :] = action_iwhole_action = ac.view(self.batch_size, -1)actor_loss = -self.critics[agent](whole_state, whole_action)actor_loss = actor_loss.mean()actor_loss.backward()self.actor_optimizer[agent].step()c_loss.append(loss_Q)a_loss.append(actor_loss)if self.steps_done % 100 == 0 and self.steps_done > 0:for i in range(self.n_agents):soft_update(self.critics_target[i], self.critics[i], self.tau)soft_update(self.actors_target[i], self.actors[i], self.tau)return c_loss, a_lossdef select_action(self, state_batch):# state_batch: n_agents x state_dimactions = th.zeros(self.n_agents,self.n_actions)FloatTensor = th.cuda.FloatTensor if self.use_cuda else th.FloatTensorfor i in range(self.n_agents):sb = state_batch[i, :].detach()act = self.actors[i](sb.unsqueeze(0)).squeeze()act += th.from_numpy(np.random.randn(2) * self.var[i]).type(FloatTensor)if self.episode_done > self.episodes_before_train and\self.var[i] > 0.05:self.var[i] *= 0.999998act = th.clamp(act, -1.0, 1.0)actions[i, :] = actself.steps_done += 1return actions