算法既可以在原始动作空间上训练,也可以在简化后的动作空间上训练。下面展示如何在原始动作空间上使用stable_baselines3库中的PPO算法进行训练。
import gym, eden
import time
from eden.wrappers import SelectActionTarget, ObsScale, Reward,ObsReader,ActScale
from stable_baselines3 import PPO
from eden.wrappers.one_agent_wrapper import OneAgent
import tensorboard
from eden.interactive import Render
if __name__ == '__main__':
e = gym.make('eden-v1')
e.reset()
e = Reward(e)#reward函数
e = OneAgent(e)#将多智能体环境变为单智能体环境
#e = ObsReader(e)#解析observation的wrapper,若想使用原始obs可删掉
#e = ActScale(e)#简化动作空间的wrapper,若想采用原始动作空间删除这行即可
e.reset()
tensorboard_dir = "PPO_compound_test"
model = PPO("MlpPolicy", e, verbose=1,tensorboard_log=tensorboard_dir + '/')
model.learn(total_timesteps=1000000)
model.save("ppo_test")#保存文件位置
#以下为观察模型效果的代码,可以展示游戏界面
#render = Render(e,10)
#model = PPO.load("ppo_test")
#obs = e.reset()
#while True:
#action, _states = model.predict(obs)
#obs, rewards, dones, info = e.step(action)
#time.sleep(.5)
#if not render.fresh():
#break
如此就可以训练并将模型保存。
原始动作空间为([动作id,x坐标,y坐标]),其中动作有9种:Idle, Attack, Collect, Pickup, Consume, Equip, Synthesize, Discard, Move。当然,在enden-v1下动作空间被包装为[左键/右键,目标的x,目标的y]。在40x40的地图配置中,x有82个取值,y有40个取值。在这么大的离散空间中,采样获得正确动作的概率太小,baseline算法如PPO等很难学到一个有意义的结果。因而需要Wrapper对action进行降维、简化,帮助算法学习一个有意义的结果。
因此在游戏中配置了一种action_wrapper的样例,通过env = ActScale(env)包装后就可以完成动作空间的映射。
为了达成合成IronIngot的目标,将动作空间修改如下,
原始动作 | 修改方式 | 描述 |
---|---|---|
Idle | 删除。 | 在合成环境中没有意义的动作 |
Move | 不单独进行move,而是其他动作失败后,往目标移动/无目标随机往一个位置移动 | |
Attack/collect/pickup | 合并后分解为找Apple,找Water,找Wood,找stone,找IronIngot。若有目标在附近且对应产品没有达到最大容量,则move到附近,优先级pickup>attack>equip>collect;否则随机移动。 | 目的类似,获得特定材料 |
equip | 不独立设置一个编号,嵌入到其他动作中,为了完成目的去装备 | |
Consume | 分为吃苹果和吃水。没有吃的去做找Apple/Water任务 | - |
Synthesize/Discard | 不独立设置一个编号,嵌入到其他动作中,为了完成目的去合成装备。Discard在有更高级装备时,可以丢弃低级装备,但目前没有用到 | 智能合成 |
具体动作编号设置如下:
编号 | 规则 |
---|---|
0(吃苹果) | 判断Hunger是否小于给定值(设定为400):小于,若背包有Apple则consume,否则执行动作找苹果;大于,若背包Apple数小于上限(60),则执行动作找苹果。 |
1(吃水) | 判断Thirst是否小于给定值(设定为400):小于,若背包有Water则consume,否则执行动作找水;大于,若背包Water数小于上限(60),则执行动作找Water。 |
2(找水) | 判断Water数量是否大于给定值(设定为100):大于,则不执行此动作;小于,判断附近是否有Pool,若能Collect直接Collect,否则向Pool移动。 |
3(找苹果) | 如果Apple数量大于于给定值(设定为100),则不执行,否则:a.判断附近是否有能Pickup的Apple,能捡直接捡,否则向目标位置移动;b.若没有装备WoodAxe则去完成找Wood工作;c.若已经装备WoodAxe且能看见AppleTree,能砍AppleTree直接砍,否则向其移动。 |
4(找铁) | a.若没有装备StoneSmelter,完成找StoneSmelter;b.若能合成IronIngot直接合成;c .IronOre不够找生铁;d.Stone不够找石头。 |
5(找生铁) | a.若视野内有IronOre,则捡/向其移动;b.若已经装备StoneAxe,则去找IronMine砍;c.找石头。 |
6(找StoneSmelter) | a.若有能装备但没装备的StoneSmelter,则装备StoneSmelter;b.判断Stone够不够合成,够则合成,不够则去找石头。 |
7(找石头) | a.若附近有能捡的Stone,则捡/向其移动;b.若有能装备但没装备的StonePickaxe,则装备;c.若已经装备StonePickaxe,则去找Rock砍;d.若没有StonePickaxe,判断Stone够不够合成,够则合成,不够则去找Rock收集Stone。 |
8(找木头) | a.若附近有能捡的Wood,则捡/向Wood移动;b.若有能装备但没装备的WoodAxe,则装备WoodAxe;c.若已经装备WoodAxe,则去找Tree砍;d.若没有木斧,判断Branch够不够合成,够则合成,不够则去找树收集(Collect)Branch。 |
默认 | 上面任务没有满足的目标时,按照"能捡Branch捡branch(防止太多不捡挡路),否则随机移动Speed距离"执行操作 |
相较于默认环境,为了简化任务难度,在实验中做了以下修改:
1.agent理应记录pool的储存数目,否则会做无意义的采水动作;为了简便,设置pool的储量为无限;
2.设定初始Hunger和Thirst为500;
3.苹果只能通过击杀苹果树得到,掉落30个苹果。
在这个配置下,测试一直执行4(找铁)动作是可以获得IronIngot的,说明任务并不难,这个action_wrapper可以看作是一种简单的人工策略。
动作设计机制就参考动作3-动作8的联动,优先级捡>获取所需装备/材料>找。需要完成更高级的合成任务时,可按此机制增加动作(如增加找IronSmelter/Steel这些操作)。
在简化动作空间上训练出的一个结果图如下,
可见,智能体可以完成采集树枝、合成/装备木斧、砍苹果树、收集苹果这些操作。实际训练时发现,设置一个合适的reward是能否训练出优秀策略的关键:把生存类的reward调高时(如吃苹果),训练出的模型就只会去吃水/吃苹果,虽然一直死不了,但也不会合成东西;把合成类的reward调高时(如合成铁的reward),训练出的模型就会不顾死活,一直去合成。因此,目前利用PPO训练出的模型很难在生存和合成之间找到一个平衡。