Procgen¶
概述¶
Procgen Benchmark 是 OpenAI 发布的一组利用 16 种利用程序随机生成的环境(CoinRun,StarPilot,CaveFlyer,Dodgeball,FruitBot,Chaser,Miner,Jumper,Leaper,Maze,BigFish,Heist,Climber,Plunder,Ninja 和 BossFight)。 procgen 的全称是 Procedural Generation,表示程序化生成。对于 procgen 环境,它可以生成同一难度但是采用不同地图的游戏,也可以生成采用同一地图但是不同难度的游戏,可以用来衡量模型学习通用技能的速度,从而判断算法对于环境的泛化能力。下图所示为其中的 Coinrun 游戏。
以下三张图片分别表示了 coinrun 环境下 level1 到 level3 的不同输入:
安装¶
安装方法¶
可以通过 pip 一键安装或结合 DI-engine 安装,只需要安装 gym 和 gym[procgen] 两个库即可完成
# Method1: Install Directly
pip install gym
pip install gym[procgen]
# Method2: Install with DI-engine requirements
cd DI-engine
pip install ".[procgen_env]"
验证安装¶
安装完成后,可以通过在 Python 命令行中运行如下命令验证安装成功:
import gym
env = gym.make('procgen:procgen-maze-v0', start_level=0, num_levels=1)
# num_levels=0 - The number of unique levels that can be generated. Set to 0 to use unlimited levels.
# start_level=0 - The lowest seed that will be used to generated levels. 'start_level' and 'num_levels' fully specify the set of possible levels.
obs = env.reset()
print(obs.shape) # (64, 64, 3)
变换前的空间(原始环境)¶
观察空间¶
实际的游戏画面,RGB 三通道图片,具体尺寸为
(64, 3, 3)
,数据类型为float32
动作空间¶
游戏操作按键空间,一般是大小为 N 的离散动作空间(N 随具体子环境变化),数据类型为
int
,需要传入 python 数值(或是 0 维 np 数组,例如动作 3 为np.array(3)
)如在 Coinrun 环境中,N 的大小为 5,即动作在 0-4 中取值,具体的含义是:
0:NOOP
1:LEFT
2:RIGHT
3:UP
4:DOWN
奖励空间¶
游戏得分,根据具体游戏内容不同会有一定的差异,一般是一个
float
数值, 如在 Coinrun 环境中, 吃到硬币则奖励 10.0分,除此以外没有其它奖励。
其他¶
游戏结束即为当前环境 episode 结束,例如在 coinrun 中,智能体吃到硬币或者游戏时间超过了允许的最长游戏时间,则游戏结束。
关键事实¶
2D RGB三通道图像输入,三维 np 数组,尺寸为
(3, 64, 64)
,数据类型为np.float32
,取值为[0, 255]
离散动作空间
奖励具有稀疏性,例如在 coinrun 中,只有吃到硬币才有得分。
环境的泛化性,对于同一环境,有不同等级,它们的输入、奖励空间、动作空间是相同的,但游戏难度却不同。
变换后的空间(RL环境)¶
观察空间¶
变换内容:将尺寸由
(64 ,64 ,3)
调整为(3, 64, 64)
变换结果:三维 np 数组,尺寸为
(3, 84, 84)
,数据类型为np.float32
,取值为[0, 255]
动作空间¶
基本无变换,依然是大小为N的离散动作空间,但一般为一维 np 数组,尺寸为
(1, )
,数据类型为np.int64
奖励空间¶
基本无变换
上述空间使用 gym 环境空间定义则可表示为:
import gym
obs_space = gym.spaces.Box(low=0, high=255, shape=(3, 64, 64), dtype=np.float32)
act_space = gym.spaces.Discrete(5)
rew_space = gym.spaces.Box(low=0, high=10, shape=(1, ), dtype=np.float32)
其他¶
环境
step
方法返回的info
必须包含eval_episode_return
键值对,表示整个 episode 的评测指标,在 Procgen 中为整个 episode 的奖励累加和
其他¶
惰性初始化¶
为了便于支持环境向量化等并行操作,环境实例一般实现惰性初始化,即__init__
方法不初始化真正的原始环境实例,只是设置相关参数和配置值,在第一次调用reset
方法时初始化具体的原始环境实例。
随机种子¶
环境中有两部分随机种子需要设置,一是原始环境的随机种子,二是各种环境变换使用到的随机库的随机种子(例如
random
,np.random
)对于环境调用者,只需通过环境的
seed
方法进行设置这两个种子,无需关心具体实现细节环境内部的具体实现:对于原始环境的种子,在调用环境的
reset
方法内部,具体的原始环境reset
之前设置环境内部的具体实现:对于随机库种子,则在环境的
seed
方法中直接设置该值
训练和测试环境的区别¶
训练环境使用动态随机种子,即每个 episode 的随机种子都不同,都是由一个随机数发生器产生,但这个随机数发生器的种子是通过环境的
seed
方法固定的;测试环境使用静态随机种子,即每个 episode 的随机种子相同,通过seed
方法指定。
存储录像¶
在环境创建之后,重置之前,调用enable_save_replay
方法,指定游戏录像保存的路径。环境会在每个 episode 结束之后自动保存本局的录像文件。(默认调用gym.wrappers.RecordVideo
实现 ),下面所示的代码将运行一个环境 episode,并将这个 episode 的结果保存在./video/
中:
from easydict import EasyDict
from dizoo.procgen.coinrun.envs import CoinRunEnv
env = CoinRunEnv(EasyDict({'env_id': 'procgen:procgen-coinrun-v0'}))
env.enable_save_replay(replay_path='./video')
obs = env.reset()
while True:
action = env.random_action()
timestep = env.step(action)
if timestep.done:
print('Episode is over, eval episode return is: {}'.format(timestep.info['eval_episode_return']))
break
DI-zoo 可运行代码示例¶
完整的训练配置文件在 github
link
内,对于具体的配置文件,例如coinrun_dqn_config.py
,使用如下的 demo 即可运行:
from easydict import EasyDict
coinrun_dqn_default_config = dict(
env=dict(
collector_env_num=4,
evaluator_env_num=4,
n_evaluator_episode=4,
stop_value=10,
),
policy=dict(
cuda=False,
model=dict(
obs_shape=[3, 64, 64],
action_shape=5,
encoder_hidden_size_list=[128, 128, 512],
dueling=False,
),
discount_factor=0.99,
learn=dict(
update_per_collect=20,
batch_size=32,
learning_rate=0.0005,
target_update_freq=500,
),
collect=dict(n_sample=100, ),
eval=dict(evaluator=dict(eval_freq=5000, )),
other=dict(
eps=dict(
type='exp',
start=1.,
end=0.05,
decay=250000,
),
replay_buffer=dict(replay_buffer_size=100000, ),
),
),
)
coinrun_dqn_default_config = EasyDict(coinrun_dqn_default_config)
main_config = coinrun_dqn_default_config
coinrun_dqn_create_config = dict(
env=dict(
type='coinrun',
import_names=['dizoo.procgen.coinrun.envs.coinrun_env'],
),
env_manager=dict(type='subprocess', ),
policy=dict(type='dqn'),
)
coinrun_dqn_create_config = EasyDict(coinrun_dqn_create_config)
create_config = coinrun_dqn_create_config
if __name__ == '__main__':
from ding.entry import serial_pipeline
serial_pipeline((main_config, create_config), seed=0)
基准算法性能¶
Coinrun(平均奖励等于 10 视为较好的 Agent)
Coinrun + DQN
Maze(平均奖励等于 10 视为较好的 Agent)
Maze + DQN