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

人工智能?呵呵~

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

不信??

那别走,你往下看!

前言

本篇幅主要是通过前端技术三件套(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、最终

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

哈哈,还行!

前端佬们凑合看呐

相关推荐
Cccp.1231 分钟前
【leetcode】(六) 图和贪心算法
数据结构·算法·leetcode
y1su3 分钟前
【Leetcode】1477. 找两个和为目标值且不重叠的子数组
数据结构·后端·算法·leetcode·职场和发展
不会就选b8 小时前
算法日常・每日刷题--<贪心>14
算法
AlienZHOU9 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
mmmmath_311 小时前
LeetCode.541.反转字符串II
数据结构·算法·leetcode
Navigator_Z11 小时前
LeetCode //MySQL - 1251. Average Selling Price
c语言·算法·leetcode
Captaincc12 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
醇氧12 小时前
MySQL 8.0 系统表损坏与引擎转换故障排查实战
数据结构·算法
大熊背12 小时前
《Color constancy by characterization of illumination chromaticity》之色度色域最大化算法(二)
算法·白平衡·色度·色温