Created
October 17, 2015 23:48
-
-
Save NeatMonster/40c6ddf26cf913b43cba to your computer and use it in GitHub Desktop.
Another demonstration of Genetic Algorithms (GAs)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package fr.neatmonster.ga; | |
import java.awt.Color; | |
import java.awt.EventQueue; | |
import java.awt.Graphics; | |
import java.awt.GridLayout; | |
import java.awt.Polygon; | |
import java.awt.image.BufferedImage; | |
import java.io.File; | |
import java.io.IOException; | |
import java.nio.file.Files; | |
import java.nio.file.Paths; | |
import java.nio.file.StandardOpenOption; | |
import java.util.Arrays; | |
import java.util.Comparator; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Random; | |
import javax.imageio.ImageIO; | |
import javax.swing.ImageIcon; | |
import javax.swing.JFrame; | |
import javax.swing.JLabel; | |
public class Main extends JFrame { | |
private static final Random rnd = new Random(); | |
// Genetics | |
private static int POPULATION_SIZE = 49; | |
private static float SELECTION_CUTOFF = 0.15f; | |
private static float MUTATION_CHANCE = 0.01f; | |
private static float MUTATION_AMOUNT = 0.10f; | |
private static boolean RANDOM_INHERITANCE = false; | |
private static boolean DIFF_SQUARED = true; | |
private static boolean FITTEST_SURVIVE = false; | |
// Graphics | |
private static int POLYGONS = 125; | |
private static int VERTICES = 3; | |
private static boolean FILL_POLYGONS = true; | |
// Internal | |
private static int GENE_SIZE; | |
private static String FILENAME = null; | |
private static int WIDTH; | |
private static int HEIGHT; | |
private static int STOP = -1; | |
// Logging | |
private static String LOG_FILE = null; | |
private static boolean LOG_IMAGES = false; | |
private static boolean LOG_FITNESS = false; | |
public static void main(final String[] argsArray) { | |
final List<String> args = Arrays.asList(argsArray); | |
final Iterator<String> it = args.iterator(); | |
String s; | |
while (it.hasNext()) { | |
switch ((s = it.next()).toLowerCase()) { | |
case "--population-size": | |
POPULATION_SIZE = Integer.parseInt(it.next()); | |
break; | |
case "--selection-cutoff": | |
SELECTION_CUTOFF = Float.parseFloat(it.next()); | |
break; | |
case "--mutation-chance": | |
MUTATION_CHANCE = Float.parseFloat(it.next()); | |
break; | |
case "--mutation-amount": | |
MUTATION_AMOUNT = Float.parseFloat(it.next()); | |
break; | |
case "--random-inheritance": | |
RANDOM_INHERITANCE = Boolean.parseBoolean(it.next()); | |
break; | |
case "--diff-squared": | |
DIFF_SQUARED = Boolean.parseBoolean(it.next()); | |
break; | |
case "--fittest-survive": | |
FITTEST_SURVIVE = Boolean.parseBoolean(it.next()); | |
break; | |
case "--polygons": | |
POLYGONS = Integer.parseInt(it.next()); | |
break; | |
case "--vertices": | |
VERTICES = Integer.parseInt(it.next()); | |
break; | |
case "--fill-polygons": | |
FILL_POLYGONS = Boolean.parseBoolean(it.next()); | |
break; | |
case "--log-images": | |
LOG_IMAGES = Boolean.parseBoolean(it.next()); | |
break; | |
case "--log-fitness": | |
LOG_FITNESS = Boolean.parseBoolean(it.next()); | |
break; | |
case "--log-file": | |
LOG_FILE = it.next(); | |
break; | |
case "--stop": | |
STOP = Integer.parseInt(it.next()); | |
break; | |
case "--help": | |
System.out.println("Syntax: [options] <filename>"); | |
System.out.println("Available Options:"); | |
System.out.println(" --population-size (int)"); | |
System.out.println(" --selection-cutoff (float)"); | |
System.out.println(" --mutation-chance (float)"); | |
System.out.println(" --mutation-amount (float)"); | |
System.out.println(" --random-inheritance (boolean)"); | |
System.out.println(" --diff-squared (boolean)"); | |
System.out.println(" --fittest-survive (boolean)"); | |
System.out.println(""); | |
System.out.println(" --polygons (int)"); | |
System.out.println(" --vertices (int)"); | |
System.out.println(" --fill-polygons (boolean)"); | |
System.out.println(""); | |
System.out.println(" --log-images (boolean)"); | |
System.out.println(" --log-fitness (boolean)"); | |
System.out.println(" --log-file (string)"); | |
System.out.println(""); | |
System.out.println(" --stop (int)"); | |
System.exit(0); | |
break; | |
default: | |
FILENAME = s; | |
break; | |
} | |
} | |
if (FILENAME == null) { | |
System.out.println("No filename specified!"); | |
System.exit(0); | |
} else if (LOG_FILE == null) | |
LOG_FILE = FILENAME; | |
GENE_SIZE = 4 + (VERTICES > 1 ? VERTICES * 2 : 3); | |
System.out.println("Filename: " + FILENAME); | |
System.out.println("Options:"); | |
System.out.println(" Population Size: " + POPULATION_SIZE); | |
System.out.println(" Selection Cutoff: " + SELECTION_CUTOFF); | |
System.out.println(" Mutation Chance: " + MUTATION_CHANCE); | |
System.out.println(" Mutation Amount: " + MUTATION_AMOUNT); | |
System.out.println(" Random Inheritance: " + RANDOM_INHERITANCE); | |
System.out.println(" Diff Squared: " + DIFF_SQUARED); | |
System.out.println(" Fittest Survice: " + FITTEST_SURVIVE); | |
System.out.println(""); | |
System.out.println(" Polygons: " + POLYGONS); | |
System.out.println(" Vertices: " + VERTICES); | |
System.out.println(" Fill Polygons: " + FILL_POLYGONS); | |
System.out.println(""); | |
System.out.println(" Log Images: " + LOG_IMAGES); | |
System.out.println(" Log Fitness: " + LOG_FITNESS); | |
System.out.println(" Log File: " + LOG_FILE); | |
System.out.println(""); | |
System.out.println(" Stop: " + (STOP > 0 ? STOP : "never")); | |
EventQueue.invokeLater(new Runnable() { | |
@Override | |
public void run() { | |
try { | |
new Main(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
}); | |
} | |
public class Individual { | |
private final float[][] genome; | |
private BufferedImage image = null; | |
private float fitness = -1f; | |
private int individualId; | |
public Individual() { | |
genome = new float[POLYGONS][GENE_SIZE]; | |
for (int i = 0; i < POLYGONS; ++i) { | |
genome[i] = new float[GENE_SIZE]; | |
for (int j = 0; j < GENE_SIZE; ++j) | |
genome[i][j] = rnd.nextFloat(); | |
} | |
individualId = nextIndividualId++; | |
} | |
public Individual(final Individual mother, final Individual father) { | |
genome = new float[POLYGONS][GENE_SIZE]; | |
final int inheritSplit = rnd.nextInt(POLYGONS); | |
for (int i = 0; i < POLYGONS; ++i) { | |
float[] inheritedGene; | |
if (RANDOM_INHERITANCE) | |
inheritedGene = i < inheritSplit ? mother.genome[i] | |
: father.genome[i]; | |
else | |
inheritedGene = rnd.nextBoolean() ? mother.genome[i] | |
: father.genome[i]; | |
System.arraycopy(inheritedGene, 0, genome[i], 0, GENE_SIZE); | |
for (int j = 0; j < GENE_SIZE; ++j) | |
if (rnd.nextFloat() < MUTATION_CHANCE) { | |
genome[i][j] += rnd.nextFloat() * 2 * MUTATION_AMOUNT | |
- MUTATION_AMOUNT; | |
if (genome[i][j] < 0f) | |
genome[i][j] = 0f; | |
if (genome[i][j] > 1f) | |
genome[i][j] = 1f; | |
} | |
} | |
} | |
public BufferedImage getRender() { | |
if (image != null) | |
return image; | |
image = new BufferedImage(WIDTH, HEIGHT, | |
BufferedImage.TYPE_INT_ARGB); | |
final Graphics g = image.getGraphics(); | |
for (final float[] gene : genome) { | |
g.setColor(new Color(gene[0], gene[1], gene[2], gene[3])); | |
if (VERTICES == 1) { | |
final int x = (int) (gene[4] * WIDTH); | |
final int y = (int) (gene[5] * HEIGHT); | |
final int r = (int) (gene[6] * WIDTH); | |
if (FILL_POLYGONS) | |
g.fillOval(x - r / 2, y - r / 2, r, r); | |
else | |
g.drawOval(x - r / 2, y - r / 2, r, r); | |
} else if (VERTICES == 2) { | |
final int x = (int) (gene[4] * WIDTH); | |
final int y = (int) (gene[5] * HEIGHT); | |
final int w = (int) (gene[6] * WIDTH); | |
final int h = (int) (gene[7] * HEIGHT); | |
if (FILL_POLYGONS) | |
g.fillOval(x - w / 2, y - h / 2, w, h); | |
else | |
g.drawOval(x - w / 2, y - h / 2, w, h); | |
} else { | |
final int[] xs = new int[VERTICES]; | |
final int[] ys = new int[VERTICES]; | |
for (int i = 0; i < VERTICES; ++i) { | |
xs[i] = (int) (gene[4 + 2 * i] * 255); | |
ys[i] = (int) (gene[5 + 2 * i] * 255); | |
} | |
if (FILL_POLYGONS) | |
g.fillPolygon(new Polygon(xs, ys, VERTICES)); | |
else | |
g.drawPolygon(new Polygon(xs, ys, VERTICES)); | |
} | |
} | |
g.dispose(); | |
return image; | |
} | |
public float getFitness() { | |
if (fitness != -1f) | |
return fitness; | |
getRender(); | |
fitness = 0; | |
for (int y = 0; y < HEIGHT; ++y) | |
for (int x = 0; x < WIDTH; ++x) { | |
final int imgRGB = Main.this.image.getRGB(x, y); | |
final int imgA = (imgRGB >> 24) & 255; | |
final int imgR = (imgRGB >> 16) & 255; | |
final int imgG = (imgRGB >> 8) & 255; | |
final int imgB = imgRGB & 255; | |
final int thisRGB = image.getRGB(x, y); | |
final int thisA = (thisRGB >> 24) & 255; | |
final int thisR = (thisRGB >> 16) & 255; | |
final int thisG = (thisRGB >> 8) & 255; | |
final int thisB = thisRGB & 255; | |
if (DIFF_SQUARED) { | |
final int diffA = imgA - thisA; | |
fitness += diffA * diffA; | |
final int diffR = imgR - thisR; | |
fitness += diffR * diffR; | |
final int diffG = imgG - thisG; | |
fitness += diffG * diffG; | |
final int diffB = imgB - thisB; | |
fitness += diffB * diffB; | |
} else { | |
fitness += Math.abs(imgA - thisA); | |
fitness += Math.abs(imgR - thisR); | |
fitness += Math.abs(imgG - thisG); | |
fitness += Math.abs(imgB - thisB); | |
} | |
} | |
if (DIFF_SQUARED) | |
fitness = 1f - fitness / (WIDTH * HEIGHT * 4L * 256 * 256); | |
else | |
fitness = 1f - fitness / (WIDTH * HEIGHT * 4L * 256); | |
return fitness; | |
} | |
public int getIndividualId() { | |
return individualId; | |
} | |
} | |
private final Individual[] population; | |
private int generation = 0; | |
private BufferedImage image; | |
private final BufferedImage[] images; | |
private int nextIndividualId = 1; | |
public Main() throws IOException { | |
try { | |
image = ImageIO.read(new File(FILENAME)); | |
} catch (final IOException e) { | |
System.out.println("Couldn't read '" + FILENAME + "'"); | |
System.out.println(e.getMessage()); | |
System.exit(0); | |
} | |
WIDTH = image.getWidth(); | |
HEIGHT = image.getHeight(); | |
population = new Individual[POPULATION_SIZE]; | |
for (int i = 0; i < POPULATION_SIZE; ++i) | |
population[i] = new Individual(); | |
setResizable(false); | |
setTitle("Genetic Algorithm"); | |
setLocationRelativeTo(null); | |
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
final int rows = (int) Math.sqrt(POPULATION_SIZE); | |
final int cols = POPULATION_SIZE / rows; | |
setLayout(new GridLayout(rows, cols)); | |
images = new BufferedImage[POLYGONS]; | |
for (int i = 0; i < POPULATION_SIZE; ++i) { | |
images[i] = new BufferedImage(WIDTH, HEIGHT, | |
BufferedImage.TYPE_INT_ARGB); | |
add(new JLabel(new ImageIcon(images[i]))); | |
} | |
pack(); | |
setVisible(true); | |
new Thread(new Runnable() { | |
@Override | |
public void run() { | |
while (STOP < 0 || generation < STOP) { | |
tick(); | |
render(); | |
} | |
} | |
}).start(); | |
} | |
public void tick() { | |
if (generation == 0) | |
sort(); | |
if (population.length > 1) { | |
final int size = population.length; | |
final int selectCount = (int) Math.floor(size * SELECTION_CUTOFF); | |
final int randomCount = FITTEST_SURVIVE ? size - 1 : size; | |
final Individual[] children = new Individual[randomCount]; | |
for (int i = 0; i < randomCount; ++i) | |
children[i] = new Individual(population[i % selectCount], | |
population[rnd.nextInt(size)]); | |
System.arraycopy(children, 0, population, FITTEST_SURVIVE ? 1 : 0, | |
randomCount); | |
} else { | |
final Individual parent = population[0]; | |
final Individual child = new Individual(parent, parent); | |
if (child.getFitness() > parent.getFitness()) | |
population[0] = child; | |
} | |
sort(); | |
++generation; | |
} | |
public void sort() { | |
Arrays.sort(population, new Comparator<Individual>() { | |
@Override | |
public int compare(final Individual i1, final Individual i2) { | |
final float cmp = i1.getFitness() - i2.getFitness(); | |
if (cmp < 0f) | |
return 1; | |
if (cmp > 0f) | |
return -1; | |
return i1.getIndividualId() - i2.getIndividualId(); | |
} | |
}); | |
} | |
private float best = 0f; | |
public void render() { | |
setTitle("Genetic Algorithm [" + generation + "]"); | |
for (int i = 0; i < POPULATION_SIZE; ++i) { | |
images[i].setData(population[i].getRender().getRaster()); | |
final Graphics g = images[i].getGraphics(); | |
g.drawString(Float.toString(population[i].getFitness()), 0, 10); | |
} | |
if ((STOP > 0 && generation == STOP) || population[0].getFitness() > best) { | |
best = population[0].getFitness(); | |
if (LOG_IMAGES) | |
try { | |
if (!new File(LOG_FILE).exists()) | |
new File(LOG_FILE).mkdir(); | |
ImageIO.write(population[0].getRender(), "PNG", | |
new File(String.format(LOG_FILE + "/image%07d.png", | |
generation))); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
if (LOG_FITNESS) | |
try { | |
if (!new File(LOG_FILE + ".csv").exists()) | |
new File(LOG_FILE + ".csv").createNewFile(); | |
Files.write(Paths.get(LOG_FILE + ".csv"), | |
(generation + "," + best + "\n").getBytes(), | |
StandardOpenOption.APPEND); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
revalidate(); | |
repaint(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment