ML-Agents与python的Low Level API通信

司空健
2023-12-01

本文基于我前面的文章Unity强化学习之ML-Agents的使用
参考Github链接:https://github.com/Unity-Technologies/ml-agents
参考文档:https://unity-technologies.github.io/ml-agents/

在之前利用Unity3D中的ML-Agents插件训练出来的游戏AI中,我们只需要自己创建对应的环境,设定深度强化学习所必要的几个因素:

  1. 神经网络所接收的状态state
  2. 神经网络输出的动作action
  3. action控制智能体的运动
  4. 回合结束条件与重置后的状态
  5. 每一个step在不同条件下智能体得到的奖励reward
  6. 训练算法及其参数(设定对应的yaml文件)

设定好后,在控制台输入对应的命令就能够用官方对应的算法训练自己的智能体。
这种算法本质上也是调用了Unity官方编写的python代码,采用官方的PPO或者SAC算法来训练自己单智能体,采用MA-POCA算法来训练自己的多智能体,对于不懂强化学习算法来说十分友好,无需关注算法本身,只需要关注如何搭建环境即可,配置对应的算法参数即可训练出不错的游戏AI。
然而,这种良好的封装带来的问题就是,我们如果想要对算法进行微调就变得有些困难了,封装得越完美,就越难以从底层入手进行改进,如果要进行强化学习的科研,我们的代码越轻量化,自己能掌握的部分越多,越利于我们进行改进,因此有必要搭建自己的python框架对Unity的环境进行训练。

python与Unity通信方法

这里采用的是ML-Agents官方的Low Level API进行通信,Unity 和 Python 之间的通信是通过open socket进行的,无需身份验证。因此,请确保进行训练的网络是安全的。
对于强化学习训练来说,与环境的通信我们至少需要知道几点:

  1. 如何创建一个环境
  2. 如何实时获取环境中每个智能体实时的状态,这一个step是否结束episode
  3. 如何给智能体输入动作action使其推进一个step
  4. 推进step后,如何获取状态以及对应的奖励reward

对环境数据的获取可以参考OpenAI的Gym环境,Gym在对环境进行Reset之后就可以返回智能体的state,对环境step函数输入action就能返回下一个step的状态state,奖励reward以及否结束episode,因此对于Unity也需要获取这些环境信息。
方便的一点是,在我们编写的Unity环境中,我们已经设定好了环境reset的条件,所以我们不用在python端给它reset。

创建一个环境

首先建议最好能把环境打包成可执行文件,把python代码和可执行文件放到同一个目录下。
我们可以用以下代码来运行一个Unity环境:

channel = EngineConfigurationChannel()
env = UnityEnvironment(file_name="UnityEnvironment", seed=1, side_channels=[channel])
channel.set_configuration_parameters(time_scale = 3.0)

其中file_name参数就是可执行文件的文件名,如果设为None,则直接在Unity编辑器中训练,time_scale可以控制环境运行的速度。

查看环境基本数据

下面的代码可以查看环境智能体的behavior_name,对应的队伍,状态空间和动作空间

behavior_names = list(env.behavior_specs.keys())
behavior_value = list(env.behavior_specs.values())
for i in range(len(behavior_names)):
    print(behavior_names[i])
    print("obs:",behavior_value[i].observation_specs, "   act:", behavior_value[0].action_spec)

查看智能体数据

对于只有一个behavior_name的环境来说,我们可以通过这样的代码获取同一个behavior_name下的智能体状态,奖励等信息。

DecisionSteps, TerminalSteps = env.get_steps(behavior_names[0])
agentsNum = len(DecisionSteps.agent_id)
print("exist:",DecisionSteps.agent_id,"   Dead:",TerminalSteps.agent_id)
print("reward:",DecisionSteps.reward,"reward_dead:",TerminalSteps.reward)
print("obs:",DecisionSteps.obs,"DeadObs:",TerminalSteps.obs)
print("interrupted:", TerminalSteps.interrupted)

这是一个重要的部分,get_steps函数贯穿了整个强化学习的始终,它返回了两个值,第一个值DecisionSteps里面包含了正在执行动作的智能体信息,TerminalSteps包含的是正处于回合结束的智能体信息,很多时候,DecisionSteps或TerminalSteps里面不一定有值,在DecisionSteps有值的智能体,我们才需要对其执行动作

执行动作

用以下代码即可操作智能体

continuous_actions = (np.random.rand(agentsNum, 2) - 0.5) * 2
discrete_actions = None
actions = ActionTuple(continuous_actions, discrete_actions)
env.set_actions(behavior_names[0],actions)

对于智能体的操作可以分为离散动作和连续动作,我们需要分布对其进行赋值,对于DecisionSteps没有数据的智能体,我们不需要对其进行赋值。

完整示例代码

该代码基于ML-Agents官方环境3DBall,将3DBall打包之后,将该python代码放在同一个目录下,启动对应的ml-agents python环境,运行该代码,即可看到一个运行随机动作的环境

from mlagents_envs.environment import UnityEnvironment
import numpy as np
from mlagents_envs.environment import ActionTuple
import time
from mlagents_envs.side_channel.engine_configuration_channel import EngineConfigurationChannel


# if file_name=None ,  use editor to train
channel = EngineConfigurationChannel()
env = UnityEnvironment(file_name="UnityEnvironment", seed=1, side_channels=[channel])
# 环境运行速度调整为3倍
channel.set_configuration_parameters(time_scale = 3.0)

env.reset()
behavior_names = list(env.behavior_specs.keys())
behavior_value = list(env.behavior_specs.values())
# 这里可以得到observation和action的shape
for i in range(len(behavior_names)):
    print(behavior_names[i])
    print("obs:",behavior_value[i].observation_specs, "   act:", behavior_value[0].action_spec)
# 这里可以得到智能体组的数据,包括状态,奖励,ID等,DecisionSteps包含回合未结束的智能体信息
# TerminalSteps包含回合已结束的智能体信息,包括结束原因等
# 智能体回合结束后进入TerminalSteps,并且下一回合进行重置并进入DecisionSteps,也有可能此回合同时进入DecisionSteps
DecisionSteps, TerminalSteps = env.get_steps(behavior_names[0])
print("agentIds:",DecisionSteps.agent_id)
print("rewards:",DecisionSteps.reward)
print("obs:",DecisionSteps.obs)
values = list(DecisionSteps.values())
for i in range(len(values)):
    print(values[i])
print("ter_reward:",TerminalSteps.reward)
print("interrupted:", TerminalSteps.interrupted)     # 是否由于步数到了而停止
# 重置环境的条件以及外部奖励函数都在环境中的C#脚本中编写,这里只负责收集数据进行训练,实现训练和数据收集的分离处理
discrete_actions = None
this_time = time.time()
for i in range(1000000):
    print("step:",i,"  last_step_cost:",time.time() - this_time)
    this_time = time.time()
    DecisionSteps, TerminalSteps = env.get_steps(behavior_names[0])
    agentsNum = len(DecisionSteps.agent_id)
    print("exist:",DecisionSteps.agent_id,"   Dead:",TerminalSteps.agent_id)
    #print("reward:",DecisionSteps.reward,"reward_dead:",TerminalSteps.reward)
    #print("obs:",DecisionSteps.obs,"DeadObs:",TerminalSteps.obs)
    continuous_actions = (np.random.rand(agentsNum, 2) - 0.5) * 2
    actions = ActionTuple(continuous_actions, discrete_actions)
    #print("actions:", actions.continuous)
    env.set_actions(behavior_names[0],actions)
    env.step()


env.close()

展望

后面在此基础之上将强化学习算法PPO对现有环境进行训练,敬请期待!

 类似资料: