Unity强化学习组件ML_Agent——SideChannel的使用

172 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

对于一些非强化学习相关参数,ML-Agent提供了SideChannel的方式

SideChannel有两种实现方式

方式1:

官方定义了两种SideChannel的类:EngineConfigurationChannel和EnvironmentParametersChannel

EngineConfigurationChannel 一般使用 set_configuration_parameters()方法:

参数介绍
width、height窗口对应尺寸. (二者必须同时定义)
quality_level仿真质量
time_scale为模拟中的增量时间定义乘数。如果将其设置为较高的值,则时间将在仿真中更快地经过,但是物理性能可能无法预测。
target_frame_rate指示仿真尝试以指定的帧速率进行渲染。
capture_frame_rate指示仿真考虑两次更新之间的时间始终保持恒定,而不管实际的帧速率如何
from mlagents_envs.environment import UnityEnvironment
from mlagents_envs.side_channel.engine_configuration_channel import EngineConfigurationChannel

channel = EngineConfigurationChannel()

env = UnityEnvironment(side_channels=[channel])

#将时间尺度调为两倍
channel.set_configuration_parameters(time_scale = 2.0)

env.reset()

EnvironmentParametersChannel 有方法set_float_parameter类似字典,有两个参数:key和value

from mlagents_envs.environment import UnityEnvironment
from mlagents_envs.side_channel.environment_parameters_channel import EnvironmentParametersChannel

channel = EnvironmentParametersChannel()

env = UnityEnvironment(side_channels=[channel])

channel.set_float_parameter("parameter_1", 2.0)

env.reset()
...

之后你可以在C#中用如下代码获取"parameter_1":

using UnityEngine;
using Unity.MLAgents.SideChannels;
using Unity.MLAgents;

var envParameters = Academy.Instance.EnvironmentParameters;
float property1 = envParameters.GetWithDefault("parameter_1", 0.0f);

方式2:

对于较为复杂的参数,方式1可能不太好用。

可以分别在C#和Python中定义一个SideChannel的类来实现二者参数的传递

python side: 定义一个继承于SideChannel的类,该类需要如下初始化函数来保证ID一致

def init(self, channel_id: uuid.UUID) -> None: # ChannelID 注:C#为GUID,python为UUID super().init(channel_id)

然后调用函数

on_message_received(self, msg:"IncomingMessage") -> None

来接收C#传来的参数 另外定义send_string、send_bool等函数向unity发送参数

from mlagents_envs.environment import UnityEnvironment
from mlagents_envs.side_channel.side_channel import (
    SideChannel,
    IncomingMessage,
    OutgoingMessage,
)
import uuid
from typing import List


class SideChannelTest(SideChannel):
    def __init__(self, channel_id: uuid.UUID) -> None:
        # ChannelID 注:C#为GUID,python为UUID
        super().__init__(channel_id)
	# 接收函数
    def on_message_received(self, msg: IncomingMessage) -> None:
        """
        Note: We must implement this method of the SideChannel interface to
        receive messages from Unity
        """
        print(msg.read_string())
	
	# 发送函数
    def send_string(self, data: str) -> None:
        """发送一个字符串给C#"""
        msg = OutgoingMessage()
        msg.write_string(data)
        super().queue_message_to_send(msg)

    def send_bool(self, data: bool) -> None:
        msg = OutgoingMessage()
        msg.write_bool(data)
        super().queue_message_to_send(msg)

    def send_int(self, data: int) -> None:
        msg = OutgoingMessage()
        msg.write_int32(data)
        super().queue_message_to_send(msg)

    def send_float(self, data: float) -> None:
        msg = OutgoingMessage()
        msg.write_float32(data)
        super().queue_message_to_send(msg)

    def send_float_list(self, data: List[float]) -> None:
        msg = OutgoingMessage()
        msg.write_float32_list(data)
        super().queue_message_to_send(msg)


channel_id = uuid.UUID("621f0a70-4f87-11ea-a6bf-784f4387d1f7")
side_channel = SideChannelTest(channel_id)

