如何使用 TensorFlow.js 自动化 Chrome 恐龙游戏?
本文为 AI 研习社编译的技术博客,原标题 :
Using TensorFlow.js to Automate the Chrome Dinosaur Game (part 1)
作者 | Aayush Arora
翻译 | 胡瑛皓 编辑 | 王立鱼
原文链接:
https://heartbeat.fritz.ai/automating-chrome-dinosaur-game-part-1-290578f13907
本文将介绍如何用TensorFlow.js自动化Chrome自带的恐龙游戏。如果你之前没有玩过,简单说明一下它是一个附送的游戏,当你离线时 (或Chrome崩溃时) 可以控制一个2d恐龙,需要控制恐龙跳跃躲避障碍。在这里可以玩一把:https://chromedino.com/
神经网络 简言之是一个模拟人脑工作的计算系统模型。人类大脑由多个神经元组成,这些神经元结合在一起共同作出判断。
图中每个连接都带有权重和偏执项,优化这些参数得到需要的结果称为学习。
一个简易神经网络
TensorFlow是近年谷歌的深度学习开发库,它不仅提升了深度学习和机器学习的性能,同时也使开发者能更好的开发人工智能应用。
在TensorFlow的Python版本发布后,谷歌发布了用于Android的TensorFlow Lite,以及大家期待已久的 TensorFlow.js。TF.js 为JavaScript浏览器及后段提供了深度学习的能力。
TensorFlow.js 的API可分为两种—核心API,其中包含基础数学函数、优化器等,发烧友可以据此从零开始构建模型;层API,构建在TensorFlow.js核心之上的高层API,便于用户直接在浏览器上构建、训练、执行深度学习模型。
安装 TensorFlow.js
本文中,我们使用层API。若要安装TensorFlow.js,我需要通过npm安装,命令如下:
[code]npm install @tensorflow/tf.js
在TensorFlow.js项目,我们通常会用webpack, parcel, 或 rollup等工具构建。
这些构建工具各有优缺点。Parcel适合较小的项目,rollup则刚出来。不过这些工具都是通过内部构建依赖图映射到项目的每个模块,最后生成1个或多个bundle。链接中的文章对这些工具做了详细的比较。
这里打个广告,如果想收到机器学习的新闻邮件,可在通过此链接注册,你每周都会收到由专业人士审核的机器学习的邮件。
Chrome 恐龙游戏
我们将创建一个AI系统,像人一样玩这个游戏。
首先我们需要模拟Chrome的自带的恐龙游戏(我们可以在GitHub中找到)。本例中我们用到一个 开源库 ,里面带有Chrome恐龙游戏的完整代码。我们对代码进行了格式化,使代码可读性更好。
让我们一步步来看一下
主类Runner.js中包含恐龙和游戏事件相关的实现。该类允许我们同时使用多个恐龙 (后续博客将使用此特性)。
在这个类里加了3个事件分别为:
- onCrash
- onReset
- onRunning
这三个事件是游戏中主要的分割点。当恐龙碰到障碍物时触发onCrash
方法,onReset方法用于onCrash触发后重置游戏,onRunning方法在每个运动实例中调用以确定恐龙是否应该跳跃。
可参考以下代码:
https://github.com/aayusharora/GeneticAlgorithms
src/game目录中包含复现Chrome恐龙游戏的代码
runner.js导出这些方法,这样nn.js,我们的人工智能类可以访问以上谈到的方法。index.html里包含了HTML的div,在这里我们注入游戏画面以及相关脚本。
很好!! 希望你还在看。
接下来看一下如何设置项目。
设置
src/nn.js
第一步是定义import的内容。
这里我们使用babel-polyfill,这样就能使用ES6的全新特性了,其中包含了新的内置对象诸如 WeakMaps。
接下来引入tensorflow库作为tf对象。
下面导入画布宽度和高度,可以使用特征缩放,最后还有我们提到的Runner类。
[code]import 'babel-polyfill'; import * as tf from '@tensorflow/tfjs'; import { CANVAS_WIDTH, CANVAS_HEIGHT } from './game/constants'; import { Runner } from './game';
接下来初始化runner类的实例为null。
[code]let runner = null;
这里写一个setup函数,当所有DOM元素加载后,该函数将会被调用。
[code]function setup() { // setup code here }
setup函数需要初始化runner实例,设置DINO_COUNT=1,并对应的给出onReset, onCrash, onRunning 事件对应的函数。之前说过Runner类支持多个恐龙同时运行。这里DINO_COUNT就设定了需要模拟的恐龙数量。
[code]runner = new Runner('.game', { DINO_COUNT: 1, onReset: handleReset, onCrash: handleCrash, onRunning: handleRunning });
把runner赋值给window.runner,这样可以作为全局变量访问,然后执行runner.init(),启动游戏。
[code]window.runner = runner; runner.init();
看起来已经完成设定,准备开始了 :)
处理重置
定义firstTime变量,表明恐龙游戏是第一次玩还是需要重置。这样就可以用同一个带条件判断的函数处理重置了。
[code]let firstTime = true;
handleReset方法输入是恐龙数组,runner类创建了一组恐龙实例,支持多个恐龙同时在游戏中。这个设定是为接下来的系列,本文中数组里只含一个恐龙。
[code]function handleReset( dinos ) { //will handle resetting and initialization of the game }
既然本例中只有1个恐龙,就取参数第0个元素。
[code]const dino = dinos[0];
如果是第一次调用,将初始化模型并保存在dino.model对象中。我们用tf.sequential()创建模型,结果返回一个序列模型。接下来在模型中增加2层。
神经网络会接收3个输入, 即定义恐龙状态的参数,例如游戏的速度, 障碍物的宽度,与恐龙间的距离。
所以第一层的输入形状是 [3] ,这是一个2D tensor数组,例如 [ [1 , 1 , 0] ], 就代表3个不同的输入值。我们用的是最基本的激活函数sigmoid函数,为下一层输出6个值。
[code]if (firstTime) { firstTime = false; dino.model = tf.sequential(); dino.model.add(tf.layers.dense({ inputShape:[3], activation:'sigmoid', units:6 }))
这是第二个输出层,包含6个输入,来自前一个隐层。
[code]dino.model.add(tf.layers.dense({ inputShape:[6], activation:'sigmoid', units:2 }))
同样这里的激活函数是Sigmoid。那你觉得输出需要多少单元? 现在只需要2个单元, 对么? 1个输出恐龙跳跃范围是 [0,1] ,另一个输出是恐龙不动范围是 [1,0].
[code]dino.model.compile({ loss:'meanSquaredError', optimizer : tf.train.adam(0.1) })
最终我们用 meanSquaredError loss 函数编译模型,用adam 优化器,其学习率为 0.1,可以试一下这个学习率 :)
在dino.training对象中,含了2个数组保存我们的训练集inputs和labels。
[code]dino.training = { inputs: [], labels: [] };
否则,这不是第一次reset,就用TensorFlow自带的model.fit函数训练神经网络。该函数的参数是两个tensor以向量形式。第一个参数的输入与input的形状一致,第二个参数与output的形状一致。这里用TensorFlow api中的tensor2d函数将二维数组转成系统中的tensor。
[code]else { dino.model.fit(tf.tensor2d(dino.training.inputs),tf.tensor2d(dino.training.labels)); }
太棒了, 我们已经完成了模型构建并实现了训练部分。
那么…
开始预测!
很显然模型预测部分将由handleRunning使用,该函数决定接下来要做什么。
handleRunning方法接收恐龙和状态作为参数。这里的状态是当前Runner的状态 — 里面包含与下一个障碍物 3ff7 的距离、其宽度和游戏速度。返回一个Promise对象,后续会回调恐龙执行的动作。
[code]function handleRunning(dino, state) { return new Promise((resolve) => { //resolve(action) } }
在promise回调函数里,传给匿名函数作为参数——解决回调函数。如果恐龙当前不在跳跃状态,就用模型预测下一个动作。predict方法处会调用ConvertStateToVector方法,其输入是状态对象,返回特征缩放向量。 接下来调用 tf.tensor2d 方法将数组转换成 tensor,然后用它来执行predict方法。
[code]if (!dino.jumping) { // whenever the dino is not jumping decide whether it needs to jump or not let action = 0;// variable for action 1 for jump 0 for not // call model.predict on the state vector after converting it to tensor2d object const prediction = dino.model.predict(tf.tensor2d([convertStateToVector(state)])); // the predict function returns a tensor we get the data in a promise as result // and based on result decide the action
model.predict方法返回一个对象,其中包含data方法,用以返回一个Promise对象。Promise对象中构造匿名回调函数,其输入为result,result是一个简单数组包含预测结果。
既然前文定义了 [0,1] 作为跳跃输出,比较结果 result[1] 和 result[0]: 如果result[1] 大于 result[0], 就让恐龙跳一下; 否则恐龙继续保持奔跑状态。
如果执行跳跃,将action设置为 1 ,将lastJumpingState设置为当前state, 因为我们选择在这个状态跳跃。
[code]const predictionPromise = prediction.data(); predictionPromise.then((result) => { // console.log(result); // converting prediction to action if (result[1] > result[0]) { // we want to jump action = 1; // set last jumping state to current state dino.lastJumpingState = state;
如果不选择跳跃,将lastRunningState设置为当前state, 因为我们选择在这个状态保持奔跑。
[code]else{ dino.lastRunningState = state; }
最后, 代入所需的参数执行Promise (0 保持, 1 预测跳跃) resolve(action);
如果恐龙当前已在跳跃状态,执行 running (0) resolve(0);
好的! 这里已经决定了恐龙如何行动。接下来要处理失败的情况,比如当恐龙 撞到障碍物。这里也是创建训练数据的地方
处理恐龙落在障碍物上
收集训练数据
handleCrash函数会检查恐龙在起跳后是否撞到障碍物,基于这个状态选择向训练集增加什么数据。
[code]function handleCrash(dino) { let input = null; let label = null; // check if at the time of crash dino was jumping or not if (dino.jumping) { // Should not jump next time // convert state object to array
如果恐龙在跳跃时撞到障碍物,这表示之前不应该起跳。那么保存lastJumpingState作为输入,其label为not-jump作为输出。
同样的如果并未及时起跳,但是恐龙碰到了障碍物,这意味着之前应该起跳。取lastRunningState作为输入,其label为jump作为其输出。然后将这些数据作为新的训练数据送入dino.training。
[code]input = convertStateToVector(dino.lastJumpingState); label = [1, 0]; } else {, // Should jump next time // convert state object to array input = convertStateToVector(dino.lastRunningState); label = [0, 1]; } // push the new input to the training set dino.training.inputs.push(input); // push the label to labels dino.training.labels.push(label); }
执行 npm 启动 webpack-dev-server, 可以打开下面的网址看到游戏:
http://localhost:8080
结论和下一步
好的, 本文中我们用神经网络自动化了Chrome恐龙游戏。后续文章将使用遗传算法结合神经网络。尝试使用遗传算法自动化游戏。
到本系列结束时,将对所有三种自动化策略的性能进行比较。
想要继续查看该篇文章相关链接和参考文献?
点击【如何使用 TensorFlow.js 自动化 Chrome 恐龙游戏?】即可访问!
今日资源推荐:《Tensorflow:实战Google深度学习框架》高清完整PDF版
《Tensorflow:实战Google深度学习框架》为使用TensorFlow深度学习框架的入门参考书,旨在帮助读者以最快、最有效的方式上手TensorFlow和深度学习。书中省略了深度学习繁琐的数学模型推导,从实际应用问题出发,通过具体的TensorFlow样例程序介绍如何使用深度学习解决这些问题。《Tensorflow:实战Google深度学习框架》包含了深度学习的入门知识和大量实践经验,是走进这个最新、最火的人工智能领域的首选参考书。
- [TensorFlowJS只如初见]实战四·使用TensorFlowJS拟合曲线(类似TensorFlow原生实现方法)
- 如何使用timeline profile tensorflow 程序
- 如何使用JS开发游戏,游戏框架LayaAir介绍
- 如何使用Createjs来编写HTML5游戏(四)TweenJS和Tick动画
- 如何使用chrome快速实现js想法
- [TensorFlowJS只如初见]实战二·使用TensorFlowJS拟合直线
- Windows下如何使用Tensorflow Object Detection API
- 如何使用Createjs来编写HTML5游戏(五)PreloadJS和SoundJS
- [TensorFlowJS只如初见]实战三·使用TensorFlowJS拟合曲线
- 在使用JS调用Window.close()方法关闭一个窗口是,如何禁止弹出对话窗口?
- 如何使用cocos2d和box2d来制作一个Breakout游戏:第一部分
- 基于Visual C++2010 与office2010开发办公自动化(26)-如何使用自动化输出Excel查询表
- (译)如何使用cocos2d和box2d来制作一个Breakout游戏:第二部分(完)
- 如何使用JS来改变CSS样式
- Select中使用js动态修改了Option时如何触发onchange事件
- 测试中如何使用自动化脚本?
- 如何使用 MFC 自动化 Excel 2000 和 Excel 2002 和 Excel 2000 和 Excel 2002 的一个区域中获取一个数组
- 某编程论坛看到的降低网络游戏CPU占用率的方法。但是看不明白如何使用。求教。
- (译)如何使用GameCenter制作一个简单的多人游戏教程:第二部分
- 使用gamequery做JS游戏(乒乓球游戏)