遗传算法(Genetic Algorithm)作为一种优化算法,其本质是通过对多个个体的基因交叉、变异、选择等操作来实现对问题求解的优化过程。为了方便直观地观察算法的演化过程,我们可以通过可视化的方式展示遗传算法中每个步骤的执行情况。具体实现过程如下:
- 确定遗传算法所要解决的问题,并选择相应的编码方式和适应度函数进行建模。一般而言,遗传算法所解决的问题需要转化为一个数学模型,例如最大化某一目标函数或最小化某一成本项。
- 对于给定的问题,确定需要优化的参数以及它们的取值范围。这些参数被称作基因(Gene),具有适应度(Fitness)值。
- 初始化种群(Population),即初始时包含若干个基因的集合。初始种群的大小应该足够大,以确保能够找到比较优的解决方案。
- 通过交叉(Crossover)和变异(Mutation)操作,生成新的个体。交叉操作是指将两个个体互相交换一部分基因序列,从而得到一对新的个体;变异操作则是指随机改变某些基因的取值,从而使得新的个体具有一定的随机性。
- 计算新生成的个体的适应度,并按照一定的规则进行选择(Selection)。在选择过程中,可以采用轮盘赌算法、锦标赛算法等多种方式,从而保证优秀的基因能够被更好地保留下来,并不断进化。
- 重复执行第4-5步,直到达到指定的终止条件。常见的终止条件包括达到最大迭代次数、达到目标适应度值等。
- 绘制可视化图表,展示遗传算法的执行过程和结果。常见的图表类型包括折线图、散点图、柱状图等,通过这些图表可以直观地观察算法的演化过程和最终的优化结果。
首先,在网页中引入 p5.js 库,创建画布,并定义一些变量:
let population; // 种群对象
let lifespan = 300; // 每个个体的寿命
let lifeP; // 显示寿命的HTML元素
let count = 0; // 计数器,每到lifespan就增加1
let target; // 目标字符串
let maxFitness = 0; // 适应度最高值,即与目标字符串完全匹配时的适应度
let bestPhrase; // 最佳个体(与目标字符串最接近的个体)
let mutationRate = 0.01; // 基因突变率
function setup() {
createCanvas(640, 360);
population = new Population(mutationRate, lifespan);
lifeP = createP();
target = "To be or not to be.";
}
function draw() {
background(220);
population.run();
lifeP.html("Generation #" + population.getGenerations() + "<br>" + "Best phrase: " + bestPhrase);
if (population.isFinished()) {
noLoop();
}
count++;
if (count == lifespan) {
population.evaluate(target);
population.selection();
count = 0;
}
}
紧接着,创建一个构造函数 Population,用来初始化种群,并实现遗传算法的各个步骤:
function Population(mutationRate, lifespan) {
this.mutationRate = mutationRate;
this.lifespan = lifespan;
this.population = [];
this.matingPool = [];
this.generations = 0;
// 初始化种群,随机生成每个个体的基因
for (let i = 0; i < 100; i++) {
this.population[i] = new DNA(this.lifespan);
}
// 计算每个个体的适应度,并挑选出适应度最高的个体
this.evaluate = function(target) {
let maxFitness = 0;
for (let i = 0; i < this.population.length; i++) {
this.population[i].calcFitness(target);
if (this.population[i].fitness > maxFitness) {
maxFitness = this.population[i].fitness;
bestPhrase = this.population[i].getPhrase();
}
}
// 将适应度高的个体添加到交配池中
this.matingPool = [];
for (let i = 0; i < this.population.length; i++) {
let fitness = map(this.population[i].fitness, 0, maxFitness, 0, 1);
let n = floor(fitness * 100);
for (let j = 0; j < n; j++) {
this.matingPool.push(this.population[i]);
}
}
this.generations++;
}
// 从交配池中选择两个个体进行交配,并产生新的个体
this.selection = function() {
let newPopulation = [];
for (let i = 0; i < this.population.length; i++) {
let a = random(this.matingPool).dna;
let b = random(this.matingPool).dna;
let child = a.crossover(b);
child.mutate(this.mutationRate);
newPopulation[i] = new DNA(this.lifespan, child);
}
this.population = newPopulation;
}
this.getGenerations = function() {
return this.generations;
}
this.isFinished = function() {
return bestPhrase == target;
}
// 在画布上显示每个个体的基因序列
this.run = function() {
for (let i = 0; i < this.population.length; i++) {
this.population[i].show(map(i, 0, this.population.length, 0, width), 50);
}
}
}
最后,创建一个构造函数 DNA,用来生成、交配和变异个体的基因:
function DNA(num) {
this.genes = [];
for (let i = 0; i < num; i++) {
this.genes[i] = newChar();
}
this.fitness = 0;
// 计算个体的适应度值
this.calcFitness = function(target) {
let score = 0;
for (let i = 0; i < this.genes.length; i++) {
if (this.genes[i] == target.charAt(i)) {
score++;
}
}
this.fitness = score / target.length;
}
// 在画布上显示个体的基因序列
this.show = function(x, y) {
let phrase = "";
for (let i = 0; i < this.genes.length; i++) {
phrase += this.genes[i];
}
text(phrase, x, y);
}
// 通过交叉操作生成新的基因序列
this.crossover = function(partner) {
let child = new Array(this.genes.length);
let midpoint = floor(random(this.genes.length));
for (let i = 0; i < this.genes.length; i++) {
if (i > midpoint) {
child[i] = this.genes[i];
} else {
child[i] = partner.genes[i];
}
}
return child;
}
// 随机突变个体的基因
this.mutate = function(mutationRate) {
for (let i = 0; i < this.genes.length; i++) {
if (random(1) < mutationRate) {
this.genes[i] = newChar();
}
}
}
// 将基因序列转换为字符串
this.getPhrase = function() {
let phrase = "";
for (let i = 0; i < this.genes.length; i++) {
phrase += this.genes[i];
}
return phrase;
}
}
// 随机生成一个字符(包括空格和标点符号)
function newChar() {
let c = floor(random(63, 122));
if (c == 63) {
c = 32;
} else if (c == 64) {
c = 46;
}
return String.fromCharCode(c);
}
这样,我们就完成了一个简单的遗传算法可视化程序。当我们在浏览器中打开该网页时,可以看到一系列随机生成的字符串,它们会不断地进化、交配和变异,直到其中一个个体与目标字符串完全匹配。在不断的进化过程中,我们可以观察到种群的适应度不断提高,最后达到与目标字符串完全匹配的状态。