前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Unity3D和TensorFlow教AI投篮

使用Unity3D和TensorFlow教AI投篮

作者头像
AiTechYun
发布2018-08-06 15:00:15
2.3K0
发布2018-08-06 15:00:15
举报
文章被收录于专栏:ATYUN订阅号ATYUN订阅号

编译:yxy

出品:ATYUN订阅号

在本文中,我们将深入探讨如何使用Unity3D和TensorFlow来教AI执行简单的游戏任务:投篮。完整的源代码可以在文末访问Github链接。

游戏简介

有一个游戏,玩家只有一个主要目标:把球投进篮筐。这听起来并不那么难,但是当你的血液告诉流动,心脏疯狂跳动,观众高声喝彩时,可能很难。在这里,我并不讨论经典的美式篮球,而是经典的Midway街机游戏NBA Jam。

如果你曾经玩过NBA Jam或者它授权的任何一个游戏,那么从球员的角度来看,你知道射球的机制非常简单。你只需在完美的时机按下投篮按钮。你有没有想过这个投篮从游戏的角度是如何选择的?如何选择球的弧度?投球有多难?计算机如何知道投篮的角度?

如果你是一个聪明的,喜欢数学的人,你可以用动手算出这些答案,但本人未能通过代数8级,所以……我不能用这种方法解决问题。我需要以不同的方式解决这个问题。而不是采取更简单,更快,更有效的实际做数学运算的路线,我们探探这个问题到底多难,学习一些简单的TensorFlow,并尝试投篮。

入门

我们需要一些准备才能完成这个项目。

  • 统一模拟与现实中的篮球运动
  • 用于训练我们模型的Node.js和TensorFlow.js
  • TensorFlowSharp用于通过ML-Agents资源包在Unity中嵌入我们的模型
  • tsjs-converter用于将TensorFlow.js模型转换为我们可以在Unity中使用的图。
  • Google表格可轻松可视化我们的线性回归

即使你不是这些技术的专家,也完全可以!(我绝对不是这方面的专家!)我会尽力解释它们是如何组合在一起的。使用这么多不同技术的缺陷是我无法详细解释所有内容,但我会尝试尽可能地链接到教育资源!

下载项目

我不会尝试逐步重新创建这个项目,因此我建议在Github上下载源代码,然后我会解释发生了什么。

注意:你需要为Tensorflow 下载ML-Agents Unity资源包的导入,才能在C#中使用。如果你在Unity中找不到有关Tensorflow的任何错误,请确保你已遵循TensorflowSharp的Unity安装文档。

我们的目标是什么?

为了简单起见,我们对这个项目的期望结果非常简单。我们想要解决:如果射手距离篮筐距离为X,用的投篮的力量Y,就这些!我不会尝试进行瞄准。我只想弄清楚投球有多难。

如果你对如何在Unity中制作更复杂的AI感兴趣,你应该查看Unity中更完整的ML-Agents项目。我将在这里讨论的方法设计的简单易懂,并不一定是最佳示例。

ML-Agents:https://github.com/Unity-Technologies/ml-agents

篮筐和球

我们已经讨论了我们的目标:投进篮筐。要将一个球投近篮筐,你首先要有一个篮筐和一个球。这是我们就要用到Unit。

如果不熟悉Unity,你只要知道它是一个游戏引擎,可以让你为所有平台构建2D和3D游戏。它内置了物理的,基础的3D建模和一个很不错的脚本运行环境(Mono),使我们可以用C#编写游戏。

我没什么艺术细胞,只能拖着一些块把这个场景拼凑了起来。

那块红色块代表我们的玩家。篮球框设置有隐形触发器,允许我们检测物体(球)何时通过篮筐。

在Unity编辑器中,你可以看到以绿色标出的隐形触发器。注意,这里有两个触发器,这样我们就可以确保我们只计算从顶部到底部落到篮筐的球。

如果我们来看看在/Assets/BallController.cs中的OnTriggerEnter方法(我们的篮球的每个实例都会有的脚本),你可以看到这两个触发器怎样配合使用。

代码语言:javascript
复制
private void OnTriggerEnter(Collider other)
代码语言:javascript
复制
{
代码语言:javascript
复制
  if (other.name== "TriggerTop")
代码语言:javascript
复制
  {
代码语言:javascript
复制
     hasTriggeredTop= true;
代码语言:javascript
复制
  }else if (other.name== "TriggerBottom") {
代码语言:javascript
复制
     if (hasTriggeredTop  && !hasBeenScored)
代码语言:javascript
复制
     {
代码语言:javascript
复制
        GetComponent<Renderer>().material= MaterialBallScored;
代码语言:javascript
复制
        Debug.Log(String.Format("{0}, {1}, {2}", SuccessCount++, Distance, Force.y));
代码语言:javascript
复制
     }
代码语言:javascript
复制
     hasBeenScored= true;
代码语言:javascript
复制
  }
代码语言:javascript
复制
}

首先,这个函数要确保顶部和底部的触发器被击中,那么它改变了球的性质(其实就是颜色),所以我们可以直观的看到球被投入篮筐,最后,它注销我们关心的两个关键变量distance和force.y。

