Show HN: A Genetic Algorithm I wrote in JavaScript to evolve "Hello World"
puremango.co.uk
puremango.co.uk
The neat part about it was that you could put in your own problem and have it solve it. I took a minute and slapped together the Hello World! version of this problem. You can copy it into the text box and play with the parameters (and resulting graph output. The output isn't a pretty as the link and it runs slower, but still neat.
function salesman() { this.target = "hello world!";
this.points = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '!', ' ' ];
this.fitness = function(chromosome) {
// higher fitness is better
var f = 0; // start at 0; the best fitness
for(var i=0, c=this.target.length; i<c ; i++) {
f -= Math.abs(this.target.charCodeAt(i) - this.points[chromosome[i]].charCodeAt(0));
}
return Math.abs(f);
};
// the size of values that should be passed to fitness
this.numberOfArgs = function() { return this.target.length; };
// the max value needed for the arguments
this.maxArg = function() { return this.points.length; };
// convert the current chromosome value which can have a maxValue
// into something fitness can use.
this.getArg = function(value, maxValue) {
return Math.round(value * (this.points.length - 1) / maxValue);
};
// Paint the solution onto bestimage
this.paint = function(values) {
var canvas = document.getElementById('bestimage');
if (canvas.getContext){
var w = canvas.width;
var h = canvas.height;
var canvasContext = canvas.getContext('2d');
canvasContext.clearRect(0, 0, w, h);
canvasContext.font = "italic 200 12px/2 Unknown Font, sans-serif";
canvasContext.strokeStyle = "blue";
for (var i = 0; i < values.length; ++i){
canvasContext.strokeText(this.points[values[i]], i*10, 10);
}
}
}
}
salesman;I got an error when playing with the values though; pop 400, mutation 0.2, crossover 0.8: "When executing function:this.people[peopleSize - i] is undefined"
So out of all genomes, 20% (.2) goes to crossover, 80% (.8) goes to crossover and the rest live. The two numbers have to be less than 1.0 for some to remain alive from one round to the next. This gives the option to also specify how many survive from one round to the next v.s (correct me if this is wrong) a random amount each time in your example.
For what it is worth you graph looks much nicer :) Back when I first made mine the canvas tag wasn't around and js was much slower. Really should remove the 'you might get an image!' text before the graph.
> This gives the option to also specify how many survive from one round to the next v.s (correct me if this is wrong) a random amount each time in your example.
in mine the population is constant. A set of two parents always have exactly two children. Breeding occurs until there are exactly as many children as there are parents, then the children mutate, and murder their parents. Genetics is fun!
Yeah I'm really happy with the graph, thanks! Canvas API is a bit clunky, but powerful (especially now we're allowed to write text into it!)
edit: what selection method are you using?
Link to the js: http://icefox.github.com/javascript_genetic_algorithm/javasc...
in bash...
to generate C code...
using temp files to pass data around.
They didn't get far, but did actually manage to get something to print after a few hours, though it never seemed to get closer to "hello world" after that point. We figured it was just that the GA was a bit overly proud of its accomplishment, and wasn't interested in improvement. This is with random strings inside a main() wrapper with stdio, and nothing else. I forget what their fitness function was, but I'm fairly certain it wasn't too simple, and I think that shot them in the foot near the end.
I still boggle at the attempt; I built a maze solver in Ruby that handled changing mazes by ant-trail-like solving (I forget the term...) that only took me a handful of hours. Then I spent another 10 to make modifying the maze as simple as possible, and I made a dozen or so wildly different mazes with moving walls, transporters, etc while doing my presentation to demonstrate that it worked. I think that may have won me more points than the functioning code; lots of people's barely worked, and nobody else had anything they could run in reasonable times during a presentation.
I'm impressed it managed to get something which ran at all! Usually with GP you at least start with a set of valid operations and keywords (eg genes=['function','printf','return','...etc']) and mix those together randomly.
[EDIT: s/GE/GA/ ... not sure why I got that wrong...]
> I love the quotes interspersed
You've no idea how hard it is to find good quotes about mutation! (I did try to find a good TMNT quote at first...)
> I was hoping for a GA that evolved a /program/
That would be very cool. I think my next project is going to be a neural network script. But I may revisit genetic algorithms again too, and at that point I'll probably look into genetic programming.
One day I hope to apply machine learning to the art of predicting real-life events. This is one small step towards that project, and I'd love to hear your feedback! - But it's 1AM now so I'll be offline for the next few hours. Here's hoping I wake up to a million upvotes! :)
* http://www.jurisgalang.com/2010/10/13/breeding-images/
* http://www.jurisgalang.com/2009/02/01/breeding-strings/The "There Is Such A Thing As The There Ain't No Such Thing As A Free Lunch" Theorem.
Maybe you could generate a bunch of different versions of an ML algo (say, ANN with different numbers of hidden nodes and starting weights), and use GA to evolve the best one. Doesn't seem like the best technique, though.
GAs are general optimisation techniques, but 'evolutionary computing' is very closely linked to AI and ML.
I did something similar in javascript --> https://github.com/JohnIdol/WasDarwinWrong ... and C# --> https://github.com/JohnIdol/typingmonkey
I tried 3 different types of fitness functions: Per-Character error summation, Levenshtein Distance and Hamming Distance.
I found that Hamming Distance produced the quickest convergences.
The code for my project is "fully OOP" so you can switch implementations for crossover, mutation and selection operators and also for the fitness functions.
Go ahead, fork me ;-)
Well, there are no local optima but plenty of global optima other than the correct answer, I think: "Hello, World!" and "!dlroW ,olleH" have the same fitness for example. So no, the gradient won't necessarily lead you to the solution.
Now, suppose we defined the fitness function instead as the number of correct characters plus the number of characters in the right place, then we'd have local optima and a better chance of getting to the optimum.
The gradient here is continuous and smooth. Problems where GA is an appropriate solution have fitness landscapes that are much more pathological.
The other answers that you are thinking on have different fitness (the comparison is target[i] ?= guess[i]).
In any case, I like this examples because they currently show the process that the GA has to converge in a way easy to see/understand. Of course, in real life it is more used when there is no information of the function to minimize (optimize), or the function is not convex in our search domain, which can lead to getting stuck in a local minimum using other search methods.
I have always felt that the fancy name increased the interest on the topic.
> Return the sum of the differences between the chromosome and the target string.
but I mean something more like:
> Return the sum of the character-wise differences between the chromosome and the target string.