前端人工智能?TensorFlow.js 学会游戏通关

15,724 阅读20分钟

题注:如果喜欢我们的文章别忘了点击关注阿里南京技术专刊呦~ 本文转载自 阿里南京技术专刊-知乎,欢迎大牛小牛投递阿里南京前端/后端开发等职位,详见 阿里南京诚邀前端小伙伴加入~

关键字:Tensorflow,JavaScript,AI,前端开发,人工智能,神经网络,遗传算法


先上最终效果

T-Rex Runner 是隐藏在 Chrome 中的彩蛋游戏,最近我用刚推出的 TensorFlow.js 开发了一个完全独立运行于浏览器环境下的 AI 程序,如下图所示 AI 可以轻松控制暴龙(T-Rex)避开障碍物。

AI 在尝试 3 次后逐渐学会了如何控制暴龙避让障碍物

引入遗传算法后,尝试 2 次后 AI 即可学会控制

查看在线演示

  • 神经网络版 - 仅支持 Chrome 桌面版
  • 遗传算法 + 神经网络 - 仅支持 Chrome 桌面版

下载或收藏我在 Github 上的源代码


概述

关于 T-Rex Runner 彩蛋游戏

作为 Chrome 浏览器死忠,你或许早已发现隐藏在 Chrome 浏览器“无法连接到互联网”报错页面中的彩蛋“T-Rex Runner”游戏。

img

如果你还没有玩儿过 T-Rex Runner,可以按照下面几个步骤开启彩蛋:

  1. 打开 Chrome 浏览器,在地址栏输入 chrome://dino,然后回车;
  2. 你将会看到上面的报错界面,但是还处于静止状态,不要怕这就是彩蛋的入口;
  3. 敲击空格键,开始游戏吧!

你的任务就是在不碰到仙人掌和空中的翼龙的情况下保持前行,坚持的时间越久则分数越高,难度也随之越来越大。

关于 TensorFlow.js

img

作为深度学习界的当红炸子鸡——TensorFlow 开源组织终于在 2018 年 3 月推出了首个 JavaScript 版本。TensorFlow.js 可以在浏览器端完成模型训练、执行和再训练等基本任务,并且借助 WebGL 技术,可以和 Python、C++ 版本一样能够通过 GPU 硬件加速完成计算过程。

目前网上关于 TensorFlow.js 的教程寥寥无几,基本上就是官方示例的解析,本文希望能从实例出发,给大家补充一些学习的动力!

关于本文

本文的目标是基于 TensorFlow.js 在浏览器端构建人工神经网络,通过反复训练让 AI 学会如何控制暴龙成功避开障碍物。本文的结构如下:

  1. 改造游戏程序:介绍游戏核心代码逻辑,用 ES6 等技术栈重构游戏代码,并引入全新的游戏生命周期事件。
  2. 实现算法模型:重点介绍如何实现基于人工神经网络的 AI 算法模型。
  3. 集成算法模型:将算法模型与游戏进行集成。
  4. 优化算法模型:通过增加 AI 玩家和暴龙的个数,我们无需改动算法模型,即可轻松提升机器学习效率。
  5. 总结

1. 改造游戏程序

1.1 用现代化前端技术栈重构

T-Rex Runner 的源代码可以在 Chromium 的代码仓库中找到,但是这个小游戏是在 2014 年编写的,使用的都是 ES5 时代的技术,更糟糕的是由于缺少模块化,整个游戏的源代码都放在同一个文件中,这很大程度上增加了理解和修改源代码的难度。

因此,我先花了一个下午的时间,用 ES6/ES7 + LESS + Webpack 等现代化前端技术栈重写了 t-rex-runner 项目,并且引入 ESLint 来保障代码质量。

除此外,我还移除了声效、鼠标控制、移动端支持和 GameOver 画面等相关代码,并且为了后面运用遗传算法,我还为游戏加入了多人模式(Multiplayer Mode,即游戏中同一局,有多只暴龙同时出现)。

