유전 알고리즘

“결국 살아남는 종은 강인한 종도 아니고, 지적 능력이 뛰어난 종도 아니다. 종국에 살아남는 것은 변화에 가장 잘 적응하는 종이다.” - 찰스 로버트 다윈

1. 유전 알고리즘이란?

유전 알고리즘(Genetic Algorithm)은 생명체가 환경에 적응하면서 진화하는 모습을 모방하여 최적화 문제를 해결하는 기법입니다.

아래 단계와 그림처럼 유전 알고리즘은 생명과학 시간에 배운 다윈의 적자생존 이론을 바탕으로 진행됩니다.

  1. 집단 생성 : 진화가 이루어질 수 있도록 다양한 유전자를 가진 충분한 개체를 만들어 줍니다.
  2. 선택 : 집단을 평가하여 다음 세대의 부모로 적합한 개체를 결정하는 단계입니다.
  3. 생식(교차) : 문제 해결을 잘 한 부모 개체를 선택하여 유전자를 섞어 자식 세대를 만듭니다.
  4. 돌연변이 : 진화의 다양성을 위해 자식 세대의 유전자 중 일부는 돌연변이가 일어나게 합니다.
  5. 새로운 세대 생성

"유전 알고리즘"

정리하면 환경에 잘 적응하는 우수한 형질을 가진 부모들이 우선적으로 선택되고 생식(교차)을 통해 자손들에게 그 형질이 전달되며 때로는 돌연변이를 통해 부모를 뛰어넘기도 합니다. 세대를 거치면서 이 과정을 반복하면 결국 환경에 잘 적응하는 우수한 형질만 남게 되는 것이지요.

이러한 과정을 이용하여 탐색이나 최적화 문제를 해결하는데 효과적이며, 로봇 이동경로 탐색, 강화 학습이나 자동 머신러닝과 같은 인공지능(AI) 분야에서 많이 사용되고 있습니다.

2. 유전 알고리즘으로 만드는 스마트 로켓

자! 그럼 유전 알고리즘을 코딩으로 구현해 보려고 합니다. 일단 목표지점까지 스스로 길을 찾아가는 스마트 로켓을 만들어 보면서 유전 알고리즘에 대해서 자세히 알아봅시다.

아래 미리 구현된 시뮬레이션의 시작 버튼을 눌러보세요.

삼각형 모양의 로켓들이 흰 원 모양의 목표지점까지 여러 시행착오를 거치면서 몇 세대가 지나면 목표지점에 성공적으로 도착하는 것을 볼 수 있습니다.

그럼 환경이 변하면 어떻게 될까요? 시뮬레이션 위에 마우스를 다른 곳에 놓고 클릭하여 목표지점을 바꿔보세요!

처음에는 로켓들이 길을 잘 못찾고 헤메지만 금세 진화를 거치며 새로운 목표지점에 도달하는 것을 볼 수 있으실 것입니다.

그럼 이 시뮬레이션이 어떻게 구성되어 있는지 의사코드로 살펴보겠습니다.

function setup() {
    1단계 : 초기화 - N개의 요소로 구성된 로켓 집단을 생성
       요소는 랜덤하게 생성된 DNA를 가짐.
    로켓의 DNA에는  시간별로 작용하는 힘의 정보가 들어가 있음. 
}

function draw() {
    선택 -> 생식 -> 돌연변이 -> 새로운 세대 생성 과정을 반복함

    2단계 : 선택 - 집간의  요소마다 적응도를 평가하고 교배풀을 생성함.
      적응도는 목표지점에 가깝게 접근한 로켓 개체일수록 높은 점수를 부여하는 것을 말함.

        생식 - N번 반복
        (1) 적응도가 높은 2개의 부모 로켓을 선택
        (2) 2개의 부모 DNA를 섞어 자식 로켓을 생성
        (3) 일정한 확률로 자신의 DNA에 돌연변이를 일으킴
        (4) 새로운 자식으로 구성된 새로운 집단을 만듬.
    
    3단계 : 부모 집단의 수명이 끝나면 새로운 집단으로 세대를 변경하고 앞의 단계로 돌아가 반복
}

class DNA { 로켓 개체 안의 들어 있는 유전자의 특성을 구현}

class Rocket { 각각의 로켓 개체의 기능을 구현}

class Population { 로켓 집단에서 선택, 생식같은 필요한 기능을 구현}

