项目地址:https://github.com/rossning92/genetic-algorithm
前言
此项目时用JS写的,我在这方面毫无基础,看起代码来比较费劲。
原项目没有注释,根据自己的分析再加上逻辑上的猜测写出来的。
肯定有很大部分一部分都是错误的,仅供参考。
简要介绍
遗传学算法模拟了生物进化论。
此项目用图形模拟生物的前行,边的简谐运动带动图形前进。
简谐运动的振幅,频率,相位作为边的参数,多条边的参数构成了染色体,单个参数构成基因。
适应度是图形向前运动的能力。跑的越快,适应度越高。
第一部分:自然选择
生成初始生物,我们依据适应度的比重生成生物的个数。例如,适应度10的生物就生成10个,适应度5的生物就生成5个。生成的所有个体称为种群,在种群中选择适应度高的生物,也就是跑得快的那个。
第二部分:繁殖
在选择出的个体中,随机挑选两个个体进行单点杂交。所谓单点杂交,在此项目中,就是选出一个点(记i),子代的前i个基因来自母亲,后i个基因来自父亲。
第三部分:突变
繁殖过程中可能产生突变,需要设置突变几率。
此项目涉及的关键技能:
/** Matter:
* 用于 web 的二维物理引擎
* Matter.Body:
* The Matter.Body module contains methods
* for creating and manipulating(操作) body models.
* 有world和Engine两个模块
* 官方文档:https://brm.io/matter-js/docs/
*
* 此项目用三角形或者由多个三角形构成的几何图形来模仿生物的运动
* 三角形的顶点道标关节,边的伸长缩短模拟肌肉的收缩
*
* Chart:
* 用于图表的创建
* 官方文档:https://chartjs.bootcss.com/docs/getting-started/usage.html
*/
准备阶段
// 生物个体类 我只摘抄了部分代码
class Individual {
id: number; //个体id,标记个体
fitness = 0; //适应度
time = 0; //时间不同,计算简谐函数
genes: number[] = []; //基因数组,整体的意思是染色体
nodes: Matter.Body[] = [];
edges: Matter.Constraint[] = []; //Matter.Constraint 模块包含了用于创建和处理约束的方法
/**
* 创造号为id的模拟生物,即视频中的三角形。
* 根据点和边的数目,循环创造闭环,也就是一个三角形
* 虽然用的是createCircle(),但是我感觉不是创建了一个圆,而是一个闭环
*/
onstructor(id: number) {
......
}
// 向2D世界中添加生物
for (const n of this.nodes) {
Matter.World.add(engine.world, n);
}
//创造个体时,振幅,频率,相位的参数是随机生成的,生成的参数作为染色体
for (let j = 0; j < this.edges.length * 3; j++) {
this.genes[j] = randomFloat(0, 1);
}
/** 边的运动(模拟肌肉的收缩),边的每次伸长和缩短按照简谐函数来计算
* 且间隔时间为0.01秒
*/
updateEdges() {
this.time += 0.01;
......// 更新边的长度
}
//更新生物的属性(边的振幅,频率,相位)和适应度
update() {
this.updateEdges(); // 更新生物属性
this.computeFitness(); // 重新计算适应度
}
//适应度的计算:物种在运动时,适应度会随之变化,变化后的值由此函数决定
computeFitness() {
this.fitness = Math.max(...this.nodes.map((x) => x.position.x));
}
......
}
自然选择
//自然选择,根据适应度进行排序
private selection() {
this.population.sort((a, b) => b.fitness - a.fitness);
const maxFitness = this.population[0].fitness;
// Create mating polls 创建交配池。为了根据适应度的比重来重组种群
const matingPool = [];
for (const c of this.population) {
const n = Math.floor((Math.max(c.fitness, 0) / maxFitness) ** 1 * 100);
for (var j = 0; j < n; j++) { // 从本代中选取前n个,n的计算如上
matingPool.push(c);
}
}
return matingPool;
}
繁殖
// 繁殖的过程是主要基因重组,突变的过程,这两个过程用了两个函数单独来实现(此为部分代码)
private reproduction(matingPool: Individual[], best: Individual) {
for (let i = 1; i < this.populationSize; i++) {
// Pick two parents
let j = Math.floor(Math.random() * matingPool.length);
let k = Math.floor(Math.random() * matingPool.length);
// Crossover 单点杂交生成新基因
let newGene = crossover(matingPool[j].genes, matingPool[k].genes);
// Mutation 基因突变
newGene = mutate(newGene);
const c = new Individual(i);
c.genes = newGene;
this.population.push(c); //新基因push进种群中
}
......
}
单点杂交:
/**
* 单点杂交
* 选取一个值(这里是一个随机值)作为点,点前的基因来自母亲,点后的基因来自父亲
* @param genes1 可认为父亲
* @param genes2 母亲
* @returns 孩子的基因
*/
function crossover(genes1: number[], genes2: number[]) {
console.assert(genes1.length == genes2.length);
var childGenes = new Array(genes1.length); // 后代基因序列
// 随机的一个点
let midpoint = Math.floor(Math.random() * childGenes.length);
//基因重组
for (let i = 0; i < childGenes.length; i++) {
childGenes[i] = i > midpoint ? genes1[i] : genes2[i];
}
return childGenes; // 返回孩子的基因序列
}
基因突变:
// mutate 繁殖时有概率会发生变异 突变的点用随机数进行计算
function mutate(genes: number[]) {
return genes.map((x) => (Math.random() < mutationRate ? Math.random() : x));
}
其他
index.ts的下半篇幅我估计是Chart绘图的,当然融合了Matter,对于没有JS基础的我来说,就不细细研究了。我的目的旨在研究遗传算法的大致框架,对这个算法有个大概了解,这个项目就相当于入门的跳板吧!
问题
猜测:如果选择三角形模拟生物,按道理应该会出现旋转前进的个体,这无疑是运动最快的,但是在一次实验中可能不会出现。也就是会陷入局部最优解。跳出局部最优解的方式是基因突变,然而这很困难。
本文仅供学习。
如有错误,请指出。