投篮

打开/Assets/BallSpawnerController.cs。这是一个生成我们的射手,产生篮球并尝试投篮的脚本。请查看DoShoot()方法末尾处的这个片段。

代码语言:javascript
复制
var ball= Instantiate(PrefabBall, transform.position, Quaternion.identity);
代码语言:javascript
复制
var bc= ball.GetComponent<BallController>();
代码语言:javascript
复制
bc.Force= new Vector3(
代码语言:javascript
复制
  dir.x* arch* closeness,
代码语言:javascript
复制
  force,
代码语言:javascript
复制
  dir.y* arch* closeness
代码语言:javascript
复制
);
代码语言:javascript
复制
bc.Distance= dist;

这个代码Instantiates是一个球的新实例,然后设置我们射击的力度和距目标的距离(所以我们可以稍后更容易地记录下来)。

在/Assets/BallController.cs中,也可以查看我们的Start()方法。在我们创建新篮球时调用此代码。

代码语言:javascript
复制
void Start ()
代码语言:javascript
复制
{
代码语言:javascript
复制
  var scaledForce= Vector3.Scale(Scaler, Force);
代码语言:javascript
复制
  GetComponent<Rigidbody>().AddForce(scaledForce);
代码语言:javascript
复制
  StartCoroutine(DoDespawn(30));
代码语言:javascript
复制
}

换句话说,我们创造一个新的球,给它一些力,然后在30秒后自动销毁这个球,因为我们将要处理很多球,我们要确保一切都是合理的。

让我们来试试,看看我们的全明星射手是怎么做的。你可以点击Unity编辑器中的倒(播放)按钮,我们会看到如下:

我们的球员,我们可以称之为“Red”,几乎准备好了迎战斯蒂芬库里。

然而,Red表现非常糟糕。原因在于Assets/BallController.cs的float force = 0.2f这行。这行要求每一个投篮完全相同。如你所见,Unity直接地采用了它。所以才一次又一次地重复。

这当然不是我们想要的。如果我们从不进行尝试,我们永远学不会像詹姆斯那样的投篮,所以让我们动手改一下。

随机投篮和收集数据

我们可以通过将力度变为随机的来引入一些随机噪声。

代码语言:javascript
复制
float force= Random.Range(0f,1f);

于是,我们终于可以看到成功得分的情况了(即使概率极低)。

Red投的很差,偶尔投进,但这是纯粹的运气。那没关系。此时,任何投篮都是我们可以使用的数据点。我们马上就会谈到这一点。

与此同时,我们不希望只能从一个地方投篮。我们希望Red能够从任何距离成功投篮。在Assets/BallSpawnController.cs中,查找这些行并取消MoveToRandomDistance()的注释。

代码语言:javascript
复制
yield return new WaitForSeconds(0.3f);
代码语言:javascript
复制
// MoveToRandomDistance();

运行后,我们会看到Red在每次投篮后都在球场上移动。

随机运动和随机力量的结合创造了一个非常奇妙的东西:数据。如果你查看Unity中的控制台,你会看到每次投篮时都会记录数据,成功的尝试会逐渐显现。

每次成功击球都会记录到目前为止成功进球的次数,距离篮筐的距离以及投篮所需的力量。这实在是太慢了,让我们为它加速。回到我们添加MoveToRandomDistance()的地方并将0.3f(每次投篮的延迟300毫秒)更改为0.05f(延迟50毫秒)。

代码语言:javascript
复制
yield return new WaitForSeconds(0.05f);
代码语言:javascript
复制
MoveToRandomDistance();

现在运行,看看这一次的投篮。

现在这是一个很好的训练制度!我们可以看到我们成功的投篮得分约6.4%。但他还不是库里。说到训练,我们真的从中学到了什么吗?TensorFlow呢?为什么这很有趣?这是我们下一步要做的。我们现在准备将这些数据从Unity中提取出来,并构建一个模型来预测所需的力度。

预测,模型和回归

在GOOGLE表格中查看我们的数据

在我们深入了解TensorFlow之前,我想看看数据,所以我让Unity运行直到Red成功完成大约50次投篮。这时查看Unity项目的根目录,应该看到一个新文件successful_shots.csv。这是来自Unity的每次成功投篮的原始储存!我有Unity导出这个,以便我可以在电子表格中轻松分析它。

这个.csv文件只有三列index,distance和force。我在Google表格中导入了这个文件并创建了一个带有趋势线的散点图,这样我们就可以了解数据的分布情况。

哇!看那个。我的意思是,看看那个。哇… 我也不知道是什么意思。让我来分析一下我们所看到的。

该图显示了一系列的点,这些点,投篮力度是Y轴,投篮距离是X轴基于拍摄的距离。在这里,我们看到的是所需的力与投篮距离之间有非常明确的相关性(有一些随机的例外情况)。

实际上,你可以将其视为“TensorFlow擅长的东西”。

