Genetic Algorithm Hello World
The Hello World of Genetic Algorithms is a simple phrase solver. While useless in terms of utility, it is probably the simplest way to understand this class of algorithm.
How it Works
Entities in this system start out as randomly generated strings (See genetic.seed = function()
below). At each iteration a portion of entites are selected for mutation. Another portition is selected for crossover (aka. mating). From there you let the system run and watch it converge on a solution, there isn’t much more to it than that!
Mutation
When an entity (string) is selected for mutation, a character in a random location is either increased or decreased by one lexicographically. See genetic.mutate = function(entity)
below for code.
Crossover
In Genetic.js crossover always results in two children from from two parents. In this simulation we implement two-point crossover. See genetic.crossover = function(mother, father)
below.
Fitness
The phrase solver demo scores entities (strings) by how close they are to the goal. The formula is simple, add one for each character that matches. Also add fractional points if it is close. The result is a floating point number, the larger the better. See genetic.fitness = function(entity)
for implementation.
Phrase Solver Demo
Refer to /system/genetic-js.html for all the configuration/options that are available. The code can be cloned from https://github.com/subprotocol/genetic-js.
Generation | Fitness | Solution |
---|---|---|
Press 'Solve' Button to begin. |
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
var genetic = Genetic.create();
genetic.optimize = Genetic.Optimize.Maximize;
genetic.select1 = Genetic.Select1.Tournament2;
genetic.select2 = Genetic.Select2.Tournament2;
genetic.seed = function() {
function randomString(len) {
var text = "";
var charset = "abcdefghijklmnopqrstuvwxyz0123456789";
for(var i=0;i<len;i++)
text += charset.charAt(Math.floor(Math.random() * charset.length));
return text;
}
// create random strings that are equal in length to solution
return randomString(this.userData["solution"].length);
};
genetic.mutate = function(entity) {
function replaceAt(str, index, character) {
return str.substr(0, index) + character + str.substr(index+character.length);
}
// chromosomal drift
var i = Math.floor(Math.random()*entity.length)
return replaceAt(entity, i, String.fromCharCode(entity.charCodeAt(i) + (Math.floor(Math.random()*2) ? 1 : -1)));
};
genetic.crossover = function(mother, father) {
// two-point crossover
var len = mother.length;
var ca = Math.floor(Math.random()*len);
var cb = Math.floor(Math.random()*len);
if (ca > cb) {
var tmp = cb;
cb = ca;
ca = tmp;
}
var son = father.substr(0,ca) + mother.substr(ca, cb-ca) + father.substr(cb);
var daughter = mother.substr(0,ca) + father.substr(ca, cb-ca) + mother.substr(cb);
return [son, daughter];
};
genetic.fitness = function(entity) {
var fitness = 0;
var i;
for (i=0;i<entity.length;++i) {
// increase fitness for each character that matches
if (entity[i] == this.userData["solution"][i])
fitness += 1;
// award fractions of a point as we get warmer
fitness += (127-Math.abs(entity.charCodeAt(i) - this.userData["solution"].charCodeAt(i)))/50;
}
return fitness;
};
genetic.generation = function(pop, generation, stats) {
// stop running once we've reached the solution
return pop[0].entity != this.userData["solution"];
};
genetic.notification = function(pop, generation, stats, isFinished) {
function lerp(a, b, p) {
return a + (b-a)*p;
}
var value = pop[0].entity;
this.last = this.last||value;
if (pop != 0 && value == this.last)
return;
var solution = [];
var i;
for (i=0;i<value.length;++i) {
var diff = value.charCodeAt(i) - this.last.charCodeAt(i);
var style = "background: transparent;";
if (diff > 0) {
style = "background: rgb(0,200,50); color: #fff;";
} else if (diff < 0) {
style = "background: rgb(0,100,50); color: #fff;";
}
solution.push("<span style=\"" + style + "\">" + value[i] + "</span>");
}
var buf = "";
buf += "<tr>";
buf += "<td>" + generation + "</td>";
buf += "<td>" + pop[0].fitness.toPrecision(5) + "</td>";
buf += "<td>" + solution.join("") + "</td>";
buf += "</tr>";
$("#results tbody").prepend(buf);
this.last = value;
};
$(document).ready(function () {
$("#solve").click(function () {
$("#results tbody").html("");
var config = {
"iterations": 4000
, "size": 250
, "crossover": 0.3
, "mutation": 0.3
, "skip": 20
};
var userData = {
"solution": $("#quote").val()
};
genetic.evolve(config, userData);
});
});