유전 알고리즘의 과정이 조금 이해가 되시나요? 한번에 이해하기 어려울 수 있어서 다음과 같이 그림으로 다시 자세히 설명드려볼께요.

"스마트 로켓1"

1단계 - 집단 생성 및 적응도 평가

  • 로켓 개체 3개로 첫 번째 로켓 집단을 만들어 보겠습니다.
  • 로켓의 수명은 6프레임으로 가정하고, 각 로켓의 유전자에는 각 프레임마다 작용하는 힘이 저장되어 있습니다. (이때 이전 세대에서 전달받은 유전자가 없으면 랜덤값으로 부여합니다.)
  • 각 로켓을 유전자에 정해진 대로 운동한 후 목표지점 사이의 거리를 기준으로 적응도를 평가합니다.

"스마트 로켓2"

2단계 - 선택 및 생식

  • 적응도 순위가 높을 수록 교배풀에 많이 배정되고 자연스럽게 적응도 순위가 높은 개체가 선택될 확률이 높아집니다.
  • 교배풀에서 부모 로켓 2개를 임의로 선택하여 교배를 합니다. 이때 가져오는 유전자는 임의의 중간점(midpoint)을 잡아 새로운 자식 로켓에게 전달합니다.
  • 그림의 예에서는 중간점을 2번째까지로 잡아 로켓 A 에서는 앞의 2개의 유전자를 가져오고 로켓 B에서는 앞에서 3번째부터 6번째까지 유전자를 가지고와 새로운 자식 유전자를 생성합니다.

"스마트 로켓3"

3단계 - 새로운 집단 생성

  • 집단의 개체수 만큼 교배풀에서 부모로켓 선택 및 생식 과정을 반복합니다.
  • 우리가 가정한 집단의 개체수는 3개이니까 3번 반복합니다. 이 때 돌연변이가 일정 확률로 발생해 부모 유전자하고는 전혀 다른 유전자를 가진 자식 로켓이 생겨나기도 합니다.
  • 새롭게 생긴 자식 로켓을 새로운 2세대 집단으로 묶어 앞의 1단계부터 다시 반복을 하면서 진화를 시키면 목표지점에 도달하는 최적화된 과정을 찾게 됩니다.

3. 기본적인 로켓의 운동 구현하기

p5.js의 구조는 크게 setup() 함수와 draw() 함수로 구성되어 있습니다.

setup() 함수는 프로그램이 시작되면 맨처음 한번 실행되는 함수입니다. 이 함수 안에는 캔버스 크기나 각종 초기값들을 설정합니다.

setup() 함수 안의 createCanvas(x,y) 함수는 화면에 그려질 캔버스의 크기를 픽셀 단위로 지정하는 명령입니다.

draw() 함수는 setup() 함수 이후에 무한 반복(1초에 30프레임~60프레임)되는 함수입니다. 이 함수 안에는 이미지나 에니메이션을 보여주는 기능을 넣습니다.

다음 코드와 같이 마우스를 클릭하면 목표점이 표시되게 그려봅시다.

let target

function setup() {
  createCanvas(640, 400);
  target = createVector(width/2, height/2);  // 목표지점 위치 초기화
}

function draw() {
  background(220);
  circle(target.x, target.y, 24);
}

function mousePressed() {    // 마우스를 클릭한 위치를 새로운 목표 지점으로 변경
  target.x = mouseX;
  target.y = mouseY;
}

Rocket 클래스를 구현해 봅시다.

class Rocket {
  constructor(x,y) {
    this.acc = createVector(0,0);  // 가속도
    this.vel = createVector(0,0);  // 속도
    this.pos = createVector(x,y);  // 위치
    this.r = 4;  // 로켓의 크기 반경
    this.maxspeed = 8;
    this.maxforce = 0.2;
  }

  // 가해진 힘을 가속도로 적용
  applyForce(force) {
    this.acc.add(force);
  }
  
  // 오일러 방법을 이용하여 가속도 -> 속도 -> 위치를 계산하여 운동
  update() {
    this.vel.add(this.acc);
    this.pos.add(this.vel);
    this.acc.mult(0);
  }
  
