多智能体的训练起点——环境PettingZoo与环境Gym的区别解读
Gym是由OpenAI开发的一套单智能体环境,现在我们尝试上手多智能体环境PettingZoo,由于Gym和PettingZoo很像,那我们用Gym的思路快速掌握PettingZoo的相关知识,通过快速捕捉多智能体环境PettingZoo和Gym的区别以类比学习PettingZoo。
PettingZoo在支持方面和Gym还是有区别的,比如PettingZoo只支持Python≥3.8,也不完全支持Windows(某些环境不支持)
一.Gym环境的主要组成部件
我们先看一个单智能体在Gym环境下做了什么
- 环境定义
env = gym.make(env_name) #创建指定env_name的环境,如:CartPole-v0
- 初始化环境/重置环境
state = env.reset() #获得s_0
- 获取动作,这一步是智能体操作,虽然和环境没什么关系,但是很重要
action = agent.take_action(state) #我们定义智能体agent实现策略take_action方法
- 更新环境(获得下一个状态,给出终止或者截断)
next_state,reward,terminated,truncated,info = env.step(state)#gym>0.21以后有五个返回值
terminated(终止,bool):在该MDP任务的结束与否标记,智能体到终止状态与否的标记
truncated(截断,bool):在该场景下非MDP任务的结束有否标记,触发条件可能是是否达到时间长度限制,或者智能体中途死了(如在悬崖漫步里,掉下悬崖了等情况),这些情况虽然智能体还没有达到终止状态,但是整个序列已经不得不结束了。
在比较简单的场景下,可能我们没有定义智能体会死亡或者有时间限制,我们只关注是否到达MDP的终止条件,此时可以写成:
next_state,reward,done, _ ,_ = env.step(state) #最后两个用不到,用 _ 省略标记,done表示终止
当结束一轮后,调用env.reset(),重新开始
我们可以写出Gym下训练的基本框架
#假设只训练一条轨迹
from pettingzoo.butterfly import pistonball_v6
env = gym.make()
state = env.reset()
agent = DQN(...) #我们需要显式定义模型
done = False
while not done:
action = agent.take_action(state)
next_state, reward, done, _ , _ = env.step(action)
#...
env.close()
二.PettingZoo环境的主要组成部件
PettingZoo力求了Gym相近,尽可能保证API相似,但是由于多智能体的特点,还是有不同。
面对多个智能体的情况,MARL在更新方式上将博弈的顺序分为两种情况:
-
拓展式博弈:每一个智能体先后行动,对应PettingZoo下的AEC
-
标准型博弈:所有智能体同时行动,对应PettingZoo下的Parallel
从更新的区别上,下面开始介绍PettingZoo与Gym的不同点:
拓展式博弈
AEC环境中,智能体按照顺序行动,并在采取行动之前接受更新的观察结果和奖励,每个代理执行完一步后即更新一次环境,可表示连续游戏的自然方式
- 环境定义
from pettingzoo.butterfly import pistonball_v6
env = pistonball_v6.env() #PettingZoo直接导入一个具体环境,通过env方法实例化
- 初始化方式
使用env.reset()方法初始化环境,作为新一轮训练的开始,没有返回值
env.reset() #注意,AEC中reset没有返回值,与Parallel区别
-
统计方式:
我们对每个智能体操作进行计数,使用env.agent_iter(max_iter=2**63)迭代器,迭代产生环境的当前代理,当环境所有代理均完成或者达到max_iter,终止。
明显在PettingZoo的拓展式博弈中,我们计数当前智能体行动一次即环境的更新,而不是所有的智能体行动一次为环境的更新,可以使用agent_iter自动统计智能体执行行动总次数。
- env.step(action):执行当前智能体的操作,并自动将控制权转交给下一个智能体,没有返回值
执行当前环境下该指定行动的智能体的操作,等同于更新环境
- env.last(observe=True):AEC特有操作,获取当前正采取行动智能体的信息。若observe=False则不会计算观测值,而是用None替代,当单个智能体完成时环境没有结束
observation
:包含当前智能体最新观察reward
:包含当前智能体在上一个时间步的奖励。termination
:包含当前智能体是否终止的标记truncation
: 包含当前智能体是否截断的标记。info
: 额外信息
因此,在AEC中可以写出如下基本训练框架:
from pettingzoo.butterfly import cooperative_pong_v5
env = cooperative_pong_v5.env(render_mode="human")
env.reset(seed=42)
for agent in env.agent_iter():
observation, reward, termination, truncation, info = env.last()
if termination or truncation:
action = None
else:
# this is where you would insert your policy
action = env.action_space(agent).sample() #可以自行定义策略,决定当前智能体的动作
env.step(action)
env.close()
标准型博弈
Parallel中,智能体同时执行动作,对于一个可支持并行运行的环境
- 环境定义
from pettingzoo.butterfly import pistonball_v6
parallel_env = pistonball_v6.parallel_env()
- 初始化方式
使用parallel_env.reset()方法初始化环境,此时将返回所有智能体的初始观察空间和信息
observaions,infos = parallel_env.reset() #AEC可以用last获得单个智能体的观察空间,而Parrllel是用reset返回所有智能体的初始观察空间
- 统计方式:我们对当前环境所有智能体执行一次操作后进行计数,使用env.agents方法统计当前环境还有多少智能体存活,若为空表示所有智能体都执行结束了
while parallel_env.agents:
#parallel_env.action_space(agent).sample() 可以改成自己的智能体策略
actions = {agent:parallel_env.action_space(agent).sample() for agents in parallel_env.agents }
- env.step(action):输入所有智能体动作的字典,一次性所有智能体更新操作操作,返回所有智能体的观察空间,奖励,截断,结束,信息
observations, rewards, terminations, truncations, infos = parallel_env.step(actions)
因此,在Parallel中可以写出如下基本训练框架:
from pettingzoo.butterfly import pistonball_v6
parallel_env = pistonball_v6.parallel_env(render_mode="human")
observations, infos = parallel_env.reset(seed=42)
while parallel_env.agents:
# this is where you would insert your policy
actions = {agent: parallel_env.action_space(agent).sample() for agent in parallel_env.agents}
observations, rewards, terminations, truncations, infos = parallel_env.step(actions)
parallel_env.close()
三、总结与画饼
可以看出,PettingZoo的Parallel形式和Gym更加类似,需要注意
- reset(),step()方法返回的都是所有智能体的字典形式
- step()方法要求输入的也是所有智能体actions的字典形式,所有要注意用for循环访问env.agents获取动作字典
对于AEC形式,要利用agent_iter指向当前智能体和last方法
- reset(),step()方法没有返回值
- 使用last()方法获取当前智能体各种信息
画饼:之后会讲解——在PettingZoo中agent在访问环境就获得了,我们不能像Gym一样实例化一个智能体,再去该类中编写它的策略。那我们应该如何在PettingZoo中编写策略呢。