虽然这个例子很简单,但是TensorFlow的优点之一是,如果我们愿意,我们可以使用类似的代码构建一个更复杂的模型。例如,在一个完整的游戏中,我们可以加入别的特征 – 比如其他游戏的位置,以及统计他们过去被盖帽的频率,以确定我们的球员是应该投篮还是传球。

创建我们的模型

在编辑器中打开tsjs/index.js。这个文件与Unity无关,只是一个基于数据(successful_shots.csv)训练模型的脚本。

下面是训练和保存模型的整个方法……

代码语言:javascript
复制
(async ()=> {
代码语言:javascript
复制
   /*
代码语言:javascript
复制
       Load our csvfile and get it into a properly shaped array of pairs likes...
代码语言:javascript
复制
       [
代码语言:javascript
复制
           [distanceA, forceB],
代码语言:javascript
复制
           [distanceB, forceB],
代码语言:javascript
复制
           ...
代码语言:javascript
复制
       ]
代码语言:javascript
复制
    */
代码语言:javascript
复制
   var pairs= getPairsFromCSV();
代码语言:javascript
复制
   console.log(pairs);
代码语言:javascript
复制
代码语言:javascript
复制
   /*
代码语言:javascript
复制
       Train the model using the data.
代码语言:javascript
复制
    */
代码语言:javascript
复制
   var model= tf.sequential();
代码语言:javascript
复制
   model.add(tf.layers.dense({units:1, inputShape: [1]}));
代码语言:javascript
复制
   model.compile({loss:'meanSquaredError', optimizer:'sgd'});
代码语言:javascript
复制
代码语言:javascript
复制
   const xs= tf.tensor1d(pairs.map((p)=> p[0]/ 100));
代码语言:javascript
复制
   const ys= tf.tensor1d(pairs.map((p)=> p[1]));
代码语言:javascript
复制
代码语言:javascript
复制
   console.log(`Training ${pairs.length}...`);
代码语言:javascript
复制
   await model.fit(xs, ys, {epochs:100});
代码语言:javascript
复制
代码语言:javascript
复制
   await model.save("file://../Assets/shots_model");
代码语言:javascript
复制
})();

我们从.csv文件中加载数据 ,并创建一系列X和Y点。从那里我们要求模型“拟合”这些数据。之后,我们保存模型以备将来使用!

遗憾的是,TensorFlowSharp不接受Tensorflow.js可以保存的格式的模型。所以我们需要做一些翻译工作才能将我们的模型引入Unity。我已经嵌入了一些实用程序来帮助解决这个问题。一般过程是我们将我们的模型从TensorFlow.js Format转换为Keras Format,在哪里,我们可以创建一个检查点,与Protobuf Graph Definition合并得到Frozen Graph Definition,然后拉入Unity。

幸运的是,你可以跳过所有这些并且只运行tsjs/build.sh,如果一切顺利,它将自动完成所有步骤并在Unity中填充frozen模型。

在Unity内部,大家可以看一下Assets/BallSpawnController.cs中的GetForceFromTensorFlow(),看看模型互动的情况。

代码语言:javascript
复制
float GetForceFromTensorFlow(float distance)
代码语言:javascript
复制
{
代码语言:javascript
复制
  var runner= session.GetRunner ();
代码语言:javascript
复制
代码语言:javascript
复制
  runner.AddInput (
代码语言:javascript
复制
     graph["shots_input"][0],
代码语言:javascript
复制
     newfloat[1,1]{{distance}}
代码语言:javascript
复制
  );
代码语言:javascript
复制
  runner.Fetch (graph ["shots/BiasAdd"] [0]);
代码语言:javascript
复制
  float[,] recurrent_tensor= runner.Run () [0].GetValue () asfloat[,];
代码语言:javascript
复制
  var force= recurrent_tensor[0,0]/ 10;
代码语言:javascript
复制
  Debug.Log(String.Format("{0}, {1}", distance, force));
代码语言:javascript
复制
  return force;
代码语言:javascript
复制
}

在进行图的定义时,你将定义一个具有多个步骤的复杂系统。在我们的例子中,我们将模型定义为单个稠密层(具有隐式输入层),这意味着我们的模型采用单个输入并为我们提供一些输出。

在TensorFlow.js中使用model.predict时,它会自动将输入提供给正确的输入图节点,并在计算完成后为你提供正确节点的输出。但是,TensorFlowSharp的工作方式不同,需要我们通过名称直接与图节点进行交互。

考虑到这一点,需要将输入数据转换为图所需的格式并将输出发送给Red。

比赛时间

使用上面的系统,我在模型上创建了一些变体。这是使用仅仅500次成功投篮训练的模型,Red的投篮如下。

我们看到进球率增加了近10倍!如果我们训练Red几个小时并收集10k或100k成功率会怎么样?好吧,我会把它留给你实现。

GitHub:https://github.com/abehaskins/tf-jam

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ATYUN订阅号 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 入门
  • 下载项目
  • 我们的目标是什么?
  • 篮筐和球
  • 投篮
  • 随机投篮和收集数据
  • 预测,模型和回归
    • 在GOOGLE表格中查看我们的数据
      • 创建我们的模型
      • 比赛时间
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档