  // 로켓을 그리고 운동방향으로 회전시킴.
  show() {
    let theta = this.vel.heading();  // 로켓의 속도 방향으로 회전해야할 각도를 계산
    let r = this.r;
    stroke(0);
    
    push(); // 특정한 좌표평면 기준이나 스타일을 적용할 때 해당 부분만 적용되게 해주는 함. pop()은 원래 설정되로 복구되게 함.
    translate(this.pos.x, this.pos.y);  // 로켓 중심위치를 좌표평면 기중으로 임시로 잡음
    rotate(theta);  // 로켓을 속도방향으로 회전시킴

    // 로켓 모양 그리기
    fill(255);
    beginShape(TRIANGLES);
    vertex(r * 2, 0);
    vertex(-r * 2, -r);
    vertex(-r * 2, r);
    endShape(CLOSE);
    pop();    
  }

  seek() {
    let desired = p5.Vector.sub(target, this.pos); 
    desired.setMag(this.maxspeed);

    let steer = p5.Vector.sub(desired, this.vel);
    steer.limit(this.maxforce); 

    this.applyForce(steer);
  }
}

Rocket 클래스에서 rocket 객체를 생성하여 운동하기 위해 코드를 업데이트 합니다.

let target

function setup() {
  createCanvas(640, 400);
  target = createVector(width/2, height/2);  // 목표지점 위치 초기화
  rocket = new Rocket(width/2, height/2);
}

function draw() {
  background(220);
  circle(target.x, target.y, 24);
  
  rocket.seek();
  rocket.update();
  rocket.show();
}

function mousePressed() {    // 마우스를 클릭한 위치를 새로운 목표 지점으로 변경
  target.x = mouseX;
  target.y = mouseY;
}

목표 지점으로 운동하는 로켓

4. 스마트 로켓 코드 구현

1) 기본 함수 및 변수 구현

let lifetime;  // 로켓의 생존 수명을 프레임으로 나타냄
let lifeCounter;  // 현재 프레임을 나타내는 변수
let mutationRate;  // 돌연변이 확률
let population;    // 로켓 집단을 나타내는 배열 
let target;    // 목표 지점 위치
const maxforce = 0.1;  // 로켓에 작용하는 힘의 제한값

function setup() {
  createCanvas(600, 360);
  
  lifetime = height;  
  lifeCounter = 0;
  mutationRate = 0.01;
  population = new Population(mutationRate, 50);  // 50개의 로켓 개체를 가진 집단을 생성
  target = createVector(width/2, 24);  // 목표지점 위치 설정
}

function draw() {
  background(220);
  
  ellipse(target.x, target.y, 24, 24);  // 목표지점 원으로 표시

  if (lifeCounter < lifetime) {  // 로켓이 살아있다면 계속 운동하게 함.
    population.live();
    lifeCounter++;
  } else {    // 로켓집단이 수명을 다했다면 집단 내에서 적응도(fitness)를 계산하여, 가장 우수한 부모 로켓을 선택(selection)하여 생식(reproduction)하여 새로운 자식 개체 집단을 만든다.
    lifeCounter = 0;
    population.fitness();
    population.selection();
    population.reproduction();
  }
  
  // 세대와 수명을 표시
  text("세대 : " + population.generations, 10, 30);
  text("남은 수명 : " + (lifetime - lifeCounter), 10, 50);

}

function mousePressed() {    // 마우스를 클릭한 위치를 새로운 목표 지점으로 변경
  target.x = mouseX;
  target.y = mouseY;
}

2) DNA 클래스 구현

// 로켓 개체 안의 들어 있는 유전자의 특성을 구현. 유전자 안에는 로켓의 각 프레임마다 적용되는 힘의 크기와 방향이 저장되어 있음. 
class DNA {    
  // 첫 세대에서는 생존 시간만큼 랜덤한 방향과 크기의 힘이 작용하게 함.
  constructor(genes) {
    if (genes) {
      this.genes = genes;
    } else {  
      this.genes = [];
      for (let i = 0; i < lifetime; i++) {
        this.genes[i] = p5.Vector.random2D();
        this.genes[i].setMag(maxforce);
      }
    }   
  }
  
  // 상대방의 유전자와 나의 유전자를 교배하여 새로운 유전자를 생성함.
  crossover(partner) {  
    let child = [];
    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];
    }
    let newgenes = new DNA(child);
    return newgenes;    
  }
  
  // 일정한 돌연변이 확률로 자식의 유전자를 랜덤하게 생성함.
  mutate(m) {  
    for (let i = 0; i < this.genes.length; i++) {
      if (random(1) < m) {
        this.genes[i] = p5.Vector.random2D();
        this.genes[i].setMag(maxforce);
      }
    }    
  }
}

3) Rocket 클래스 수정

