前端:人工智能?我也会啊!来个花活,😎😎😎“自动驾驶”整起!

人工智能?呵呵~

前端佬来整花活,手撸一个自动驾驶!!

不信??

那别走,你往下看!

前言

本篇幅主要是通过前端技术三件套(JS+HTML+CSS),来一个模拟的"自动驾驶"。

当然,受限于篇幅,只是提供一个大致的框架和流程。

相信大佬一看就懂,不是难事!

预先准备

1、道路场景准备

"自动驾驶"嘛,首先就得是道路的绘制。

道路的绘制,核心点在于:线,由点生成线,然后由线扩充道路的宽度。

大致思路的伪代码如下:

js 复制代码
// 点的类
export default class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  .....其它绘制代码...
}

// 线的类
export default class Segment {
  constructor(startPoint, endPoint) {
    this.startPoint = startPoint;
    this.endPoint = endPoint;
  }
  .....其它绘制代码...
 }
 
 // 由线,生成多边形类以及优化外角为原型
 export default class Polygon {
      constructor(segments) {
        this.segments = segments;
        // 点位循环,加入线条中去
        for (let i = 1; i <= points.length; i++) {
          this.segments.push(new Segment(points[i - 1], points[i % points.length]));
        }
      }
  }

具体效果如下:

另外,小地图的绘制用于缩小整体的地图展示,有了点和线,按照这样的数据,绘制一个没算啥问题。

PS:提示下哈,这些代码都可以由代码AI生成!反正我大部分都是AI生成的

2、小汽车准备

上面的道路达成后,那么就得开始整辆车了。

比如比较贴合真实的车子开动,车需要满足这些的功能。比如:

  • 支持前、后、前左转弯、前右转弯,后左转弯。后右转弯;

  • 模拟摩擦力,向前或者向后的动力,如果向前或者前后的动力不再持续,则速度会慢慢降至0;

效果如下(以下由键盘的↑↓←→键控制):

OK,成了,那再加上车与道路的碰撞,当车碰撞到路,直接消失;,如下:

嗯,好像不赖的样子。

有一说一,像我这种手残的控制,转弯基本都能撞墙。😭😭😭😭😭😭😭😭😭😭😭😭。。

开始,上"感知"!

1、汽车的感知--"雷达"线

车子、道路都有准备好了,现在咱们得想想,怎么给车赋予一个感知能力。

现实生活中,让汽车能感知周围环境,目前有两种主流方式:一种是雷达成像,另一种为摄像头直接探测;

摄像头应该用过手机的都能理解,雷达成像的话,前端佬们自己去搜搜吧,好像还挺前沿的。

由于我们仅仅是模拟,那么我们就直接模仿"雷达"线,如下图:

其中黑色为道路之外的距离,黄色为道路内的距离。

ok,这个感知搞定。

2、装"智能"--神经网络算法

这边先暂时跳过项目,咱先了解下神经网络算法,先看下图:

这玩意就是神经网络算法的最基础图示,基本就是三种类型层级:输入层、隐藏层、输出层。这边就不过多说神经网络的数学推导了,因为我不会,也不能给你说明白!o(╥﹏╥)o

接下来说的可能有点绕,前端佬需要集中下注意力!!!!

先说输入层的值,输入层的每个点的值都对应雷达线的实时黑色线长度!!

然后基于输入层的值,计算出隐藏层的值,拿隐藏层A来说,公式如下: 雷达线1黑色长度 * 雷达线1的权重值 + 雷达线2黑色长度 * 雷达线2的权重值 + 雷达线3黑色长度 * 雷达线3的权重值 + 雷达线4黑色长度 * 雷达线4的权重值 + 雷达线5黑色长度 * 雷达线5的权重值,得出一个值。当这个值大于隐藏层A的偏置值,则得出隐藏A为的值1,如果小于,则为0。

就这样逐个算完隐藏层值,我们再算出对应控制键位,公式如下:隐藏层A值 * 隐藏层A权重值 + 隐藏层B值 * 隐藏层B权重值 + 隐藏层C值 * 隐藏层C权重值 + 隐藏层D值 * 隐藏层D权重值 + 隐藏层E值 * 隐藏层E权重值 + 隐藏层F值 * 隐藏层F权重值 ,得出一个值。当这个值大于向上↑键位的偏置值,则认为可以按下这个键,如果小于,则认为不用按这个键。

如上,我们就能根据预设好的每一个节点的权重值和偏置值,让车子有对应的应对策略,从而实现自动驾驶

大致计算如上,看不懂没有关系,这里直接上network的基础代码。

js 复制代码
import { lerp } from '../utils/index.js';

export class NeuralNetwork {
  constructor(neuronCounts) {
    this.levels = [];
    // 获取层级
    for (let i = 0; i < neuronCounts.length - 1; i++) {
      this.levels.push(new Level(neuronCounts[i], neuronCounts[i + 1]));
    }
  }
  // 返回层级输出
  static feedForward(givenInputs, network) {
    let outputs = Level.feedForward(givenInputs, network.levels[0]);

    for (let i = 1; i < network.levels.length; i++) {
      outputs = Level.feedForward(outputs, network.levels[i]);
    }
    return outputs;
  }

  // 从最佳策略中,变更值
  static mutate(network, amount = 1) {
    network.levels.forEach((level) => {
      for (let i = 0; i < level.biases.length; i++) {
        level.biases[i] = lerp(level.biases[i], Math.random() * 2 - 1, amount);
      }
      for (let i = 0; i < level.weights.length; i++) {
        for (let j = 0; j < level.weights[i].length; j++) {
          level.weights[i][j] = lerp(
            level.weights[i][j],
            Math.random() * 2 - 1,
            amount,
          );
        }
      }
    });
  }
}
export class Level {
  constructor(inputCount, outputConut) {
    this.inputs = new Array(inputCount);
    this.outputs = new Array(outputConut);
    // 偏差值,每个输出神经元都有一个偏差值
    // 高于该值它将触发这个输出
    this.biases = new Array(outputConut);

    // 权重值
    // 每一条输入,都对应很多条输出,每个输出都有对应的权重值
    this.weights = [];

    for (let i = 0; i < inputCount; i++) {
      this.weights[i] = new Array(outputConut);
    }

    Level.#randomize(this);
  }

  // 随机数据
  static #randomize(level) {
    for (let i = 0; i < level.inputs.length; i++) {
      for (let j = 0; j < level.outputs.length; j++) {
        level.weights[i][j] = Math.random() * 2 - 1;
      }
    }

    for (let i = 0; i < level.biases.length; i++) {
      level.biases[i] = Math.random() * 2 - 1;
    }
  }

  // 反馈
  static feedForward(givenInputs, level) {
    // console.log(level)
    for (let i = 0; i < level.inputs.length; i++) {
      level.inputs[i] = givenInputs[i];
    }
    for (let i = 0; i < level.outputs.length; i++) {
      let sum = 0;
      // 计算每一条线的积分,输入加权重
      for (let j = 0; j < level.inputs.length; j++) {
        sum += level.inputs[j] * level.weights[j][i];
      }

      // 判断,如果大于偏差值,则输出结果
      if (sum > level.biases[i]) {
        level.outputs[i] = 1;
      } else {
        level.outputs[i] = 0;
      }
      // level.outputs[i] = Math.tanh(sum + level.biases[i]);
    }

    return level.outputs;
  }
}
3、动起来--随机森林

好了,经过上面的一番操作,如果已经有很好的每一层的权重值和偏置值,那么汽车基本可以很好的。

很显然,一开始就有这么神奇的事情,也只有copy才能做到。

如果没有咋办呢?

咋办?凉拌!!!!直接上随机值,能碰一个算一个(细心前端佬可以发现,本身里面就加入了随机值这么玩)。

我如上就随机生成了一个,如下:

运气相当爆棚,好像一开始就很智能了,但到后面显然不太好,转角直接碰头了。

这个就说明,我们初始的数据不好!也就是所谓的模型数据不理想。

接下来咋办呢?如何将这个汽车训练的越来越好呢?

4、挑好的-遗传算法

不知道看到这里的前端佬,有没有看过一部动漫《火影忍者》,里面的主人公有一种绝壁技能:多重影分身。

而这个多重影分身绝对是挂一样的存在,用这个学习忍术,别人几十年才会的,他能几个月就能办到,非常的变态。我也解释下,方便没有看过的前端佬,就是使用这个法术你可以得到几百跟你同样的分身,而这些分身也有智能,可以依靠你的指导同时去学习一个东西,而要其中有一个会了,然后再散了功,那么你就会了。

基于这种思路,那我们就可以基于随机生成的参数,随机生成1000个模型,哪个瞅着还行就用哪个,如下图(车子分身需要久一点才能看到):

就是这样。

但这里还隐藏一个问题,如果纯纯的随机,模型之间就会过于跳跃,遇到个好的全靠命了。

所以,如上js代码中,会将输入的每个权重值,都会以lerp(线性插值)去设置,这也就是所谓的遗传之一了。让每一个值能在一定的范围上下波动。从而每选择一个好的模型,都能从其中去做一个突变,得到一个更优秀的原型。

5、最终

最终,我大致搞了这么一个模型,效果如下:

哈哈,还行!

前端佬们凑合看呐

相关推荐
我是天龙_绍2 小时前
vue3 props 如何写ts,进行类型标注
前端
叫我詹躲躲3 小时前
n8n 自动化工作流平台完整部署
前端·langchain·领域驱动设计
遂心_4 小时前
为什么 '1'.toString() 可以调用?深入理解 JavaScript 包装对象机制
前端·javascript
IT_陈寒4 小时前
JavaScript 性能优化:5 个被低估的 V8 引擎技巧让你的代码快 200%
前端·人工智能·后端
惯导马工4 小时前
【论文导读】ORB-SLAM3:An Accurate Open-Source Library for Visual, Visual-Inertial and
深度学习·算法
王同学QaQ4 小时前
Vue3对接UE,通过MQTT完成通讯
javascript·vue.js
岛风风4 小时前
关于手机的设备信息
前端
ReturnTrue8685 小时前
nginx性能优化之Gzip
前端
程序员鱼皮5 小时前
刚刚 Java 25 炸裂发布!让 Java 再次伟大
java·javascript·计算机·程序员·编程·开发·代码