网站公告 | 全新unity3d 完整学习路线,最强课程配套、服务!详情点击
查看: 6085|回复: 4
收起左侧

[开发经验] Unity机器学习 | 编写代理及创建游戏AI

[复制链接]

[开发经验] Unity机器学习 | 编写代理及创建游戏AI[复制链接]

AdvancePikachu 发表于 2018-1-10 11:14:15 [显示全部楼层] |只看大图 回帖奖励 |倒序浏览 |阅读模式 回复:  4 浏览:  6085
自2017年9月Unity推出了机器学习代理工具, 机器学习的开发者持续增长并在社区中获得认可。Unity机器学习代理项目入选mybridge评选2017年最佳30个机器学习开源项目,并在Github中获得1710星。Unity机器学习社区挑战赛也在如火如荼的进行中。


http://forum.china.unity3d.com/thread-30124-1-1.html
今天的文章中,我们将会学习如何设置一个基本代理,它能利用强化机器学习达到随机选择的数字目标。我们将使用到最新的Unity机器学习代理工具创建并训练代理,完成指定任务,并探讨将这个简单的代理优化为真正的游戏AI。

设置Unity机器学习代理工具和TensorFlow

开始之前,我们需要设置好机器学习代理工具。设置请参考以下文章:

配置Unity机器学习代理工具和TensorFlow环境

Mac下配置Unity机器学习代理工具

说明:本文的代码和运行环境基于Windows 10环境。

机器学习代理(ML-Agents)场景设置

当我们完成设置后,打开Unity项目,创建新场景。

首先新建一个游戏对象,命名为“NumberAcademy“。添加“TemplateAcademy”组件到“NumberAcademy“游戏对象中。

TemplateAcademy组件脚本可从Github下载:
https://github.com/Unity-Technol ... /TemplateAcademy.cs

场景设置过程并不需要这个组件做太多事,所以我们只用模板中的基本内容即可。

Unity机器学习 | 编写代理及创建游戏AI

Unity机器学习 | 编写代理及创建游戏AI

NumberAcademy游戏对象


在这个组件下,创建子游戏对象,命名为“NumberBrain“。

添加一个Brain组件。将状态数量(State Size)和动作数量(Action Size)的值设为2。将动作空间类型(Action Space Type)设为离散(Discrete)。

在这项目中,我们将会用到两个独立的动作,也就是“上”和“下”。之所以使用离散类型,是因为这些动作会以整数值形式呈现。

将状态空间类型(State Space Type)设为连续(Continuous)。我们将跟踪两个浮点数,所以使用连续类型。

Unity机器学习 | 编写代理及创建游戏AI

Unity机器学习 | 编写代理及创建游戏AI

NumberBrain游戏对象


将大脑类型(Brain Type)设为玩家(Player),并加入两个动作。选择任意两个键位,在此我们选择的是A和B,并分别赋值为0和1。绑定0的键位将减小当前值,而绑定1的键位将增大它。

Unity机器学习 | 编写代理及创建游戏AI

Unity机器学习 | 编写代理及创建游戏AI

Brain脚本组件