class Rocket {
  constructor(loc, dna) {
    this.acc = createVector();  // 가속도
    this.vel = createVector();  // 속도
    this.pos = loc.copy();  // 위치
    this.r = 4;  // 로켓의 크기 반경
    this.fitness = 0;  // 적응도
    this.dna = dna;    // 로켓의 유전자
    this.geneCounter = 0;  // 유전자 배열 순서
    
    this.hitTarget = false;  // 로켓이 목표지점에 도달했는지 여부
  }
  
  // 적응도 계산. 목표 지점에 가깝게 접근한 개체일수록 높은 점수를 부여함.
  calcFitness() {
    let d = dist(this.pos.x, this.pos.y, target.x, target.y);
    this.fitness = pow(1 / d, 2); // 적응도 함수는 가까울수록 제곱에 비례하게 커지도록 설정함.
  }
  
  // 로켓이 목표지점에 도달했는지 여부를 판단.
  checkTarget() {
    let d = dist(this.pos.x, this.pos.y, target.x, target.y);
    if (d < 12) {
      this.hitTarget = true;
    }
  }
  
  // 가해진 힘을 가속도로 적용
  applyForce(f) {
    this.acc.add(f);
  }
  
  // 오일러 방법을 이용하여 가속도 -> 속도 -> 위치를 계산하여 운동
  update() {
    this.vel.add(this.acc);
    this.pos.add(this.vel);
    this.acc.mult(0);
  }
  
  // 로켓의 모양을 그리고 운동방향으로 회전시킴.
  show() {
    let theta = this.vel.heading();  // 로켓의 속도 방향으로 회전해야할 각도를 계산
    let r = this.r;
    stroke(0);
    
    push(); // 특정한 좌표평면 기준이나 스타일을 적용할 때 해당 부분만 적용되게 해주는 함. pop()은 원래 설정되로 복구되게 함.
    translate(this.pos.x, this.pos.y);  // 로켓 중심위치를 좌표평면 기중으로 임시로 잡음
    rotate(theta);  // 로켓을 속도방향으로 회전시킴

    // 로켓 모양 그리기
    fill(255);
    beginShape(TRIANGLES);
    vertex(r * 2, 0);
    vertex(-r * 2, -r);
    vertex(-r * 2, r);
    endShape(CLOSE);

    pop();    
  }
  
  //로켓의 DNA 유전자 배열에 저장된 힘의 정보대로 로켓에 적용
  run() {
    this.checkTarget();
    if (!this.hitTarget) {
      this.applyForce(this.dna.genes[this.geneCounter]);
      this.geneCounter = (this.geneCounter + 1) % this.dna.genes.length;
      this.update();
    }
    this.show();
  }
}

4) Population 클래스 구현

// 로켓 집단에서 선택, 생식같은 필요한 기능을 구현
class Population {
  constructor(m, num) {
    this.mutationRate = m;  // 돌연변이 확률
    this.rockets = [];
    this.matingPool = [];  // 교배풀
    this.generations = 0;  // 세대
    
    // 50개의 로켓 개체로 되어 있는 집단 생성
    for (let i=0; i < num; i++) {
      let location = createVector(width /2, height + 20);
      this.rockets[i] = new Rocket(location, new DNA());
    }
  }
  
  // 생존시 활동
  live() {
    for (let i = 0; i < this.rockets.length; i++) {
      this.rockets[i].run();
    }
  }
  
  // 로켓 집단 모두의 적응도를 계산
  fitness() {
    for (let i = 0; i < this.rockets.length; i++) {
      this.rockets[i].calcFitness();
    }    
  }
  
  // 적응도가 높은 개체일수록 더 많은 개체를 교배 풀에 넣어 적응도 점수가 높은 개체가 선택될 가능성이 높게 구현함. 
  selection() {
    this.matingPool = [];
    var maxFitness = this.getMaxFitness();
    
    for (let i = 0; i < this.rockets.length; i++) {
      let fitnessNormal = map(this.rockets[i].fitness, 0, maxFitness, 0, 1);
      let n = floor(fitnessNormal * 100); // Arbitrary multiplier

      for (let j = 0; j < n; j++) {
        this.matingPool.push(this.rockets[i]);
      }
    }  
  }
  