有关代码已上传至 Github,详细请见 src/game 目录中。

1.2 游戏核心代码

t-rex-runner 是一个非常标准的面向对象编程游戏程序,事实上你也可以将它作为 HTML5 游戏开发入门的经典示例。重构后的 t-rex-runner 项目,主要包含以下类型:

img

  • Runner 类:这是游戏的核心,掌管整个游戏的生命周期,主要类成员包括:

    • currentSpeed 属性:表示当前游戏速度,玩家坚持的时间越长速度就越快,速度越快难度越高。
    • init() 方法:负责根据 config 参数初始化 CanvasHorizonDistanceMeterTRexGroup 等类的实例,并且首次触发 restart()update()
    • restart() 方法:重置所有运行时参数,重新启动全新一局游戏。
    • update() 方法:刷新并重绘当前帧,通过 requestAnimationFrame() 调用,大约为 60 帧每秒(由 Runtime.getFPS() 方法决定)。
  • Trex 类:代表一只 T-Rex,即暴龙,主要类成员包括:

    • jumping 属性:表示当前暴龙是否已经处于跳跃状态(跳起、跳跃中或落地中等均为 true)。
    • reset() 方法:重置暴龙的所有参数,由 Runner.restart() 在游戏重启前调用
    • startJump() 方法:控制暴龙起跳,用于躲避仙人掌。
    • setDuck() 方法:控制暴龙匍匐前进,用于躲避低空飞行的翼龙。
  • TrexGroup 类:代表包含 n 个暴龙的种群,这在原先代码中是没有的,之所以要有种群的概念是为了支持多玩家模式,即同时有 n 只暴龙以各自独立的方式玩同一局游戏。除拥支持 Trex 类大多数方法外,还包括:

    • lives() 方法:获取当前种群中活着的暴龙数量。
  • Obstacle 类:代表障碍物,例如各种高度、宽度的仙人掌和空中的翼龙等,主要类成员包括:

    • type 属性:表示当前障碍物类型,包括仙人掌(CACTUS_SMALL / CACTUS_LARGE)和翼龙(PTERODACTYL)等。
    • width 和 height 属性:表示障碍物的大小。
    • xPos 和 yPos 属性:表示障碍物的位置。

除以上核心类型外,其他还包括:

  • Horizon 类:代表整个游戏舞台背景或称场景,熟悉游戏开发的同学一定知道它类似于很多框架中 stage / scene / background 概念,在本游戏中包含云、星星、月亮和最重要的障碍物 Obstacles。
  • HorizonLine 类:代表地平线,坑洼不平的路面有助于人推到出当前“速度”。
  • CollisionBox 类:代表一个矩形,通常一个 Trex 或 Obstacle 可以用若干个矩形组成一个近似多边形,用于计算多边形碰撞。
  • Cloud 类:代表云朵,这是为了将来做计算机图像识别时,做干扰项用。
  • **DistanceMeter 类:**代表右上角的距离仪表。
  • ImageSprite 模块:经典的 Sprite 贴图。
  • constants 模块:用于存储游戏的默认配置参数。
  • utils 模块:包含了常用的工具方法。

1.3 提供生命周期事件

为了让 AI 替代人类参与到游戏中,我们除了需要有 Trex.startJump() 这样的输出类方法外,还需在 Runner 类中提供必要的事件作为输入:

img

  • onReset() 事件:当游戏重启时将触发该事件,通常 AI 模型的训练的过程将在此事件中完成。
  • onRunning() 事件:每只没有“crash”的暴龙会在每一次 update() 后触发该事件,事件的返回值将被作为 action,当 action1 时表示将执行跳跃,0 则表示保持不变。 可以利用该事件实现对游戏状态的监控,同时命令暴龙在特定的时机改变跳跃状态。
  • onCrash() 事件:当暴龙撞到仙人掌或者翼龙的时候,将触发该事件,可以通过该事件评价 AI 模型执行的效果,例如在遗传算法中,可以用它来计算种群中某一暴龙模型的排名。

