前端人工智能?TensorFlow.js 學會遊戲通關

題注:若是喜歡咱們的文章別忘了點擊關注阿里南京技術專刊呦~ 本文轉載自 阿里南京技術專刊-知乎,歡迎大牛小牛投遞阿里南京前端/後端開發等職位,詳見 阿里南京誠邀前端小夥伴加入~php

關鍵字:Tensorflow,JavaScript,AI,前端開發,人工智能,神經網絡,遺傳算法html


先上最終效果

T-Rex Runner 是隱藏在 Chrome 中的彩蛋遊戲,最近我用剛推出的 TensorFlow.js 開發了一個徹底獨立運行於瀏覽器環境下的 AI 程序,以下圖所示 AI 能夠輕鬆控制暴龍(T-Rex)避開障礙物。前端

AI 在嘗試 3 次後逐漸學會了如何控制暴龍避讓障礙物git

引入遺傳算法後,嘗試 2 次後 AI 便可學會控制github

查看在線演示算法

  • 神經網絡版 - 僅支持 Chrome 桌面版
  • 遺傳算法 + 神經網絡 - 僅支持 Chrome 桌面版

下載或收藏我在 Github 上的源代碼spring


概述

關於 T-Rex Runner 彩蛋遊戲

做爲 Chrome 瀏覽器死忠,你或許早已發現隱藏在 Chrome 瀏覽器「沒法鏈接到互聯網」報錯頁面中的彩蛋「T-Rex Runner」遊戲。chrome

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)**的運行模式,沒錯,你也能夠本身設計新的算法模型並進行測試。

歡迎在下方的留言區中與我交流。


最後請關注咱們的專欄:

  • <前端零棧 />:讓咱們在一塊兒聊聊『前端技術』與『前端人工智能』
  • 阿里巴巴南京技術專刊:阿里巴巴南京研發中心官方的技術交流渠道

阿里巴巴南京研發中心正在招聘前端技術專家和高級工程師(僅社招),歡迎加入咱們的團隊!

相關文章
相關標籤/搜索