  // 교배풀에서 부모 개체를 선택해 교배하여 새로운 자식 개체를 생성함. 
  reproduction() {
    for (let i = 0; i < this.rockets.length; i++) {
      let m = floor(random(this.matingPool.length));
      let d = floor(random(this.matingPool.length));
      let mom = this.matingPool[m];
      let dad = this.matingPool[d];
      let momgenes = mom.dna;
      let dadgenes = dad.dna;
      let child = momgenes.crossover(dadgenes);
      child.mutate(this.mutationRate);
      let location = createVector(width / 2, height + 20);
      this.rockets[i] = new Rocket(location, child);
    }
    this.generations++;    
  }
  
  // 집단 내에서 가장 높은 적응도의 값을 가져옴.
  getMaxFitness() {
    var record = 0;
    for (let i = 0; i < this.rockets.length; i++) {
      if (this.rockets[i].fitness > record) {
        record = this.rockets[i].fitness;
      }
    }
    return record;
  }  
}

5) 완성된 전체 코드

완성된 시뮬레이션 코드는 다음과 같습니다.

let lifetime;  // 로켓의 생존 수명을 프레임으로 나타냄
let lifeCounter;  // 현재 프레임을 나타내는 변수
let mutationRate;  // 돌연변이 확률
let population;    // 로켓 집단을 나타내는 배열 
let target;    // 목표 지점 위치
const maxforce = 0.1;  // 로켓에 작용하는 힘의 제한값

function setup() {
  createCanvas(600, 400);
  
  lifetime = height;  
  lifeCounter = 0;
  mutationRate = 0.01;
  population = new Population(mutationRate, 50);  // 50개의 로켓 개체를 가진 집단을 생성
  target = createVector(width/2, 24);  // 목표지점 위치 설정
}

function draw() {
  background(220);
  
  ellipse(target.x, target.y, 24, 24);  // 목표지점 원으로 표시

  if (lifeCounter < lifetime) {  // 로켓이 살아있다면 계속 운동하게 함.
    population.live();
    lifeCounter++;
  } else {    // 로켓집단이 수명을 다했다면 집단 내에서 적응도(fitness)를 계산하여, 가장 우수한 부모 로켓을 선택(selection)하여 생식(reproduction)하여 새로운 자식 개체 집단을 만든다.
    lifeCounter = 0;
    population.fitness();
    population.selection();
    population.reproduction();
  }
  
  // 세대와 수명을 표시
  text("세대 : " + population.generations, 10, 30);
  text("남은 수명 : " + (lifetime - lifeCounter), 10, 50);

}

function mousePressed() {    // 마우스를 클릭한 위치를 새로운 목표 지점으로 변경
  target.x = mouseX;
  target.y = mouseY;
}

// 로켓 개체 안의 들어 있는 유전자의 특성을 구현. 유전자 안에는 로켓의 각 프레임마다 적용되는 힘의 크기와 방향이 저장되어 있음. 
class DNA {    
  // 첫 세대에서는 생존 시간만큼 랜덤한 방향과 크기의 힘이 작용하게 함.
  constructor(genes) {
    if (genes) {
      this.genes = genes;
    } else {  
      this.genes = [];
      for (let i = 0; i < lifetime; i++) {
        this.genes[i] = p5.Vector.random2D();
        this.genes[i].setMag(maxforce);
      }
    }   
  }
  
  // 상대방의 유전자와 나의 유전자를 교배하여 새로운 유전자를 생성함.
  crossover(partner) {  
    let child = [];
    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];
    }
    let newgenes = new DNA(child);
    return newgenes;    
  }
  
  // 일정한 돌연변이 확률로 자식의 유전자를 랜덤하게 생성함.
  mutate(m) {  
    for (let i = 0; i < this.genes.length; i++) {
      if (random(1) < m) {
        this.genes[i] = p5.Vector.random2D();
        this.genes[i].setMag(maxforce);
      }
    }    
  }
}

// 각각의 로켓 개체의 기능을 구현
class Rocket {
  constructor(loc, dna) {
    this.acc = createVector();  // 가속도
    this.vel = createVector();  // 속도
    this.pos = loc.copy();  // 위치
    this.r = 4;  // 로켓의 크기 반경
    this.fitness = 0;  // 적응도
    this.dna = dna;    // 로켓의 유전자
    this.geneCounter = 0;  // 유전자 배열 순서
    
    this.hitTarget = false;  // 로켓이 목표지점에 도달했는지 여부
  }
  
  // 적응도 계산. 목표 지점에 가깝게 접근한 개체일수록 높은 점수를 부여함.
  calcFitness() {
    let d = dist(this.pos.x, this.pos.y, target.x, target.y);
    this.fitness = pow(1 / d, 2); // 적응도 함수는 가까울수록 제곱에 비례하게 커지도록 설정함.
  }
  
