MLAgents - 跑一个Dome
公众号
MLAgents的更新令人抓狂,很多的函数竟然不是进行版本继承,而是重新编写,然后就是写出来就一堆报错。
环境
python 3.10
pytorch 2.5.1+cu11.8
MLAgents 22(最新的就是最好的)
mlagents最好是下载到本地再安装,这样就不会断掉,而且在导入Unity的时候简单方便。
安装流程(只有最优解)
把MLAgents包下到本地,搜索MLAgents。
点击这个Unity ML-Agents Toolkit - Github
可能会有点慢,也有可能链接不上。需要一点魔法。
点击all release,找到所有的版本。
找到最新的版本,我这里是22。
滑到底部,
点击第一个下载
当然了,百度网盘链接,no thanks.
通过网盘分享的文件:ml-agents-release_22.zip
链接:
https://pan.baidu.com/s/146eJ9C-jyHI_wnrwrmeehw?pwd=tdcc 提取码: tdcc
MLAgents的github页面这里其实有个Documentation,可以试着去学习。
全套说明,应有尽有。
安装 Unity:确保你安装了 Unity 2023.2 或更高版本。这是运行 ML-Agents 环境的前提。
安装 Python:安装 Python 3.10.1 到 3.10.12 之间的版本。推荐使用 3.10.12 版本。
克隆仓库:克隆 ML-Agents 的 GitHub 仓库。这是获取最新版本和 bug 修复的推荐方式。如果你不克隆仓库,你将无法访问示例环境、训练配置或 com.unity.ml-agents.extensions Unity 包。此外,入门指南假设你已经克隆了仓库。
安装 Unity 包:
安装 com.unity.ml-agents Unity 包。这是 ML-Agents Toolkit 的核心包。
安装 com.unity.ml-agents.extensions Unity 包(可选)。这个包提供了额外的功能和扩展。
安装 Python 包:
安装 mlagents-envs Python 包。这个包提供了与 ML-Agents 环境交互所需的 Python 环境。
安装 mlagents Python 包。这个包包含了 ML-Agents Toolkit 的 Python API。
接下来就是图文解说
1、找个位置,新建文件夹ML,记得不要使用中文。
2、下载Unity Hub
3、安装Unity
我下的是2023.2.20f1c1,因为不是LTS版本,因此后面可能会不维护,不过根据官方的文档,我们可以下载Unity 2023.2 或更高版本。
Unity有个很ex的东西,就是运行软件与文件的版本不对,就会报错,所有需要hub来下载多个版本的Unity。
在hub新建一个项目
注意一下,编译器版本,模板,项目名字和项目路径(这里选择之前新建的文件夹)。记得不要使用中文。
4、配置MLAgents环境
(1)在项目的右边有三个点。在资源管理器中显示,就可以跳到项目的路径下。
那个MLAgents文件放哪?
我的建议是放在Dome_S的并列路径下,自由度会高很多。
双击打开你的Unity项目,我的叫Dome_S。
接下来就是右键,选择在终端中打开,这样就不用再cd过来了。
python配置环境,首先需要新建一个虚拟环境。
我这里使用pip新建,用conda也是可以的。
py -m venv +你自定义的环境名字
激活环境
你自定义的环境名字\Scripts\activate
激活后,就会在路径的前面出现环境名字。接下来就可以安装了。
首先更新以下pip
python -m pip install --upgrade pip
再下载pytorch,这个可以去到官网下载,这个是基操了。
因为我的下载有点问题,因此我是使用whl下载的。
pip install torch-2.5.1+cu118-cp310-cp310-win_amd64.whl
详细内容可以参考这篇文章
关于如何正确启动GPU来跑DeepLearning
往上一级cd (Linux基操)
cd ..
然后cd到MLAgents文件夹
cd ML_A 跳到ML_A文件夹,这是我保存MLAgents文件的文件夹名字。
ls 查看ML_A中的文件
可以看到
cd ml-agents-envs
pip install e .
cd ml-agents
pip install e .
只要没报错就是安装成功。
5、配置Unity的MLAgents环境
打开Unity项目,
Window -> Packge Manager
点击左上角的+,选择install package from disk...
选择com.unity.ml-agents->package.json
继续选择 com.unity.ml-agents.extensions->package.json,没什么可选,初选都是必选。
unity就会自动下载。
搭建主体
一个球和一个方块
新建一个sence
在Hierarchy中新建一个RLDome场景
右键 ->Create Empty
然后创建 一个Plane(命名为Floor),一个方块Cube(Target),一个球Sphere(RollerAgent)
点击RLDome->右键->
修改物体的位置,参数就是图上的参数。
点击一下物体就能看到其属性
选择球,可以看到对应的Inspector面板,划到最下面,有个Add Component。
添加一个Rigidbody
参数
单击Add Component->New Script,新建一个脚本RollerAgent,当然也可以不新建NewScript,不过为了后期好管理,就新建一个吧。
右键->C# Script
就会得到这个
将这个文件直接拖到球那
球的属性就会有
因为只需要获取球和方块的x、y和z,还有球的x和y的速度。那就是8维数据,因此我们需要在球的属性中新建一个Behavior Parameters,参数设置如下。
RollerAgent代码
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;
using UnityEngine;public class RollerAgent : Agent
{[SerializeField]private Transform Target; // 方块目标public float speed = 10; // 小球移动速度private Rigidbody rBody; // 小球刚体private void Start(){// 获取刚体组件rBody = GetComponent<Rigidbody>();}/// <summary>/// Agent重置:每次训练开始时调用/// </summary>public override void OnEpisodeBegin(){// 如果小球掉落平台,重置其位置和速度if (this.transform.position.y < 0){rBody.velocity = Vector3.zero;rBody.angularVelocity = Vector3.zero;transform.position = new Vector3(0, 0.5f, 0);}// 随机移动目标方块的位置Target.position = new Vector3(Random.value * 8 - 4, 0.5f, Random.value * 8 - 4);}/// <summary>/// 收集智能体的观察值/// </summary>/// <param name="sensor"></param>public override void CollectObservations(VectorSensor sensor){// 添加目标的位置 (3 个值:x, y, z)sensor.AddObservation(Target.position);// 添加小球的位置 (3 个值:x, y, z)sensor.AddObservation(transform.position);// 添加小球的速度 (2 个值:x, z,因为 y 方向不需要)sensor.AddObservation(rBody.velocity.x);sensor.AddObservation(rBody.velocity.z);}public override void OnActionReceived(ActionBuffers actionBuffers){// 获取动作数组:连续动作var continuousActions = actionBuffers.ContinuousActions;// 动作控制小球的移动Vector3 controlSignal = Vector3.zero;controlSignal.x = continuousActions[0]; // x 轴方向的力controlSignal.z = continuousActions[1]; // z 轴方向的力rBody.AddForce(controlSignal * speed);// 计算小球与目标的距离float distanceToTarget = Vector3.Distance(transform.position, Target.position);// 不同情况给奖励if (distanceToTarget < 1.42f){// 到达目标SetReward(1.0f);EndEpisode();}if (transform.position.y < 0){// 小球掉落EndEpisode();}}/// <summary>/// 手动测试用的动作生成逻辑(启用 Heuristic Only 时调用)/// </summary>/// <param name="actionsOut"></param>public override void Heuristic(in ActionBuffers actionsOut){var continuousActions = actionsOut.ContinuousActions;continuousActions[0] = Input.GetAxis("Horizontal"); // 左右continuousActions[1] = Input.GetAxis("Vertical"); // 前后// 调试信息Debug.Log($"Heuristic Actions: {continuousActions[0]}, {continuousActions[1]}");}}
记得保存
开始训练
准备一个训练参数文件rollerball_config.yaml,放到哪里都行但是要记住,我在config文件夹下。
我是在.\Dome_S\Assets\新建config文件夹,并新建一个txt文件,将以下代码复制进去,保存为rollerball_config.yaml,强制修改文件类型是可以的。
注释版本
behaviors:# 定义了一个名为 RollerBallBrain 的智能体行为配置RollerBallBrain:# 指定使用的强化学习算法是近端策略优化(PPO)trainer_type: ppo# 定义了训练过程中的超参数hyperparameters:# 每次更新策略时使用的样本数量batch_size: 64# 经验回放缓冲区的大小,用于存储智能体的经验buffer_size: 2048# 学习率,控制模型权重更新的步长learning_rate: 0.0003# PPO 算法中的一个超参数,用于调整策略更新的稳定性beta: 0.005# PPO 算法中的裁剪参数,用于限制策略更新的幅度epsilon: 0.2# 用于计算广义优势估计(GAE)的折扣因子lambd: 0.95# 每个批次数据在更新策略时使用的迭代次数num_epoch: 3# 定义了神经网络的结构network_settings:# 是否对输入数据进行标准化处理normalize: true# 神经网络隐藏层的单元数量hidden_units: 128# 神经网络的隐藏层数量num_layers: 2# 定义了奖励信号的配置reward_signals:# 外部奖励信号,通常由环境提供extrinsic:# 奖励折扣率,用于计算未来奖励的当前价值gamma: 0.99# 奖励信号的强度,用于调整奖励的影响力strength: 1.0# 定义了训练过程中的一些通用设置max_steps: 500000# 每个训练周期中智能体可以采取的最大步数time_horizon: 64# 训练过程中记录和显示摘要信息的频率,以步数为单位summary_freq: 10000
开始训练
还是在我们的项目文件夹下打开终端。使用命令行启动训练。
本来我是使用这个命令的
mlagents-learn config\rollerball_config.yaml --run-id=RollerBall-1 --train
很抱歉,这是过时的,会报错。
应该使用这个
mlagents-learn config/rollerball_config.yaml --run-id=RollerBall-1 --inference
-
mlagents-learn
:这是 ML-Agents Toolkit 提供的命令行工具,用于训练和测试智能体。 -
config/rollerball_config.yaml
:这是你的训练配置文件的路径。rollerball_config.yaml
是一个 YAML 文件,它包含了智能体训练所需的所有配置信息,如超参数、网络结构、奖励信号等。这个文件应该位于项目的config
目录下。 -
--run-id=RollerBall-1
:这个选项指定了运行的 ID。在 ML-Agents 中,每个训练周期都会生成一组模型和摘要文件,这些文件需要一个唯一的标识符来区分。在这里,RollerBall-1
就是这个训练周期的标识符。如果你之前已经使用这个 ID 进行了训练,那么这个命令将会加载那个训练周期的模型进行推理。 -
--inference
:这个选项告诉mlagents-learn
工具,你想要进行的是模型推理,而不是训练。在推理模式下,智能体会使用已经训练好的模型来在环境中执行动作,而不是学习新的行为。
嘿,还是有问题,但不是什么大问题,就是上一次训练的残留,导致的报错。
使用
mlagents-learn config/rollerball_config.yaml --run-id=RollerBall-1 --force
清除残留
再使用训练语句就ok了
mlagents-learn config/rollerball_config.yaml --run-id=RollerBall-1 --inference
记得一定要点击unity中的play,才能继续train。
然后等待Mean Reward接近于1 就可以Ctrl c 停止了
训练完后就可以用了。
测试
很简单
找到Assets下的results的RollerBallBrain,选择一个网络。
点击球,查看属性
将其中一个网络拖动到Behavior Parameters的Model中,点击运行就可以看到球自动去撞击方块了。
输出
很贴心,训练后就会有一个onnx文件。
这个时候就是onnx的问题了,可以部署到各种地方,实现嵌入式部署。
import onnx
import mlagents
import onnxruntime as ort
import numpy as np
import time
import torch
import math
import gym
import tensorflow as tf
from gym_unity.envs import UnityToGymWrapper
from mlagents_envs.environment import UnityEnvironment
from mlagents_envs.side_channel.engine_configuration_channel import EngineConfigurationChannel
#from mlagents_envs.base_env import DecisionSteps
from mlagents_envs.base_env import (BehaviorSpec,ActionSpec,DecisionSteps,TerminalSteps,BehaviorMapping,ActionTuple,
)
#from mlagents.trainers.tests.dummy_config import create_observation_specs_with_shapes
#import sys
#sys.path.append('D:/ml-agents-release_17/ml-agents/mlagents/trainers/tests/')
#from dummy_config import create_observation_specs_with_shapes
# 加载模型
model = onnx.load(r'C:\Users\Win10\Desktop\Pyramids.onnx')
# 检查模型格式是否完整及正确
onnx.checker.check_model(model)
# 获取输出层,包含层名称、维度信息
output = model.graph.outputinput = model.graph.input# load the model with ONNX Runtime and look at its input and output.
onnx_session = ort.InferenceSession(r'C:\Users\Win10\Desktop\Pyramids.onnx')
print("input name='{}' and shape={}".format(onnx_session.get_inputs()[0].name, onnx_session.get_inputs()[0].shape))
print("input name1='{}' and shape={}".format(onnx_session.get_inputs()[1].name, onnx_session.get_inputs()[1].shape))
print("output name='{}' and shape={}".format(onnx_session.get_outputs()[0].name, onnx_session.get_outputs()[0].shape))input_name0 = onnx_session.get_inputs()[0].name
input_name1 = onnx_session.get_inputs()[1].nameoutput_name0 = onnx_session.get_outputs()[0].name
output_name1 = onnx_session.get_outputs()[1].name
# This is a non-blocking call that only loads the environment.
unity_env = UnityEnvironment(file_name=r"D:\ml-agents-release\bin\UnityEnvironment")
#unity_env = UnityEnvironment(file_name=r"D:\ml-agents-release_17\bin\UnityEnvironment", seed=1, side_channels=[])engine_config_channel=EngineConfigurationChannel()
#engine_config_channel.set_configuration_parameters(time_scale=0.1)unity_env.reset()#agents=unity_env.get_behavior_names()
#group_name = agents[0]
# group_spec = unity_env.get_behavior_spec(group_name)
#step_result=unity_env.get_steps(group_name)behavior_names = unity_env.behavior_specs.keys()
#behavior_name = unity_env.behavior_specs.keys()
print("behavior_name:{}".format(behavior_names))behavior_names = unity_env.behavior_specs
print("behavior_name:{}".format(behavior_names))
for behavior_name in behavior_names:print(behavior_name)
decision_steps, terminal_steps = unity_env.get_steps(behavior_name)
print("decision_steps {} " .format(decision_steps))
print(decision_steps[0].obs)
print(decision_steps[0].obs[1])
print(behavior_name)
# for agent_id_terminated in terminal_steps:
# print("Agent " + behavior_name + " has terminated, resetting environment.")
# # This is probably not the desired behaviour, as the other agents are still active.
# unity_env.reset()
# actions=[]
# for agent_id_terminated in terminal_steps:
# actions.append(np.random.uniform(-1, 1, 2))#
# if len(actions) > 0:
# unity_env.set_actions(behavior_name, np.array(actions))
# try:
# unity_env.step()
# except:
# print("Something happend when taking a step in the environment.")
# print("The communicatior has probably terminated, stopping simulation early.")
# #break
# unity_env.close()
def to_numpy(tensor):return tensor.detach().cpu().numpy().astype(np.float32) if tensor.requires_grad else tensor.cpu().numpy().astype(np.float32)
episode_count=100
reward=0for i in range(episode_count):# Start interacting with the environment.unity_env.reset()while True:unity_env.step()decision_steps, terminal_steps = unity_env.get_steps(behavior_name)if len(terminal_steps)!=0:#the agent is donegame_over=True#获取环境信息,作为onnx的输入a=list(np.array(decision_steps[0].obs[0]))#激光雷达数据b=list(np.array(decision_steps[0].obs[1]))#终点在小车坐标系下的坐标agentID=decision_steps[0].agent_idobservation=np.asarray(np.array([a+b]),dtype=np.float32)actionMask=np.asarray(decision_steps[0].action_mask, dtype=np.float32)actionMask = np.asarray(np.array([[1,1]]), dtype=np.float32)# 运行onnx模型,模型有两个输入,result = onnx_session.run([], {input_name0: observation, input_name1: actionMask})if result[4][0][0]>result[4][0][1]:discr=0else:discr=1discr=[[discr]]action_tuple=ActionTuple(np.asarray(result[2],dtype=np.float32),np.asarray(discr,dtype=np.int32))#if(result[3][0][0]):# conti_action = np.asarray(discr, dtype=np.int32)# disc_action=np.asarray(result[2],dtype=np.float32)# action_tuple=ActionTuple()## action_tuple.add_continuous(conti_action)# action_tuple.add_discrete(disc_action)#unity_env.set_actions(behavior_name, action_tuple)unity_env.set_action_for_agent(behavior_name,agentID,action_tuple)episode_done=(terminal_steps.group_reward.shape[0]>0)print(episode_done)if episode_done:break
unity_env.close()