MADDPG算法是强化学习的进阶算法,在读对应论文Multi-Agent Actor-Critic for Mixed Cooperative-Competitive Environments的过程中,往往会遇到很多不是很好理解的数学公式,这篇文章旨在帮助读者翻过数学这座大山,从PARL的代码理解MADDPG算法。
- 把MADDPG拆分成多个算法
- 什么是多智能体?有哪些环境?
- 从PARL的代码解读MADDPG
- 复现“老鹰捉小鸡”的游戏环境
- 回归论文
1. 把MADDPG拆分成多个算法
MADDPG的全称是Multi-Agent Deep Deterministic Policy Gradient。我们可以把它拆开去理解:
- Multi-Agent:多智能体
- Deep:与DQN类似,使用目标网络+经验回放
- Deterministic:直接输出确定性的动作
- Policy Gradient: 基于策略Policy来做梯度下降从而优化模型
我们可以把思路理一下,MADDPG其实是在DDPG的基础上做的修改,而DDPG可以看作在DPG的基础之上修改而来,DPG是由DQN和Policy Gradient两者结合后得到的;也可以把DDPG理解为让DQN可以扩展到连续控制动作空间的算法。
那下面我们就来把这些算法一一回顾一下:
Q-learning算法
Q-learning算法最主要的就是Q表格,里面存着每个状态的动作价值。然后用Q表格用来指导每一步的动作。并且每走一步,就更新一次Q表格,也就是说用下一个状态的Q值去更新当前状态的Q值。
DQN算法
DQN的本质其实是Q-learning算法,改进就是把Q表格换成了神经网络,向神经网络输入状态state,就能输出所有状态对应的动作action。
在讲PG算法前,我们需要知道的是,在强化学习中,有两大类方法,一种基于值(Value-based),一种基于策略(Policy-based):
Value-based的算法的典型代表为Q-learning和SARSA,将Q函数优化到最优,再根据Q函数取最优策略;Policy-based的算法的典型代表为Policy Gradient,直接优化策略函数。可以举一个例子区分这两种方法:
如果用DQN玩剪刀石头布这种随机性很大的游戏,很可能训练到最后,一直输出同一个动作;但是用Policy Gradient的话,优化到最后就会发现三个动作的概率都是一样的。
Policy Gradient算法
可以通过类比监督学习的方式来理解Policy Gradient。向神经网络输入状态state,输出的是每个动作的概率,然后选择概率最高的动作作为输出。训练时,要不断地优化概率,尽可能地使输出值的概率逼近1。
DPG算法
DPG算法可以理解为PG+DQN,它是首次能处理确定性的连续动作空间问题的算法。要学习DPG算法,就要知道Actor-Critic结构,Actor的前生是Policy Gradient,可以在连续动作空间内选择合适的动作action;Critic的前生是DQN或者其他的以值为基础的算法,可以进行单步更新,效率更高。Actor基于概率分布选择行为,Critic基于Actor生成的行为评判得分,Actor再根据Critic的评分修改选行为的概率。DPG就是在Actor-Critic结构上做的改进,让Actor输出的action是确定值而不是概率分布。
DDPG算法
DDPG算法可以理解为DPG+DQN。因为Q网络的参数在频繁更新梯度的同时,又用于计算Q网络和策略网络的梯度,所以Q网络是不稳定的,所以为了稳定Q网络,DDPG分别给策略网络和Q网络都搭建了一个目标网络,专门用来稳定Q网络:
MADDPG解决了什么问题?
简单来看,MADDPG其实就是在DDPG的基础上,解决一个环境里存在多个智能体的问题。
像Q-Learning或者policy gradient都不适用于多智能体环境。主要的问题是,在训练过程中,每个智能体的策略都在变化,因此从每个智能体的角度来看,环境变得十分不稳定,其他智能体的行动带来环境变化:
-
对DQN算法来说,经验回放的方法变的不再适用,因为如果不知道其他智能体的状态,那么不同情况下自身的状态转移会不同。
-
对PG算法来说,环境的不断变化导致了学习的方差进一步增大。
2. 什么是多智能体?有哪些环境?
在单智能体强化学习中,智能体所在的环境是稳定不变的,但是在多智能体强化学习中,环境是复杂的、动态的,因此给学习过程带来很大的困难。
我理解的多智能体环境是一个环境下存在多个智能体,并且每个智能体都要互相学习,合作或者竞争。
下面我们看一下都有哪些多智能体环境。
OpenAI 的捉迷藏环境
比较有意思的环境是OpenAI的捉迷藏环境,主要讲的是两队开心的小朋友agents在玩捉迷藏游戏中经过训练逐渐学到的各种策略:
这里我也找了一个视频:
OpenAI智能体上演捉迷藏攻防大战,自创套路与反套路
这个环境是基于mujoco的, mujoco是付费的,这里有一个简化版的类似捉迷藏的环境,也是OpenAI的
OpenAI的小球版“老鹰捉小鸡”环境
GitHub链接 : https://github.com/openai/multiagent-particle-envs
里面一共有6个多智能体环境,大家可以去尝试一下,这里我们主要讲解一下simple_world_comm这个环境:
这个环境中有6个智能体,其中两个绿色的小球速度快,他们要去蓝色小球(水源)那里获得reward;而另外四个红色小球速度较慢,他们要追逐绿色小球以此来获得reward。
- 剩下的两个绿色大球是森林,绿色小球进入森林时,红色小球就无法获取绿色小球的位置;
- 黑色小球是障碍物,小球都无法通过;
- 两个蓝色小球是水源,绿色小球可以通过靠近水源的方式获取reward。
这个环境中,只有智能体可以移动,每个episode结束后,环境会随机改变。
这是一个合作与竞争的环境,绿色小球和红色小球都要学会和队友合作,于此同时,绿色小球和红色小球之间存在竞争的关系。
下面我们从PARL的代码解读MADDPG。
3. 从PARL的代码解读MADDPG
我原来的思路是通过PARL里DDPG的代码与MADDPG的代码作比较,但是我发现这两个算法的代码不是一个人写的,在对比时区别比较大,不易从中找到两个算法的区别,因此我打算只看MADDPG的算法,就不做代码对比了。
Algorithm
target_network
这里还是要提一句,MADDPG算法和DDPG一样的是,分别给策略网络和Q网络都搭建了一个target_network,这在代码的体现里如下:
self.model = model
self.target_model = deepcopy(model)
也就是把model深拷贝了一份。
Actor-Critir结构
接着就是Actor-Critir的结构:
- 给Actor输入环境的观察值obs,输出的就是动作;
- 把Actor输出的动作和对应的环境的观察值obs输入给Critir,最后输出Q值。
对应的代码如下:
# Actor
def predict(self, obs):
""" input:
obs: observation, shape([B] + shape of obs_n[agent_index])
output:
act: action, shape([B] + shape of act_n[agent_index])
"""
this_policy = self.model.policy(obs)
this_action = SoftPDistribution(
logits=this_policy,
act_space=self.act_space[self.agent_index]).sample()
return this_action
def predict_next(self, obs):
""" input: observation, shape([B] + shape of obs_n[agent_index])
output: action, shape([B] + shape of act_n[agent_index])
"""
next_policy = self.target_model.policy(obs)
next_action = SoftPDistribution(
logits=next_policy,
act_space=self.act_space[self.agent_index]).sample()
return next_action
# Critir
def Q(self, obs_n, act_n):
""" input:
obs_n: all agents' observation, shape([B] + shape of obs_n)
output:
act_n: all agents' action, shape([B] + shape of act_n)
"""
return self.model.value(obs_n, act_n)
def Q_next(self, obs_n, act_n):
""" input:
obs_n: all agents' observation, shape([B] + shape of obs_n)
output:
act_n: all agents' action, shape([B] + shape of act_n)
"""
return self.target_model.value(obs_n, act_n)
这一部分描述了Actor具体怎么输出动作,以及Critir怎么打分。
Actor网络的参数更新
上面讲的这些部分跟DDPG算法是一致的,区别就在于网络的更新方式上,准确说,更新方式是一样的,只不过从一个智能体变成了多个智能体的情况。以下代码体现的是多个Actor网络的更新:
def _actor_learn(self, obs_n, act_n):
i = self.agent_index
this_policy = self.model.policy(obs_n[i])
sample_this_action = SoftPDistribution(
logits=this_policy,
act_space=self.act_space[self.agent_index]).sample()
action_input_n = act_n + []
action_input_n[i] = sample_this_action
eval_q = self.Q(obs_n, action_input_n)
act_cost = layers.reduce_mean(-1.0 * eval_q)
act_reg = layers.reduce_mean(layers.square(this_policy))
cost = act_cost + act_reg * 1e-3
fluid.clip.set_gradient_clip(
clip=fluid.clip.GradientClipByNorm(clip_norm=0.5),
param_list=self.model.get_actor_params())
optimizer = fluid.optimizer.AdamOptimizer(self.lr)
optimizer.minimize(cost, parameter_list=self.model.get_actor_params())
return cost
Critir网络的参数更新
然后我查阅了一些资料,说引入可以观察全局的Critic来指导Actor训练,所以Critic网络的更新不需要对每个Actor的Critir都进行更新,只需要更新可以观察全局的Critic即可:
def _critic_learn(self, obs_n, act_n, target_q):
pred_q = self.Q(obs_n, act_n)
cost = layers.reduce_mean(layers.square_error_cost(pred_q, target_q))
fluid.clip.set_gradient_clip(
clip=fluid.clip.GradientClipByNorm(clip_norm=0.5),
param_list=self.model.get_critic_params())
optimizer = fluid.optimizer.AdamOptimizer(self.lr)
optimizer.minimize(cost, parameter_list=self.model.get_critic_params())
return cost
以上就是MADDPG算法的主要部分,但是核心思想体现的不是特别明显,下面看Agent部分。
Agent
build_program
这里定义了4个动态图,其实就是Actor网络和Critir网络以及他们对应的目标网络:
def build_program(self):
self.pred_program = fluid.Program() #Actor
self.learn_program = fluid.Program() #Critic
self.next_q_program = fluid.Program() #target_Critic
self.next_a_program = fluid.Program() #target_Actor
with fluid.program_guard(self.pred_program):
obs = layers.data(
name='obs',
shape=[self.obs_dim_n[self.agent_index]],
dtype='float32')
self.pred_act = self.alg.predict(obs)
with fluid.program_guard(self.learn_program):
obs_n = [
layers.data(
name='obs' + str(i),
shape=[self.obs_dim_n[i]],