  // 로켓이 목표지점에 도달했는지 여부를 판단.
  checkTarget() {
    let d = dist(this.pos.x, this.pos.y, target.x, target.y);
    if (d < 12) {
      this.hitTarget = true;
    }
  }
  
  // 가해진 힘을 가속도로 적용
  applyForce(f) {
    this.acc.add(f);
  }
  
  // 오일러 방법을 이용하여 가속도 -> 속도 -> 위치를 계산하여 운동
  update() {
    this.vel.add(this.acc);
    this.pos.add(this.vel);
    this.acc.mult(0);
  }
  
  // 로켓의 모양을 그리고 운동방향으로 회전시킴.
  show() {
    let theta = this.vel.heading();  // 로켓의 속도 방향으로 회전해야할 각도를 계산
    let r = this.r;
    stroke(0);
    
    push(); // 특정한 좌표평면 기준이나 스타일을 적용할 때 해당 부분만 적용되게 해주는 함. pop()은 원래 설정되로 복구되게 함.
    translate(this.pos.x, this.pos.y);  // 로켓 중심위치를 좌표평면 기중으로 임시로 잡음
    rotate(theta);  // 로켓을 속도방향으로 회전시킴

    // 로켓 모양 그리기
    fill(255);
    beginShape(TRIANGLES);
    vertex(r * 2, 0);
    vertex(-r * 2, -r);
    vertex(-r * 2, r);
    endShape(CLOSE);

    pop();    
  }
  
  //로켓의 DNA 유전자 배열에 저장된 힘의 정보대로 로켓에 적용
  run() {
    this.checkTarget();
    if (!this.hitTarget) {
      this.applyForce(this.dna.genes[this.geneCounter]);
      this.geneCounter = (this.geneCounter + 1) % this.dna.genes.length;
      this.update();
    }
    this.show();
  }
}

// 로켓 집단에서 선택, 생식같은 필요한 기능을 구현
class Population {
  constructor(m, num) {
    this.mutationRate = m;  // 돌연변이 확률
    this.rockets = [];
    this.matingPool = [];  // 교배풀
    this.generations = 0;  // 세대
    
    // 50개의 로켓 개체로 되어 있는 집단 생성
    for (let i=0; i < num; i++) {
      let location = createVector(width /2, height + 20);
      this.rockets[i] = new Rocket(location, new DNA());
    }
  }
  
  // 생존시 활동
  live() {
    for (let i = 0; i < this.rockets.length; i++) {
      this.rockets[i].run();
    }
  }
  
  // 로켓 집단 모두의 적응도를 계산
  fitness() {
    for (let i = 0; i < this.rockets.length; i++) {
      this.rockets[i].calcFitness();
    }    
  }
  
  // 적응도가 높은 개체일수록 더 많은 개체를 교배 풀에 넣어 적응도 점수가 높은 개체가 선택될 가능성이 높게 구현함. 
  selection() {
    this.matingPool = [];
    var maxFitness = this.getMaxFitness();
    
    for (let i = 0; i < this.rockets.length; i++) {
      let fitnessNormal = map(this.rockets[i].fitness, 0, maxFitness, 0, 1);
      let n = floor(fitnessNormal * 100); // Arbitrary multiplier

      for (let j = 0; j < n; j++) {
        this.matingPool.push(this.rockets[i]);
      }
    }  
  }
  
  // 교배풀에서 부모 개체를 선택해 교배하여 새로운 자식 개체를 생성함. 
  reproduction() {
    for (let i = 0; i < this.rockets.length; i++) {
      let m = floor(random(this.matingPool.length));
      let d = floor(random(this.matingPool.length));
      let mom = this.matingPool[m];
      let dad = this.matingPool[d];
      let momgenes = mom.dna;
      let dadgenes = dad.dna;
      let child = momgenes.crossover(dadgenes);
      child.mutate(this.mutationRate);
      let location = createVector(width / 2, height + 20);
      this.rockets[i] = new Rocket(location, child);
    }
    this.generations++;    
  }
  
  // 집단 내에서 가장 높은 적응도의 값을 가져옴.
  getMaxFitness() {
    var record = 0;
    for (let i = 0; i < this.rockets.length; i++) {
      if (this.rockets[i].fitness > record) {
        record = this.rockets[i].fitness;
      }
    }
    return record;
  }  
}

스마트 로켓 웹에디터 코드 실행하기

업데이트: