人工智能?呵呵~
前端佬来整花活,手撸一个自动驾驶!!
不信??
那别走,你往下看!

前言
本篇幅主要是通过前端技术三件套(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、最终
最终,我大致搞了这么一个模型,效果如下:

哈哈,还行!
前端佬们凑合看呐