现在我们来创建新脚本,命名为NumberDemoAgent.cs,将其基本类设置为Agent,替换原有的“: MonoBehaviour with : Agent”。代码如下:
[C#] 纯文本查看 复制代码
public class NumberDemoAgent : Agent
 
{
 
                       [SerializeField]
 
                       private float currentNumber;
 
                       [SerializeField]
 
                       private float targetNumber;
 
                       [SerializeField]
 
                       private Text text;
 
                       [SerializeField]
 
                       private Transform cube;
 
                       [SerializeField]
 
                       private Transform sphere;
 
  
 
                       int solved;





currentNumber和targetNumber字段在这段代码里最重要。其它的代码都是用作调试和可视化。我们的代理将挑选一个随机的targetNumber,试着通过使用增长和减小指令,将currentNumber的值调整到目标值。

下面将重写CollectState方法。代码如下:


[C#] 纯文本查看 复制代码
public override List<float> CollectState()
 
                       {
 
                            List<float> state = new List<float>();
 
                            state.Add(currentNumber);
 
                            state.Add(targetNumber);
 
                            return state;
 
                       }



在上面这段段代码里,我们会返回两个浮点数给currentNumber和targetNumber,作为代理的状态。

注意:这里是如何匹配二个状态变量的,而且它们都是浮点数,这就是为什么我们要将状态空间类型设为连续而不是分离。

为了训练我们的代理,需要选取随机目标数,所以要重写AgentReset()方法。代码如下:

[C#] 纯文本查看 复制代码
public override void AgentReset()
 
                       {
 
                            targetNumber = UnityEngine.Random.RandomRange(-1f, 1f);
 
                            sphere.position = new Vector3(targetNumber * 5, 0, 0);
 
                            currentNumber = 0f;
 
                       }



最后也是最重要的一部分是AgentReset()方法。这是我们的输入动作,处理任务,也就是回应动作,如果代理回报(reward)结果则为成功。

代码如下:

[C#] 纯文本查看 复制代码
public override void AgentStep(float[] action)
 
                       {
 
                            if (text != null)
 
                                   text.text = string.Format("C:{0} / T:{1} [{2}]", currentNumber, targetNumber, solved);
 
  
 
                            switch ((int)action[0])
 
                            {
 
                                   case 0:
 
                                          currentNumber -= 0.01f;
 
                                          break;
 
                                   case 1:
 
                                          currentNumber += 0.01f;
 
                                          break;
 
                                   default:
 
                                          return;
 
                            }
 
  
 
                            cube.position = new Vector3(currentNumber * 5f, 0f, 0f);
 
  
 
                            if (currentNumber < -1.2f || currentNumber > 1.2f)
 
                            {
 
                                   reward = -1f;
 
                                   done = true;
 
                                   return;
 
                            }
 
  
 
                            float difference = Mathf.Abs(targetNumber - currentNumber);
 
                            if (difference <= 0.01f)
 
                            {
 
                                   solved++;
 
                                   reward = 1;
 
                                   done = true;
 
                                   return;
 
                            }
 
                       }



首先我们会看到文字的变化。这只是用于调试或可视化,它让我们能看到当前值,还有目标值,以及我们所需要解决问题(即达到目标值)所测试的数值次数。

下一步是切换查看动作和完成任务的地方。在示例中,脚本要么通过减小当前值回应动作0,要么增大当前值回应动作1。除此之外,输入的其它值都无效,但若是脚本得到了其它值,脚本会忽略这个值并返回。

现在我们基于currentNumber移动立方体,这个值会用作x轴偏移值。该立方体在此用于可视化,对实际的逻辑或训练过程没有任何影响。

然后检查currentNumber对大小限制的反应。因为我们选取的随机数是在-1和1之间的,如果随机数为-1.2或是1.2,将视作失败,因为数值超过了极限。本示例中,我们给代理回报-1,表示失败,然后给done变量赋值为true,以让代理能重置并再次尝试。

最后,我们会检查currentNumber和目标值的差值是否小于0.01。若小于,则认为是匹配值,将回报(reward)值设为1.0,表示成功,给done赋值为true表示完成。我们还会再次期间使用solved变量作为计数器,用于调试,这样我们就能知道成功的次数了。

下面是完整的脚本代码:

[C#] 纯文本查看 复制代码
using System.Collections.Generic;
 
using UnityEngine;
 
using UnityEngine.UI;
 
  
 
public class NumberDemoAgent : Agent
 
{
 
                       [SerializeField]
 
                       private float currentNumber;
 
                       [SerializeField]
 
                       private float targetNumber;
 
                       [SerializeField]
 
                       private Text text;
 
                       [SerializeField]
 
                       private Transform cube;
 
                       [SerializeField]
 
                       private Transform sphere;
 
  
 
                       int solved;
 
  
 
                       public override List<float> CollectState()
 
                       {
 
                            List<float> state = new List<float>();
 
                            state.Add(currentNumber);
 
                            state.Add(targetNumber);
 
                            return state;
 
                       }
 
  
 
                       public override void AgentStep(float[] action)
 
                       {
 
                            if (text != null)
 
                                   text.text = string.Format("C:{0} / T:{1} [{2}]", currentNumber, targetNumber, solved);
 
  
 
                            switch ((int)action[0])
 
                            {
 
                                   case 0:
 
                                          currentNumber -= 0.01f;
 
                                          break;
 
                                   case 1:
 
                                          currentNumber += 0.01f;
 
                                          break;
 
                                   default:
 
                                          return;
 
                            }
 
  
 
                            cube.position = new Vector3(currentNumber * 5f, 0f, 0f);
 
  
 
                            if (currentNumber < -1.2f || currentNumber > 1.2f)
 
                            {
 
                                   reward = -1f;
 
                                   done = true;
 
                                   return;
 
                            }
 
  
 
                            float difference = Mathf.Abs(targetNumber - currentNumber);
 
                            if (difference <= 0.01f)
 
                            {
 
                                   solved++;
 
                                   reward = 1;
 
                                   done = true;
 
                                   return;
 
                            }
 
                       }
 
  
 
                       public override void AgentReset()
 
                       {
 
                            targetNumber = UnityEngine.Random.RandomRange(-1f, 1f);
 
                            sphere.position = new Vector3(targetNumber * 5, 0, 0);
 
                            currentNumber = 0f;
 
                       }
 
}




设置代理

现在脚本完成了,我们需要新建游戏对象,命名为“NumberDemoAgent”。

将刚刚写好的NumberDemoAgent.cs脚本附加到该游戏对象上,并将NumberBrain添加到该组件的Brain部分。

Unity机器学习 | 编写代理及创建游戏AI

Unity机器学习 | 编写代理及创建游戏AI

NumberDemoAgent游戏对象


下一步新建文本(Text)对象,将其放到明显的位置,理想位置是在屏幕的中央,字号要大。将该文本对象附加到NumberDemoAgent上。

新建立方体(Cube)对象,将它们也添加到NumberDemoAgent上,这样我们可以看到过程的变化,比阅读一个个数值更轻松。

在运行模式下测试

点击play,就能用设置好的键位A和B,左右移动立方体。当我们将方块向球体移动时,它将增大solved计数器,并重置。如果朝错误的方向移动的太远,它也会重置(请记住1.2的范围值限制)。

训练

当它在运行模式下成功运行后,选择brain并把Brain Type设为外部(External)。保存场景,并构建一个可执行文件,文件中只包含了一个启用调试模式的场景。

Unity机器学习 | 编写代理及创建游戏AI

Unity机器学习 | 编写代理及创建游戏AI


关于输出文件夹,选择python文件夹作为存放这个机器学习代理项目的文件夹。例如,我的文件夹的地址是这个:C:\ml-agents\python。别忘了该可执行文件的名字,因为后续我们就需要它了。

Anaconda / Jupyter

启动Anaconda命令指示符。将目录定位到刚刚的构建目录,也就是那个python文件夹下,命令示例:cd c:\ml-agents\python

输入命令“jupyter notebook”(提示:这里可能要打两次回车),自动打开浏览器界面。会得到这样一个网页界面:

Unity机器学习 | 编写代理及创建游戏AI

Unity机器学习 | 编写代理及创建游戏AI


提示:这里要修改高亮部分。对于env_name变量,要输入可执行文件的名字。对于buffer_size和batch_size,你可以使用截图里的值。要注意,这里面的当前值部分只在测试的时候会看到。

当编辑好Hyperparameters部分后,按照步骤运行。从第一步和第二步开始。在第三步时,你会看到一个打开的游戏窗口。第一次打开时,你也许会得到窗口许可对话框,记得要选择允许。

在进行到第四步时,先注意得到的结果,第一次运行时也许会需要几分钟。

Unity机器学习 | 编写代理及创建游戏AI

Unity机器学习 | 编写代理及创建游戏AI


当它保存多次后,点击stop按钮。然后继续第五步,并运行。这将导出你的训练数据到一个与可执行文件同名的.bytes文件中,导出位置在上述python目录的子目录下。示例:python/models/ppo。

Unity机器学习 | 编写代理及创建游戏AI

Unity机器学习 | 编写代理及创建游戏AI


复制.bytes文件,(再次提醒,它的名字会和你的可执行文件名字一样)将这个粘贴到Unity项目里的某个位置。

选择Brain脚本组件,将Brain Type设为Internal。把.byetes文件添加到Graph Model字段上。

Unity机器学习 | 编写代理及创建游戏AI

Unity机器学习 | 编写代理及创建游戏AI


保存并点击运行,就这样我们就完成创建自定义代理。

结语

这个示例相当简单,只是用来让你理解这个系统的运作方式。我们很期待能看到这个项目你能够继续开发,并构建成更大更有趣的项目,用来控制游戏AI,从而制作出有意思的游戏机制和AI。更多精彩的机器学习文章欢迎访问 Unity官方社区(Unitychina.cn)! 更多Unity机器学习社区挑战赛的信息请访问Unity Connect!

+1
6080°C
4
  • 泰课_robin
  • MoneyCH
  • XIAOWEN
  • HultFang
过: 他们
因分享而快乐,学习以自强!
泰课_robin 发表于 2018-1-10 11:47:19 显示全部楼层
支持点赞多谢分享,ai的分享确实比较少
来自苹果客户端来自苹果客户端
因分享而快乐,学习以自强!
MoneyCH 发表于 2018-1-10 19:20:37 显示全部楼层
带你赚币带你飞,泰斗里面有正妹。
因分享而快乐,学习以自强!
XIAOWEN 发表于 2018-1-11 10:07:26 显示全部楼层
是我太笨了吗?这讲的看不明白他到底做出了一个什么东西。
因分享而快乐,学习以自强!
HultFang 发表于 2018-1-11 10:09:23 显示全部楼层
v5v5v5v5v5v5v5v5v5v5v5v5v5v5v5v5v5v5v5
因分享而快乐,学习以自强!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

VR/AR版块|Unity3d|Unreal4|新手报道|小黑屋|站点地图|沪ICP备14023207号-9|【泰斗社区】-专注互联网游戏和应用的开发者平台 ( 浙ICP 备 13006852号-15 )|网站地图

© 2001-2013 Comsenz Inc.  Powered by Discuz! X3.4

1
QQ