Unity机器学习 | 编写代理及创建游戏AI
自2017年9月Unity推出了机器学习代理工具, 机器学习的开发者持续增长并在社区中获得认可。Unity机器学习代理项目入选mybridge评选2017年最佳30个机器学习开源项目,并在Github中获得1710星。Unity机器学习社区挑战赛也在如火如荼的进行中。
今天的文章中,我们将会学习如何设置一个基本代理,它能利用强化机器学习达到随机选择的数字目标。我们将使用到最新的Unity机器学习代理工具创建并训练代理,完成指定任务,并探讨将这个简单的代理优化为真正的游戏AI。
设置Unity机器学习代理工具和TensorFlow
开始之前,我们需要设置好机器学习代理工具。设置请参考以下文章:
说明:本文的代码和运行环境基于Windows 10环境。
机器学习代理(ML-Agents)场景设置
当我们完成设置后,打开Unity项目,创建新场景。
首先新建一个游戏对象,命名为“NumberAcademy“。添加“TemplateAcademy”组件到“NumberAcademy“游戏对象中。
TemplateAcademy组件脚本可从Github下载:
https://github.com/Unity-Technologies/ml-agents/blob/master/unity-environment/Assets/ML-Agents/Template/Scripts/TemplateAcademy.cs
场景设置过程并不需要这个组件做太多事,所以我们只用模板中的基本内容即可。
NumberAcademy游戏对象
在这个组件下,创建子游戏对象,命名为“NumberBrain“。
添加一个Brain组件。将状态数量(State Size)和动作数量(Action Size)的值设为2。将动作空间类型(Action Space Type)设为离散(Discrete)。
在这项目中,我们将会用到两个独立的动作,也就是“上”和“下”。之所以使用离散类型,是因为这些动作会以整数值形式呈现。
将状态空间类型(State Space Type)设为连续(Continuous)。我们将跟踪两个浮点数,所以使用连续类型。
NumberBrain游戏对象
将大脑类型(Brain Type)设为玩家(Player),并加入两个动作。选择任意两个键位,在此我们选择的是A和B,并分别赋值为0和1。绑定0的键位将减小当前值,而绑定1的键位将增大它。
Brain脚本组件
现在我们来创建新脚本,命名为NumberDemoAgent.cs,将其基本类设置为Agent,替换原有的“: MonoBehaviour with : Agent”。代码如下:
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方法。代码如下:
public override List<float> CollectState()
{
List<float> state = new List<float>();
state.Add(currentNumber);
state.Add(targetNumber);
return state;
}
在上面这段段代码里,我们会返回两个浮点数给currentNumber和targetNumber,作为代理的状态。
注意:这里是如何匹配二个状态变量的,而且它们都是浮点数,这就是为什么我们要将状态空间类型设为连续而不是分离。
为了训练我们的代理,需要选取随机目标数,所以要重写AgentReset()方法。代码如下:
public override void AgentReset()
{
targetNumber = UnityEngine.Random.RandomRange(-1f, 1f);
sphere.position = new Vector3(targetNumber * 5, 0, 0);
currentNumber = 0f;
}
最后也是最重要的一部分是AgentReset()方法。这是我们的输入动作,处理任务,也就是回应动作,如果代理回报(reward)结果则为成功。
代码如下:
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变量作为计数器,用于调试,这样我们就能知道成功的次数了。
下面是完整的脚本代码:
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部分。
NumberDemoAgent游戏对象
下一步新建文本(Text)对象,将其放到明显的位置,理想位置是在屏幕的中央,字号要大。将该文本对象附加到NumberDemoAgent上。
新建立方体(Cube)对象,将它们也添加到NumberDemoAgent上,这样我们可以看到过程的变化,比阅读一个个数值更轻松。
在运行模式下测试
点击play,就能用设置好的键位A和B,左右移动立方体。当我们将方块向球体移动时,它将增大solved计数器,并重置。如果朝错误的方向移动的太远,它也会重置(请记住1.2的范围值限制)。
训练
当它在运行模式下成功运行后,选择brain并把Brain Type设为外部(External)。保存场景,并构建一个可执行文件,文件中只包含了一个启用调试模式的场景。
关于输出文件夹,选择python文件夹作为存放这个机器学习代理项目的文件夹。例如,我的文件夹的地址是这个:C:\ml-agents\python。别忘了该可执行文件的名字,因为后续我们就需要它了。
Anaconda / Jupyter
启动Anaconda命令指示符。将目录定位到刚刚的构建目录,也就是那个python文件夹下,命令示例:cd c:\ml-agents\python
输入命令“jupyter notebook”(提示:这里可能要打两次回车),自动打开浏览器界面。会得到这样一个网页界面:
提示:这里要修改高亮部分。对于env_name变量,要输入可执行文件的名字。对于buffer_size和batch_size,你可以使用截图里的值。要注意,这里面的当前值部分只在测试的时候会看到。
当编辑好Hyperparameters部分后,按照步骤运行。从第一步和第二步开始。在第三步时,你会看到一个打开的游戏窗口。第一次打开时,你也许会得到窗口许可对话框,记得要选择允许。
在进行到第四步时,先注意得到的结果,第一次运行时也许会需要几分钟。
当它保存多次后,点击stop按钮。然后继续第五步,并运行。这将导出你的训练数据到一个与可执行文件同名的.bytes文件中,导出位置在上述python目录的子目录下。示例:python/models/ppo。
复制.bytes文件,(再次提醒,它的名字会和你的可执行文件名字一样)将这个粘贴到Unity项目里的某个位置。
选择Brain脚本组件,将Brain Type设为Internal。把.byetes文件添加到Graph Model字段上。
保存并点击运行,就这样我们就完成创建自定义代理。
结语
这个示例相当简单,只是用来让你理解这个系统的运作方式。我们很期待能看到这个项目你能够继续开发,并构建成更大更有趣的项目,用来控制游戏AI,从而制作出有意思的游戏机制和AI。更多精彩的机器学习文章欢迎访问 Unity官方社区(Unitychina.cn)! 更多Unity机器学习社区挑战赛的信息请访问Unity Connect!
参考信息
Unity ML Agents GitHub
https://github.com/Unity-Technologies/ml-agents
HyperParameters Doc
https://github.com/Unity-Technologies/ml-agents/blob/master/docs/best-practices-ppo.md
2017年最佳30个机器学习开源项目
https://medium.mybridge.co/30-amazing-machine-learning-projects-for-the-past-year-v-2018-b853b8621ac7
Unity机器学习系列文章
官方活动
2018首场Unity大师课程-上海站 即将开场 (名额有限!)
时间:2018年1月14日
地址:https://www.bagevent.com/event/1031633
时间:截至到2018年1月16日
地址:https://connect.unity.com/challenges/neon
地址: https://connect.unity.com/challenges/ml-agents-1
点击“阅读原文”访问Unity官方社区!