ML-Agents案例之食物收集者

2024-04-03 18:32

本文主要是介绍ML-Agents案例之食物收集者,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本案例源自ML-Agents官方的示例,Github地址:https://github.com/Unity-Technologies/ml-agents,本文是详细的配套讲解。

本文基于我前面发的两篇文章,需要对ML-Agents有一定的了解,详情请见:Unity强化学习之ML-Agents的使用、ML-Agents命令及配置大全。

我前面的相关文章有:

ML-Agents案例之Crawler

ML-Agents案例之推箱子游戏

ML-Agents案例之跳墙游戏

环境说明

在这里插入图片描述

环境中存在有多个智能体,他们的任务是收集尽可能多的绿色食物球,并避免碰到红色球:碰到绿色球奖励+1,碰到红色球-1,此外,智能体之间可以通过发射射线冻结其他智能体,以让自己吃到更多的食物球,达到增加自己分数的目的。

观察空间:使用了网格传感器(Grid Sensor),这个传感器具体说明参考ML-Agents案例之推箱子游戏 的多人模式。

在本案例中传感器直接挂载在智能体本身,z轴(前后方向)网格数为40,x轴(左右方向)网格数为40,y轴(上下方向)网格数为1,检测的标签有,食物,坏食物,其他智能体,被冻结的智能体,墙壁,加上什么都没观测到,维度为40 * 40 * 6 = 960个观察维度。

在这里插入图片描述

在这里插入图片描述

动作空间:3个连续动作输出,对应于前后运动,左右运动和旋转。1个离散输出,对应于是否发生激光。

代码分析

食物生成

控制食物生成的脚本,挂载在空物体上,这个脚本不会自己运作,需要在智能体挂载的脚本上调用。

using UnityEngine;
using Unity.MLAgentsExamples;public class FoodCollectorArea : Area
{public GameObject food;public GameObject badFood;public int numFood;public int numBadFood;public bool respawnFood;public float range;// 生成食物void CreateFood(int num, GameObject type){for (int i = 0; i < num; i++){GameObject f = Instantiate(type, new Vector3(Random.Range(-range, range), 1f,Random.Range(-range, range)) + transform.position,Quaternion.Euler(new Vector3(0f, Random.Range(0f, 360f), 90f)));f.GetComponent<FoodLogic>().respawn = respawnFood;f.GetComponent<FoodLogic>().myArea = this;}}// 重置区域,随机化智能体们的位置,并生成两种食物public void ResetFoodArea(GameObject[] agents){foreach (GameObject agent in agents){if (agent.transform.parent == gameObject.transform){agent.transform.position = new Vector3(Random.Range(-range, range), 2f,Random.Range(-range, range))+ transform.position;agent.transform.rotation = Quaternion.Euler(new Vector3(0f, Random.Range(0, 360)));}}CreateFood(numFood, food);CreateFood(numBadFood, badFood);}public override void ResetArea(){}
}

食物被吃掉的处理

挂载在食物上的脚本,同样需要调用才能起作用:

using UnityEngine;public class FoodLogic : MonoBehaviour
{public bool respawn;public FoodCollectorArea myArea;// 被吃掉后的两种选择,一个是重新随机位置,一个是直接销毁public void OnEaten(){if (respawn){transform.position = new Vector3(Random.Range(-myArea.range, myArea.range),3f,Random.Range(-myArea.range, myArea.range)) + myArea.transform.position;}else{Destroy(gameObject);}}
}

智能体主文件FoodCollectorAgent.cs:

初始化:

public override void Initialize()
{m_AgentRb = GetComponent<Rigidbody>();m_MyArea = area.GetComponent<FoodCollectorArea>();m_FoodCollecterSettings = FindObjectOfType<FoodCollectorSettings>();// 从配置文件中获取参数m_ResetParams = Academy.Instance.EnvironmentParameters;// 设置参数SetResetParameters();
}
// 激光长度
public void SetLaserLengths()
{m_LaserLength = m_ResetParams.GetWithDefault("laser_length", 1.0f);
}
// 设置智能体体积大小
public void SetAgentScale()
{float agentScale = m_ResetParams.GetWithDefault("agent_scale", 1.0f);gameObject.transform.localScale = new Vector3(agentScale, agentScale, agentScale);
}public void SetResetParameters()
{SetLaserLengths();SetAgentScale();
}

观察输入(可以通过设置useVectorObs和useVectorFrozenFlag)来配置是否输入,在案例中没有选择开启这两项输入:

public override void CollectObservations(VectorSensor sensor)
{if (useVectorObs){var localVelocity = transform.InverseTransformDirection(m_AgentRb.velocity);// 输入水平方向的两个速度sensor.AddObservation(localVelocity.x);sensor.AddObservation(localVelocity.z);// 输入是否冻结和是否发射sensor.AddObservation(m_Frozen);sensor.AddObservation(m_Shoot);}else if (useVectorFrozenFlag){// 输入是否冻结和是否发射sensor.AddObservation(m_Frozen);}
}

动作输出:

// 执行输出的主函数,里面的内容都封装到MoveAgent了
public override void OnActionReceived(ActionBuffers actionBuffers)
{MoveAgent(actionBuffers);
}public void MoveAgent(ActionBuffers actionBuffers)
{m_Shoot = false;// 超过冻结时间,解冻if (Time.time > m_FrozenTime + 4f && m_Frozen){Unfreeze();}// 超过被毒时间,解除毒素;超过满意时间,变为正常状态if (Time.time > m_EffectTime + 0.5f){if (m_Poisoned){Unpoison();}if (m_Satiated){Unsatiate();}}var dirToGo = Vector3.zero;var rotateDir = Vector3.zero;var continuousActions = actionBuffers.ContinuousActions;var discreteActions = actionBuffers.DiscreteActions;// 没有冻结的时候才能执行动作if (!m_Frozen){// 获取三个连续输出var forward = Mathf.Clamp(continuousActions[0], -1f, 1f);var right = Mathf.Clamp(continuousActions[1], -1f, 1f);var rotate = Mathf.Clamp(continuousActions[2], -1f, 1f);dirToGo = transform.forward * forward;dirToGo += transform.right * right;rotateDir = -transform.up * rotate;// 获取一个离散输出var shootCommand = discreteActions[0] > 0;if (shootCommand){// 射击时减速m_Shoot = true;dirToGo *= 0.5f;m_AgentRb.velocity *= 0.75f;}// 执行移动m_AgentRb.AddForce(dirToGo * moveSpeed, ForceMode.VelocityChange);transform.Rotate(rotateDir, Time.fixedDeltaTime * turnSpeed);}// 超过一定速度需要减速if (m_AgentRb.velocity.sqrMagnitude > 25f) // slow it down{m_AgentRb.velocity *= 0.95f;}// 执行射击逻辑if (m_Shoot){var myTransform = transform;myLaser.transform.localScale = new Vector3(1f, 1f, m_LaserLength);var rayDir = 25.0f * myTransform.forward;// 绘制射线,参数为起始位置,长度向量,颜色,持续时间,能否被遮挡Debug.DrawRay(myTransform.position, rayDir, Color.red, 0f, true);RaycastHit hit;// 发出球状射线,参数为起始位置,球半径,距离,碰撞到的物体,距离if (Physics.SphereCast(transform.position, 2f, rayDir, out hit, 25f)){// 射线碰到其他智能体,其他智能体会被冻结if (hit.collider.gameObject.CompareTag("agent")){hit.collider.gameObject.GetComponent<FoodCollectorAgent>().Freeze();}}}else{myLaser.transform.localScale = new Vector3(0f, 0f, 0f);}
}
// 被冻结的逻辑,修改标签,计算时间,替换材质
void Freeze()
{gameObject.tag = "frozenAgent";m_Frozen = true;m_FrozenTime = Time.time;gameObject.GetComponentInChildren<Renderer>().material = frozenMaterial;
}
// 解冻的逻辑
void Unfreeze()
{m_Frozen = false;gameObject.tag = "agent";gameObject.GetComponentInChildren<Renderer>().material = normalMaterial;
}// 吃到坏食物的状态
void Poison()
{m_Poisoned = true;m_EffectTime = Time.time;gameObject.GetComponentInChildren<Renderer>().material = badMaterial;
}
// 解毒
void Unpoison()
{m_Poisoned = false;gameObject.GetComponentInChildren<Renderer>().material = normalMaterial;
}
// 吃到好食物的状态
void Satiate()
{m_Satiated = true;m_EffectTime = Time.time;gameObject.GetComponentInChildren<Renderer>().material = goodMaterial;
}
// 好状态消失
void Unsatiate()
{m_Satiated = false;gameObject.GetComponentInChildren<Renderer>().material = normalMaterial;
}

和食物碰撞的逻辑:

void OnCollisionEnter(Collision collision)
{// 吃到好食物,奖励一分,进入满意状态if (collision.gameObject.CompareTag("food")){Satiate();collision.gameObject.GetComponent<FoodLogic>().OnEaten();AddReward(1f);if (contribute){m_FoodCollecterSettings.totalScore += 1;}}// 吃到坏食物,扣除1分,进入中毒状态if (collision.gameObject.CompareTag("badFood")){Poison();collision.gameObject.GetComponent<FoodLogic>().OnEaten();AddReward(-1f);if (contribute){m_FoodCollecterSettings.totalScore -= 1;}}
}

一轮游戏开始执行的逻辑:

public override void OnEpisodeBegin()
{// 消除所有异常状态Unfreeze();Unpoison();Unsatiate();m_Shoot = false;// 速度位置都归零m_AgentRb.velocity = Vector3.zero;myLaser.transform.localScale = new Vector3(0f, 0f, 0f);transform.position = new Vector3(Random.Range(-m_MyArea.range, m_MyArea.range),2f, Random.Range(-m_MyArea.range, m_MyArea.range))+ area.transform.position;transform.rotation = Quaternion.Euler(new Vector3(0f, Random.Range(0, 360)));// 重新设置参数,包括激光长短,智能体大小SetResetParameters();
}

可以通过以下代码自己控制智能体的输出:

 public override void Heuristic(in ActionBuffers actionsOut){var continuousActionsOut = actionsOut.ContinuousActions;if (Input.GetKey(KeyCode.D)){continuousActionsOut[2] = 1;}if (Input.GetKey(KeyCode.W)){continuousActionsOut[0] = 1;}if (Input.GetKey(KeyCode.A)){continuousActionsOut[2] = -1;}if (Input.GetKey(KeyCode.S)){continuousActionsOut[0] = -1;}var discreteActionsOut = actionsOut.DiscreteActions;discreteActionsOut[0] = Input.GetKey(KeyCode.Space) ? 1 : 0;}

配置文件

配置文件是普通的PPO和SAC配置:

behaviors:GridFoodCollector:trainer_type: ppohyperparameters:batch_size: 1024buffer_size: 10240learning_rate: 0.0003beta: 0.005epsilon: 0.2lambd: 0.95num_epoch: 3learning_rate_schedule: linearnetwork_settings:normalize: falsehidden_units: 256num_layers: 1vis_encode_type: simplereward_signals:extrinsic:gamma: 0.99strength: 1.0keep_checkpoints: 5max_steps: 2000000time_horizon: 64summary_freq: 10000
behaviors:GridFoodCollector:trainer_type: sachyperparameters:learning_rate: 0.0003learning_rate_schedule: constantbatch_size: 256buffer_size: 2048buffer_init_steps: 0tau: 0.005steps_per_update: 10.0save_replay_buffer: falseinit_entcoef: 0.05reward_signal_steps_per_update: 10.0network_settings:normalize: falsehidden_units: 256num_layers: 1vis_encode_type: simplereward_signals:extrinsic:gamma: 0.99strength: 1.0keep_checkpoints: 5max_steps: 2000000time_horizon: 64summary_freq: 60000threaded: false

效果演示

在这里插入图片描述

后记

本案例虽然场景中存在多个智能体,但它们彼此竞争,所以是单智能体案例。相比于前面的案例,新颖之处在于拥有攻击手段,能够发射射线干扰对手,如何利用攻击手段也称为了智能体进化的课题。

这篇关于ML-Agents案例之食物收集者的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/873664

相关文章

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

Python中re模块结合正则表达式的实际应用案例

《Python中re模块结合正则表达式的实际应用案例》Python中的re模块是用于处理正则表达式的强大工具,正则表达式是一种用来匹配字符串的模式,它可以在文本中搜索和匹配特定的字符串模式,这篇文章主... 目录前言re模块常用函数一、查看文本中是否包含 A 或 B 字符串二、替换多个关键词为统一格式三、提

Python get()函数用法案例详解

《Pythonget()函数用法案例详解》在Python中,get()是字典(dict)类型的内置方法,用于安全地获取字典中指定键对应的值,它的核心作用是避免因访问不存在的键而引发KeyError错... 目录简介基本语法一、用法二、案例:安全访问未知键三、案例:配置参数默认值简介python是一种高级编

MySQL中的索引结构和分类实战案例详解

《MySQL中的索引结构和分类实战案例详解》本文详解MySQL索引结构与分类,涵盖B树、B+树、哈希及全文索引,分析其原理与优劣势,并结合实战案例探讨创建、管理及优化技巧,助力提升查询性能,感兴趣的朋... 目录一、索引概述1.1 索引的定义与作用1.2 索引的基本原理二、索引结构详解2.1 B树索引2.2

从入门到精通MySQL 数据库索引(实战案例)

《从入门到精通MySQL数据库索引(实战案例)》索引是数据库的目录,提升查询速度,主要类型包括BTree、Hash、全文、空间索引,需根据场景选择,建议用于高频查询、关联字段、排序等,避免重复率高或... 目录一、索引是什么?能干嘛?核心作用:二、索引的 4 种主要类型(附通俗例子)1. BTree 索引(

HTML中meta标签的常见使用案例(示例详解)

《HTML中meta标签的常见使用案例(示例详解)》HTMLmeta标签用于提供文档元数据,涵盖字符编码、SEO优化、社交媒体集成、移动设备适配、浏览器控制及安全隐私设置,优化页面显示与搜索引擎索引... 目录html中meta标签的常见使用案例一、基础功能二、搜索引擎优化(seo)三、社交媒体集成四、移动

六个案例搞懂mysql间隙锁

《六个案例搞懂mysql间隙锁》MySQL中的间隙是指索引中两个索引键之间的空间,间隙锁用于防止范围查询期间的幻读,本文主要介绍了六个案例搞懂mysql间隙锁,具有一定的参考价值,感兴趣的可以了解一下... 目录概念解释间隙锁详解间隙锁触发条件间隙锁加锁规则案例演示案例一:唯一索引等值锁定存在的数据案例二:

MySQL 表的内外连接案例详解

《MySQL表的内外连接案例详解》本文给大家介绍MySQL表的内外连接,结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录表的内外连接(重点)内连接外连接表的内外连接(重点)内连接内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选,我

Java Stream.reduce()方法操作实际案例讲解

《JavaStream.reduce()方法操作实际案例讲解》reduce是JavaStreamAPI中的一个核心操作,用于将流中的元素组合起来产生单个结果,:本文主要介绍JavaStream.... 目录一、reduce的基本概念1. 什么是reduce操作2. reduce方法的三种形式二、reduce