# 在不定义file_name的情况下下面这句应该是可以直接与Unity Editor交互的,但可能因为网络问题我还未成功
# 如果你也不能成功的话,可以试试用UnityEnvironment(file_name= "exe_path", side_channels=[side_channel])
# 其中exe_path为你自unity场景中build出的exe文件的绝对路径
# 关于使用exe文件,也可参阅下面的文章
# https://blog.csdn.net/weixin_48592526/article/details/113482913
env = UnityEnvironment(side_channels=[side_channel])
env.reset()
side_channel.send_string("The environment was reset")

behavior_name = list(env.behavior_specs.keys())[0]  # Get the first group_name
spec = env.behavior_specs[behavior_name]
for i in range(1000):
    decision_steps, terminal_steps = env.get_steps(behavior_name)
    # We send data to Unity : A string with the number of Agent at each
    side_channel.send_string(
        f"Step {i} occurred with {len(decision_steps)} deciding agents and "
        f"{len(terminal_steps)} terminal agents"
    )
    env.step()  # Move the simulation forward

env.close()

unity side: 与python side 及其类似,定义一个继承于SideChannel的类,该类的构造函数中也需要传入ChannelID

然后调用函数

OnMessageReceived(IncomingMessage msg)

来接收C#传来的参数 另外定义SendStingToPython 、SendBoolToPython等函数向unity发送参数

定义继承于SideChannel的类

using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.SideChannels;
using System;

public class SideChannelTest: SideChannel
{
    public SideChannelTest()
    {
        ChannelId = new Guid("621f0a70-4f87-11ea-a6bf-784f4387d1f7");
    }

    protected override void OnMessageReceived(IncomingMessage msg)
    {
        var receivedString = msg.ReadString();
        Debug.Log("From Python : " + receivedString);
    }

    public void SendDebugStatementToPython(string logString, string stackTrace, LogType type)
    {
        if (type == LogType.Error)
        {
            var stringToSend = type.ToString() + ": " + logString + "\n" + stackTrace;
            using (var msgOut = new OutgoingMessage())
            {
                msgOut.WriteString(stringToSend);
                QueueMessageToSend(msgOut);
            }
        }
    }
}

以下脚本要挂载在场景中的某一物体上

using UnityEngine;
using Unity.MLAgents;


public class RegisterStringLogSideChannel : MonoBehaviour
{

    StringLogSideChannel stringChannel;
    public void Awake()
    {
        // We create the Side Channel
        stringChannel = new StringLogSideChannel();

        // When a Debug.Log message is created, we send it to the stringChannel
        Application.logMessageReceived += stringChannel.SendDebugStatementToPython;

        // The channel must be registered with the SideChannelManager class
        SideChannelManager.RegisterSideChannel(stringChannel);
    }

    public void OnDestroy()
    {
        // De-register the Debug.Log callback
        Application.logMessageReceived -= stringChannel.SendDebugStatementToPython;
        if (Academy.IsInitialized){
            SideChannelManager.UnregisterSideChannel(stringChannel);
        }
    }

    public void Update()
    {
        // Optional : If the space bar is pressed, raise an error !
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Debug.LogError("This is a fake error. Space bar was pressed in Unity.");
        }
    }
}
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.SideChannels;


public class RegisterStringLogSideChannel : MonoBehaviour
{

    SideChannelTest sideChannel;
    public void Awake()
    {
        // We create the Side Channel
        sideChannel = new SideChannelTest();

        // When a Debug.Log message is created, we send it to the stringChannel
        Application.logMessageReceived += sideChannel.SendDebugStatementToPython;

        // The channel must be registered with the SideChannelManager class
        SideChannelManager.RegisterSideChannel(sideChannel);
    }

    public void OnDestroy()
    {
        // De-register the Debug.Log callback
        Application.logMessageReceived -= sideChannel.SendDebugStatementToPython;
        if (Academy.IsInitialized)
        {
            SideChannelManager.UnregisterSideChannel(sideChannel);
        }
    }

    public void Update()
    {
        // Optional : If the space bar is pressed, raise an error !
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Debug.LogError("This is a fake error. Space bar was pressed in Unity.");
        }
    }
}

现在,如果您运行python脚本并在出现提示时按Play the Unity Editor,则Unity Editor中的控制台将在每个Python步骤中显示一条消息(如果你是用file_name直接与exe文件交互的话就不能看见控制台了)。另外,如果您在Unity Engine中按空格键,则消息将出现在终端中。