Press "Enter" to skip to content

从0开始学习 Deep Learning – 02手写纯JS的深度学习玩耍 Chrome 恐龙游戏(一)

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

从0开始学习 Deep Learning – 02手写纯JS的深度学习玩耍 Chrome 恐龙游戏(一)

 

Cowboy_Jr

 

2022年09月24日 22:42·  阅读 74

 

纸上得来终觉浅,绝知此事要躬行

 

上一章文章关于机器学习的基础概念搞清楚了,最快的学习方式就是实践,所以这里用原生 JS 不使用任何 XNN 的库完成了一个简易版的深度学习库,完成 Chrome Dino 游戏的通关。

 

直接上 Demo

 

 

直接上源码: github.com/Cowboy-Jr/a…

 

麻雀虽小五脏俱全,包含了正向传播、逆向传播、梯度下降、损失函数、拟合等等

 

前置准备

 

按照典型的学习方法论,需要理解监督学习的运作模式,也就是

 

收集数据 -> 获取特征 -> 推理 -> 输出结果 -> 验证是否正确
    ^                                       |
    |                                       | 
    |_______________________________________V

 

所以对应在 Chrome Dino 这个游戏里就是

 

跑 -> 获取障碍物信息 -> 推理 -> 是否跳 -> 验证是否正确
^                                       |
|                                       | 
|_______________________________________V

 

有了这个思路就可以工程化项目,但是如何运用数学知识完成机器学习逻辑是个比较麻烦的事情,这个后面继续讲解,我们先从上往下的讲解如何运行这个 AI 游戏。

 

工程

 

仓库目录大致如下

 

- src
   |- ai-deno.ts    // 入口文件
   |- game          // Chrome Dino 游戏
   |- jnn
       |- fm.ts     // 深度学习库
       |- legend.ts // 图例

 

Game 仓库

 

Chrome Dino 的游戏库是从网上 Fork 出来的,没什幺特色之处,中规中矩,唯一之处就是为了能收集特征数据,新增了几个状态的回调。

 

ai-deno.ts

 

主入口文件,结合 Game 和 JNN 和 legend 运行游戏,里面重要是几个游戏状态和对应的逻辑

 

github.com/Cowboy-Jr/a…

 

dino = new Runner('.game', {
    DINO_COUNT: 1,
    // 第一次执行,用于初始化状态
    onFirstTime: handleFirstTime,
    // 重新运行,用于训练模型
    onReset: handleReset,
    // 每次游戏结束,用于收集失败时的特征数据
    onCrash: handleCrash,
    // 跑的时候,用于推理是否需要跳起躲避障碍物
    onRunning: handleRunning
});

 

分析

 

初始化状态 onFirstTime

 

从前面的所提到回调逻辑,可以看出我们需要初始化一些状态,比如说整个 NN 框架的状态,每个 Layer、每个 neuron 的参数,另外就是训练数据的初始化值。

 

let trainingData = {
  input: [],
  output: [],
};

 

这里的训练数据 input 包含了每次游戏失败恐龙离障碍物的距离、速度、障碍物的大小,Output 存储的是每次游戏失败时,Dino 的上一个的状态,例如是跳起还是跑步。

 

跑步 onRunning

 

我们知道在恐龙运行的时候,需要根据当前 Dino 的状态,获取特征数据放入框架中推理他是否应该跳起。

 

const handleRunning = async (dino, state) => {
  const input = convertStateToVector(state);
  let action = 0;
  // running
  if (dino.jumping === false) {
    const [output0, output1] = nn.predict(input);
    if (output1 > output0) {
      // need jump
      action = 1;
      dino.lastJumpingState = state;
    } else {
      // keep running
      action = 0;
      dino.lastRunningState = state;
    }
  }
  // jumping
  await sleep(10)
  updateLegend(nn, input);
  return action;
};

 

state 包含了,当前恐龙的状态。但是,这些状态只并不能直接使用,我们必须要把数据进行归一化,具体为什幺需要归一化,可以网上自行查找。

 

然后我们拿着归一化数据放入框架中进行推理,判断他是否需要跳起。并把当前的推断存储起来用于游戏失败时的状态保存,这样的话,我们在训练模型时可以知道上一次到底是对的还是错的。

 

游戏失败 onCrash

 

每次游戏失败时,会把恐龙的状态存储起来,归一化处理后,放入训练特征数据中,并且吧,应该输出的值也保存在 output 中。

 

const handleCrash = async (dino) => {
  let input = null;
  let output = null;
  if (dino.jumping) {
    // 获取最后一次跳跃状态
    input = convertStateToVector(dino.lastJumpingState);
    // 不跳
    output = [1, 0];
  } else {
    // 获取当前行走状态
    input = convertStateToVector(dino.lastRunningState);
    // 跳
    output = [0, 1];
  }
  trainingData.input.push(input);
  trainingData.output.push(output);
};

 

游戏重新开始 onReset

 

游戏开始后,我们拿着训练特征数据放到框架中进行反向传播训练,训练框架中的参数也就是所谓的模型,具体细节下一篇文章会展开讲解

 

const handleReset = async () => {
  await sleep(1000);
  console.log(trainingData);
  // training data
  nn.fit(trainingData.input, trainingData.output, {
    async onEpochFinish(trainData) {
      // updateLegend(nn, trainData);
    }
  });
};

 

Forever

 

后续的流程就是反复地进行游戏、推理、收集特征数据、训练模型。

 

后续

 

具体学习框架的细节,下一篇文章会展开讲解是如何设计的。敬请期待。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注