遗传算法可视化

2,169 阅读3分钟

遗传算法(Genetic Algorithm)作为一种优化算法,其本质是通过对多个个体的基因交叉、变异、选择等操作来实现对问题求解的优化过程。为了方便直观地观察算法的演化过程,我们可以通过可视化的方式展示遗传算法中每个步骤的执行情况。具体实现过程如下:

  1. 确定遗传算法所要解决的问题,并选择相应的编码方式和适应度函数进行建模。一般而言,遗传算法所解决的问题需要转化为一个数学模型,例如最大化某一目标函数或最小化某一成本项。
  2. 对于给定的问题,确定需要优化的参数以及它们的取值范围。这些参数被称作基因(Gene),具有适应度(Fitness)值。
  3. 初始化种群(Population),即初始时包含若干个基因的集合。初始种群的大小应该足够大,以确保能够找到比较优的解决方案。
  4. 通过交叉(Crossover)和变异(Mutation)操作,生成新的个体。交叉操作是指将两个个体互相交换一部分基因序列,从而得到一对新的个体;变异操作则是指随机改变某些基因的取值,从而使得新的个体具有一定的随机性。
  5. 计算新生成的个体的适应度,并按照一定的规则进行选择(Selection)。在选择过程中,可以采用轮盘赌算法、锦标赛算法等多种方式,从而保证优秀的基因能够被更好地保留下来,并不断进化。
  6. 重复执行第4-5步,直到达到指定的终止条件。常见的终止条件包括达到最大迭代次数、达到目标适应度值等。
  7. 绘制可视化图表,展示遗传算法的执行过程和结果。常见的图表类型包括折线图、散点图、柱状图等,通过这些图表可以直观地观察算法的演化过程和最终的优化结果。

首先,在网页中引入 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);
}

这样,我们就完成了一个简单的遗传算法可视化程序。当我们在浏览器中打开该网页时,可以看到一系列随机生成的字符串,它们会不断地进化、交配和变异,直到其中一个个体与目标字符串完全匹配。在不断的进化过程中,我们可以观察到种群的适应度不断提高,最后达到与目标字符串完全匹配的状态。