下面是一个示例程序,基于以上生命周期事件:

let runner = null;

// 排名
let rankList = [];

// 初始化游戏。
function setup() {
  // 创建游戏运行时
  runner = new Runner(
    '.game',            // HTML 中对应的游戏 DIV 容器
    {
      T_REX_COUNT: 10,  // 每一局同时有 10 只暴龙
      onReset: handleReset,
      onRunning: handleRunning,
      onCrash: handleCrash
    }
  );
  // 初始化
  runner.init();
}

let firstTime = true;
// 每次游戏重新开始前会调用此方法。
// tRexes 参数表示当前的暴龙种群。
function handleReset({ tRexes }) {
  if (firstTime) {
    firstTime = false;
    tRexes.forEach((tRex) => {
      // 随机初始化每一只暴龙的模型
      // minDistance 在本例中代表可容忍的障碍物最小间距
      tRex.model = { minDistance: Math.random() * 50 };
    });
  } else {
    // 打印排名
    rankList.forEach(
      (tRex, i) => console.info(i + 1, tRex.model.minDistance)
    );
    // 清空排名
    rankList.splice(0);
  }
}

// 在游戏运行中,活着的暴龙会持续调用此方法来询问是否要跳跃。
// tRex 参数表示当前上下文中的暴龙。
// state 参数中,obstacleX 表示距离最近的障碍物的横坐标,obstacleWidth
// 表示障碍物宽度,speed 表示当前游戏全局速度。
// 方法返回 1 表示跳跃,2 则表示不变。
function handleRunning({ tRex, state }) {
  if (state.obstacleX <= tRex.model.minDistance &&
      !tRex.jumping) {
    // 这里我们直接用一个“人工【的】智能”,即:
    // 当前障碍物距离到达阈值,则命令暴龙跳跃
    return 1;
  }
  return 0;
}

const deadTrexes = [];
// 当暴龙“crash”时,会调用此方法来通知。
function handleCrash({ tRex }) {
  // 记录排名,最后 crashed 暴龙排在最前面
  rankList.unshift(tRex);
}

// 订购 DOMContentLoaded 事件以触发 setup() 方法
document.addEventListener('DOMContentLoaded', setup);

2. 实现算法模型

2.1 初中生都能懂的算法模型

“算法模型”一词对于刚接触 AI 的前端同学来说,可能听上去有些高不可测,其实不然,让我们先合上教科书,来一起看看下面这个初中就学过的简单公式:

img
据统计每多一个公式,就会少 n 个读者,这是本文的倒数第 3 个公式

公式中,x 是一输入项(inputs) ,y 是输出项(outputs),而 f(x) 就是模型 (model)的核心函数。例如:

  • y = weight \cdot x + bias
    时,因为是线性函数(Linear Function,也称一次方程),所以被称为线性模型(Linear Model),该模型除了函数公式外,还包含了 weightbias 等参数,举一个例子,据说知乎文章中美多一个公式,就会少 n 个读者,这就是一个典型的线性模型**;**

事实上 AI 决定当前是否需要跳跃也是一个线性模型,用一个线性函数表示就是:

y = w1 * obstacleX + w2 * obstacleWidth + b

obstacleXobstacleWidth 是输入项,它们来自于 handleRunning() 方法的 state 参数,该参数中: - obstacleX 表示距离最近的障碍物的横坐标 - obstacleWidth 表示障碍物宽度 - speed 表示当前游戏全局速度。 当 y 输出的值小于 0 时,则表示需要“跳跃”。

其中 w1w2 分别表示 obstacleXobstacleWidth权重(weight)b偏移量(bias),它们都是该线性模型的参数。

与初中数学有所不同的是,这里的输入和输出通常都是向量(vector),而不像前面的例子中都是标量**,**并且多为线性运算。千万不要被线性数学和公式吓跑,“算法”不完全是“数学”,更不是“算数”,请接着往下看。

2.2 预测,训练和评价

预测 Prediction

在机器学习中,已知输入项 x 和模型求 y 时,被称为**预测(predict)**过程。

训练 Training

通过已知输入项 x 和输出项 y 来调节模型中 w1w2b 参数直到“最佳效果”的过程,被称为训练(train)过程,而 y 因为是已知的输出项,又被称为标签(label),多组xy 在一起被称为**训练数据集(training data set)。**训练通常需要反复执行很多次,才能达到“最佳效果”。

评价 Evaluation

在训练过程中,将训练数据集中的 x 作为输入项,执行预测过程,将预测结果与标签 y 的实际结果进行对比,并通过一个函数得到一个分值用以表示当前模型的拟合能力,被称为评价(evaluatie)过程,这个函数被称为评价函数或损失函数(loss function)

机器学习就是一个不断训练、评价迭代的模型训练过程,训练得越好,则未来预测得越准确。

2.1 和 2.2 这两节中的内容均为笔者自己多年工作实践的总结,与教科书难免有差异还请谅解,有关术语定义请以教科书为准。

2.3 定义算法模型抽象类

在正式进入到 AI 算法实现环节之前,我们还需要定义一个通用的面向对象 AI 模型—— Model 抽象类,其成员主要包括:

  • predict(inputX) 方法:根据给出的 inputX 预测出 y 值并将其返回。
  • train(inputs, labels) 方法:根据输入和标签纸优化模型参数。
  • fit(inputs, labels) 方法:反复执行 train() 方法,停止的条件可以是执行到一定的次数,也可以是当 loss() 方法返回的均方差小于一个阀值。
  • loss(predictedYs, labels) 方法:根据预测值和实际标签值,计算出一个评价值,值越小说明当前模型拟合得越好,默认提供的是均方误差(mean squared error),其实就是将每一个预测值减去标签值然后进行平方,求这个平方的平均值。

上述inputXinputslabels 等都是用**向量(vector)**来表示的,可以用数组来表示,在 TensorFlow.js 中则用 tf.Tensor 表示。

在本项目中,Model 抽象类是所有算法模型的基类,让我们来看一个最简单的模型——随机模型的源代码:

import Model from '../Model';

// 随机模型继承自 Model
export default class RandomModel extends Model {
  // weights 和 biases 是 RandomModel 的模型参数
  weights = [];
  biases = [];

  init() {
    // 初始化就是随机的过程
    this.randomize();
  }

  predict(inputXs) {
    // 最简单的线性模型
    const inputX = inputXs[0];
    const y =
      this.weights[0] * inputX[0] +
      this.weights[1] * inputX[1]+
      this.weights[2] * inputX[2] +
      this.biases[0];
    return y < 0 ? 1 : 0;
  }

  train(inputs, labels) {
    // 随机模型还要啥训练,直接随机!
    this.randomize();
  }

  randomize() {
    // 随机生成所有模型参数
    this.weights[0] = random();
    this.weights[1] = random();
    this.weights[2] = random();
    this.biases[0] = random();
  }
}

function random() {
  return (Math.random() - 0.5) * 2;
}

作者注:千万不要小看这个模型,通过遗传算法,随机模型也能控制暴龙避开障碍物,只是学习效率略低,请在桌面版 Chrome 上观看 Demo

2.4 定义算法模型的输入与输出

输入项

简单来说,我们首先将 1.3 节中 handleRunning() 方法得到的 state JSON 参数转换成一个 3 维向量,即一个 3 维数组,并进行归一化处理,所谓**归一化(Normalize)**可以理解成将一个标量变成 0 - 1 之间的值的函数。相关代码如下:

function handleRunning({ state }) {
  const inputs = convertStateToVector(state);
  ...
}

function convertStateToVector(state) {
  if (state) {
    // 生成一个包含 3 个数字的数组,即向量
    // 数字被归一后,值域为 0 到 1
    // 如 [0.1428, 0.02012, 0.00549]
    return [
      state.obstacleX / CANVAS_WIDTH,      // 障碍物离暴龙的距离
      state.obstacleWidth / CANVAS_WIDTH,  // 障碍物宽度
      state.speed / 100                    // 当前游戏全局速度
    ];
  }
  return [0, 0, 0];
}

输出项

接下来我们来定义输出项,最简单的方法是一个 2 维向量,其中第一维代表暴龙保持状态不变的可能性,而第二维度代表跳跃的可能性。例如:

  • [0, 1] 表示跳跃
  • [0.2158, 0.8212] 表示跳跃
  • [0.998, 0.997] 则表示保持不变,继续前行;
  • f([0.1428, 0.02012, 0.00549]) = [0.2158, 0.8212] 表示预测结果为跳跃
  • 如果 state{ obstacleX: 0.1428, obstacleWidth: 0.02012, speed: 0.00549 },暴龙跳跃后 crash 了,则可以在训练过程中通过将 [0.1428, 0.02012, 0.00549] 对应于 [1, 0] 标签,来告诉 AI 下一次碰到这种情况不要再跳跃了,而应该 保持不变

2.5 人工神经网络模型

受限于篇幅,实在无法将神经网络的原理在此复述。以下内容摘自 Wikipedia

人工神经网络(英语:artificial neural network,缩写 ANN),简称神经网络(neural network,缩写 NN)或类神经网络,在机器学习认知科学领域,是一种模仿生物神经网络(动物的中枢神经系统,特别是大脑)的结构和功能的数学模型计算模型,用于对函数进行估计或近似。神经网络由大量的人工神经元联结进行计算。大多数情况下人工神经网络能在外界信息的基础上改变内部结构,是一种自适应系统。[来源请求]现代神经网络是一种非线性统计性数据建模工具。典型的神经网络具有以下三个部分: 结构Architecture)结构指定了网络中的变量和它们的拓扑关系。例如,神经网络中的变量可以是神经元连接的权重(weights)和神经元的激励值(activities of the neurons)。 **激励函数(Activity Rule)**大部分神经网络模型具有一个短时间尺度的动力学规则,来定义神经元如何根据其他神经元的活动来改变自己的激励值。一般激励函数依赖于网络中的权重(即该网络的参数)。 **学习规则(Learning Rule)**学习规则指定了网络中的权重如何随着时间推进而调整。这一般被看做是一种长时间尺度的动力学规则。一般情况下,学习规则依赖于神经元的激励值。它也可能依赖于监督者提供的目标值和当前权重的值。例如,用于手写识别的一个神经网络,有一组输入神经元。输入神经元会被输入图像的数据所激发。在激励值被加权并通过一个函数(由网络的设计者确定)后,这些神经元的激励值被传递到其他神经元。这个过程不断重复,直到输出神经元被激发。最后,输出神经元的激励值决定了识别出来的是哪个字母。

常见的多层结构的神经网络由三部分组成: 输入层(Input layer),众多神经元(Neuron)接受大量非线形输入消息。输入的消息称为输入向量。 输出层(Output layer),消息在神经元链接中传输、分析、权衡,形成输出结果。输出的消息称为输出向量。 隐藏层(Hidden layer),简称“隐层”,是输入层和输出层之间众多神经元和链接组成的各个层面。隐层可以有多层,习惯上会用一层。隐层的节点(神经元)数目不定,但数目越多神经网络的非线性越显著,从而神经网络的强健性(robustness)(控制系统在一定结构、大小等的参数摄动下,维持某些性能的特性。)更显著。习惯上会选输入节点1.2至1.5倍的节点。

2.6 搭建人工神经网络

img

如上图所示,在本节中我们将搭建一个两层神经网络(Neural Network,简称 NN),输入项为一个三维向量组成的矩阵,输出则为一个二维向量组成的矩阵,隐含层中包含 6 个神经元,激励函数为 sigmoid

下面为 NNModel 的源代码:

import * as tf from '@tensorflow/tfjs';

import { tensor } from '../../utils';
import Model from '../Model';

/**
 * 神经网络模型
 */
export default class NNModel extends Model {
  weights = [];
  biases = [];

  constructor({
    inputSize = 3,
    hiddenLayerSize = inputSize * 2,
    outputSize = 2,
    learningRate = 0.1
  } = {}) {
    super();
    this.hiddenLayerSize = hiddenLayerSize;
    this.inputSize = inputSize;
    this.outputSize = outputSize;
    // 我们使用 ADAM 作为优化器
    this.optimizer = tf.train.adam(learningRate);
  }

  init() {
    // 隐藏层
    this.weights[0] = tf.variable(
      tf.randomNormal([this.inputSize, this.hiddenLayerSize])
    );
    this.biases[0] = tf.variable(tf.scalar(Math.random()));
    // 输出层tput layer
    this.weights[1] = tf.variable(
      tf.randomNormal([this.hiddenLayerSize, this.outputSize])
    );
    this.biases[1] = tf.variable(tf.scalar(Math.random()));
  }

  predict(inputXs) {
    const x = tensor(inputXs);
    // 预测的是指
    const prediction = tf.tidy(() => {
      const hiddenLayer = tf.sigmoid(x.matMul(this.weights[0]).add(this.biases[0]));
      const outputLayer = tf.sigmoid(hiddenLayer.matMul(this.weights[1]).add(this.biases[1]));
      return outputLayer;
    });
    return prediction;
  }

  train(inputXs, inputYs) {
    // 训练的过程其实就是将带标签的数据交给内置的 optimizer 进行优化
    this.optimizer.minimize(() => {
      const predictedYs = this.predict(inputXs);
      // 计算损失值,优化器的目标就是最小化该值
      return this.loss(predictedYs, inputYs);
    });
  }
}

如果你此前使用过 Python 版的 TensorFlow,不难发现上面的代码就是将线性数学公式或者 Python 翻译成了 JavaScript 代码。与 Python 版本不同的是,由于 JavaScript 缺少 Python 符号重载(operation overloading)的语言特性,因此在公式表达方面比较繁琐,例如数学公式:

y = sigmoid(x \cdot weights + biases)

用 Python 表示可直接表示为:

y = tf.sigmoid(tf.matmul(x, Weights) + biases)

而 JavaScript 由于缺少加号符号重载,因此要写成:

y = tf.sigmoid(tf.matMul(x, weights).add(biases));

3. 集成算法模型

在第 2 章中我们重构了 T-Rex Runner 的代码结构,并暴露出生命周期事件以便 AI 截获并控制暴龙的行为,在第 3 章结尾,基于 TensorFlow.js 我们用 50 行代码就构建了一个神经网络,现在我们只需要将两者进行有机的结合,就能实现 AI 玩游戏,具体步骤如下:

  1. handleRunning() 事件处理中,调用模型的 predict() 方法,根据当前 state 决定是否需要跳跃;
  2. handleCrash() 事件处理中,如果暴龙是因为“跳跃”而 crash 的,就在训练数据集中记录标签为“保持不变”,反之则记录为“跳跃”,这就是我们在教育中所谓“吸取教训”、“矫枉过正”的过程;
  3. handleReset() 事件处理中,执行模型的 fit() 方法,根据最新训练数据集进行反复训练。

具体代码片段如下:

let firstTime = true;
function handleReset({ tRexes }) {
  // 由于当前模型中我们只有一只暴龙,因此只需要第一只就够了
  const tRex = tRexes[0];
  if (firstTime) {
    // 首次初始化模型
    firstTime = false;
    tRex.model = new NNModel();
    tRex.model.init();
    tRex.training = {
      inputs: [],
      labels: []
    };
  } else {
    // 根据最新收集的训练数据重新训练
    tRex.model.fit(tRex.training.inputs, tRex.training.labels);
  }
}

function handleRunning({ tRex, state }) {
  return new Promise((resolve) => {
    if (!tRex.jumping) {
      let action = 0;
      const prediction = tRex.model.predictSingle(convertStateToVector(state));
      // tensor.data() 方法是对 tensor 异步求值的过程,返回一个 Promise 对象:
      prediction.data().then((result) => {
        if (result[1] > result[0]) {
          // 应该跳跃
          action = 1;
          // 记录最后“跳跃”时的状态,以备 handleCrash() 复盘时使用
          tRex.lastJumpingState = state;
        } else {
          // 保持不变,并记录最后“保持不变”的状态值,以备 handleCrash() 复盘时使用
          tRex.lastRunningState = state;
        }
        resolve(action);
      });
    } else {
      resolve(0);
    }
  });
}

function handleCrash({ tRex }) {
  let input = null;
  let label = null;
  if (tRex.jumping) {
    // 跳错了,应该保持不变!下次记住了!
    input = convertStateToVector(tRex.lastJumpingState);
    label = [1, 0];
  } else {
    // 不应该保守的,应该跳跃才对!下次记住了!
    input = convertStateToVector(tRex.lastRunningState);
    label = [0, 1];
  }
  tRex.training.inputs.push(input);
  tRex.training.labels.push(label);
}

基于人工神经网络的 AI 模型运行效果请在桌面版 Chrome 上观看 Demo


4. 优化算法模型

如果你观察过这个线上 Demo不难发现,通常在 4-5 次 crash 后,AI 逐渐学会了跳跃障碍的时间和技巧,但是有的时候“运气不好”的话可能需要 10 次以上,那么有没有什么办法可以优化算法呢?答案是肯定的:

  • 一种方法是通过多玩家模式(Multiplayer Mode),即 3 只暴龙同时出现在同一局中,对应背后的 3 个共享失败教训的人工神经网络模型 AI,由于“见多识广” ,这种优化导致成功率极高、大大缩短了训练时间,但是仍然具有一定的随机性。请在桌面版 Chrome 上观看 Demo
  • 另一种方法仍然是多玩家模式,一局共有 10 只暴龙,不同的是我们引入了达尔文的“优胜劣汰”机制,即每一局结束后,坚持时间最长的两只暴龙进行“交配(crossover)”,生出幼崽(offspring),幼崽的“染色体(chromosome)”遗传自于父母,同时为了保证生物多样性,还会随机变化(mutate)一部分基因(gene),这被称为“遗传算法(Genetic Algorithm)”。结合遗传算法后,几乎可以保证在 4 代以内获得最优化的神经网络模型参数。请在桌面版 Chrome 上观看 Demo

5. 总结

本文通过 AI 玩转 T-Rex Runner 的实例,介绍了如何重构游戏代码、利用 TensorFlow.js 快速搭建人工神经网络的过程。

关于未来,本项目计划通过 CNN 卷积神经网络来直接通过捕获 HTML Canvas 中的图像信息,分析 handleRunning() 中的 state 状态。如果你对本项目有兴趣,请在 Github 上关注本项目,我还会继续持续更新。

或许你已经发现,这个项目采用了类似**测试台(Test Bench)**的运行模式,没错,你也可以自己设计新的算法模型并进行测试。

欢迎在下方的留言区中与我交流。


最后请关注我们的专栏:

  • <前端零栈 />:让我们在一起聊聊『前端技术』与『前端人工智能』
  • 阿里巴巴南京技术专刊:阿里巴巴南京研发中心官方的技术交流渠道

阿里巴巴南京研发中心正在招聘前端技术专家和高级工程师(仅社招),欢迎加入我们的团队!