├── CNAME ├── README.md ├── app.js ├── index.html ├── package.json └── styles.css /CNAME: -------------------------------------------------------------------------------- 1 | gh.nk1.dev -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | http://nk1tz.com/explore-genetic-algorithm/ 2 | 3 | ## Genetic Algorithm Explorer 4 | Explore an genetic algorithm (evolutionary search) by playing with the parameters during a solution landscape search. 5 | 6 | ### How it works 7 | Type your name (or anything) in the input box, and click 'Evolve Me'. Change parameters on the fly by hitting enter and observe the effects on the algorithm. The top 10 individuals of each generation are displayed along with their respective score (see fitness function). 8 | 9 | #### Fitness function 10 | The score of an individual is found by comparing char-by-char the ASCII code values of a string against the target string. 11 | 12 | #### Mutation Rate 13 | Chance of mutating any given char during mating. 14 | 15 | #### Parents per Child 16 | How many individuals' contribute part of their genome during a single mating event. 17 | 18 | #### Population Size 19 | How many individuals are generated at every generational event. 20 | 21 | #### Selection Bias 22 | How heavily weighted is the selection process towards the best individuals. 23 | 24 | #### Elites 25 | How many of the top individuals are cloned into the next generation. 26 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var target = ""; 2 | var lowestScore = Infinity; 3 | var topPerformerGenome = []; 4 | var mutationRate = 0.009; 5 | var sexNumber = 4; 6 | var sBias = 0.8; 7 | var popSize = 30; 8 | var elites = 0; 9 | 10 | // Define character space 11 | const CHAR_SPACE_MIN = 32; 12 | const CHAR_SPACE_MAX = 126; 13 | 14 | // Define nodes 15 | var topPerformerNode1 = document.getElementById('top-performer1'); 16 | var topPerformerNode2 = document.getElementById('top-performer2'); 17 | var topPerformerNode3 = document.getElementById('top-performer3'); 18 | var topPerformerNode4 = document.getElementById('top-performer4'); 19 | var topPerformerNode5 = document.getElementById('top-performer5'); 20 | var topPerformerNode6 = document.getElementById('top-performer6'); 21 | var topPerformerNode7 = document.getElementById('top-performer7'); 22 | var topPerformerNode8 = document.getElementById('top-performer8'); 23 | var topPerformerNode9 = document.getElementById('top-performer9'); 24 | var topPerformerNode10 = document.getElementById('top-performer10'); 25 | 26 | var scoreNode1 = document.getElementById('score1'); 27 | var scoreNode2 = document.getElementById('score2'); 28 | var scoreNode3 = document.getElementById('score3'); 29 | var scoreNode4 = document.getElementById('score4'); 30 | var scoreNode5 = document.getElementById('score5'); 31 | var scoreNode6 = document.getElementById('score6'); 32 | var scoreNode7 = document.getElementById('score7'); 33 | var scoreNode8 = document.getElementById('score8'); 34 | var scoreNode9 = document.getElementById('score9'); 35 | var scoreNode10 = document.getElementById('score10'); 36 | 37 | var inputNode = document.getElementById('textBox'); 38 | var evolveStartNode = document.getElementById('start-evolve'); 39 | var mutationRateNode = document.getElementById('mutation-rate'); 40 | var sexNumberNode = document.getElementById('sex-number'); 41 | var popSizeNode = document.getElementById('pop-size'); 42 | var selectionBiasNode = document.getElementById('selection-bias'); 43 | var elitesNode = document.getElementById('elites'); 44 | 45 | 46 | // Click Event 47 | evolveStartNode.onclick = () => beginEvolution(inputNode.value); 48 | mutationRateNode.onchange = () => {mutationRate = mutationRateNode.value}; 49 | selectionBiasNode.onchange = () => {sBias = selectionBiasNode.value}; 50 | sexNumberNode.onchange = () => {sexNumber = sexNumberNode.value}; 51 | popSizeNode.onchange = () => {popSize = popSizeNode.value}; 52 | elitesNode.onchange = () => {elites = elitesNode.value}; 53 | 54 | // Set up default display 55 | mutationRateNode.value = mutationRate; 56 | sexNumberNode.value = sexNumber; 57 | popSizeNode.value = popSize; 58 | selectionBiasNode.value = sBias; 59 | elitesNode.value = elites; 60 | 61 | 62 | function beginEvolution(target) { 63 | topPerformerNode1.style.color = 'darkorange'; 64 | let targetGenome = getGenomeFromString(target) 65 | let initialPop = generateRandomPopulationOfSize(targetGenome.length, popSize); 66 | let initialScores = getScoresForEntirePopulation(initialPop, targetGenome) 67 | evolution(targetGenome, initialPop, initialScores); 68 | } 69 | 70 | 71 | function evolution(targetGenome, pop, scores) { 72 | let [sortedPop, sortedScores] = getSortedPopAndScores(scores, pop); 73 | pop = generateNewPopulation(sortedPop, sortedScores); 74 | scores = getScoresForEntirePopulation(pop, targetGenome); 75 | console.log('generations') 76 | 77 | if(sortedScores[0] > 0) { 78 | setTimeout(()=>evolution(targetGenome, pop, scores), 0) 79 | displayData(sortedPop, sortedScores); 80 | } 81 | else { 82 | displayData(sortedPop, sortedScores); 83 | topPerformerNode1.style.color = 'darkgreen'; 84 | return; 85 | } 86 | } 87 | 88 | 89 | function displayData(sortedPop, sortedScores) { 90 | topPerformerNode1.textContent = getStringFromGenome(sortedPop[0]); 91 | topPerformerNode2.textContent = getStringFromGenome(sortedPop[1]); 92 | topPerformerNode3.textContent = getStringFromGenome(sortedPop[2]); 93 | topPerformerNode4.textContent = getStringFromGenome(sortedPop[3]); 94 | topPerformerNode5.textContent = getStringFromGenome(sortedPop[4]); 95 | topPerformerNode6.textContent = getStringFromGenome(sortedPop[5]); 96 | topPerformerNode7.textContent = getStringFromGenome(sortedPop[6]); 97 | topPerformerNode8.textContent = getStringFromGenome(sortedPop[7]); 98 | topPerformerNode9.textContent = getStringFromGenome(sortedPop[8]); 99 | topPerformerNode10.textContent = getStringFromGenome(sortedPop[9]); 100 | scoreNode1.textContent = sortedScores[0]; 101 | scoreNode2.textContent = sortedScores[1]; 102 | scoreNode3.textContent = sortedScores[2]; 103 | scoreNode4.textContent = sortedScores[3]; 104 | scoreNode5.textContent = sortedScores[4]; 105 | scoreNode6.textContent = sortedScores[5]; 106 | scoreNode7.textContent = sortedScores[6]; 107 | scoreNode8.textContent = sortedScores[7]; 108 | scoreNode9.textContent = sortedScores[8]; 109 | scoreNode10.textContent = sortedScores[9]; 110 | } 111 | 112 | 113 | function generateNewPopulation(sortedPop, sortedScores) { 114 | let newGeneration = []; 115 | let p = 0; 116 | while(p < popSize) { 117 | if(p < elites) { 118 | newGeneration.push(sortedPop[p]) 119 | } 120 | else { 121 | newGeneration.push( 122 | mateGenomesGetNewGenome( 123 | ...selectNindividuals(sortedPop, sortedScores) 124 | ) 125 | ) 126 | } 127 | p++; 128 | } 129 | return newGeneration; 130 | } 131 | 132 | 133 | function getSortedPopAndScores(scoresArray, popArray) { 134 | //1) combine the arrays: 135 | var list = []; 136 | for (var j in scoresArray) 137 | list.push({'genome': popArray[j], 'score': scoresArray[j]}); 138 | //2) sort: 139 | list.sort(function(a, b) { 140 | return ((a.score < b.score) ? -1 : ((a.score == b.score) ? 0 : 1)); 141 | }); 142 | //3) separate them back out: 143 | for (var k = 0; k < list.length; k++) { 144 | popArray[k] = list[k].genome; 145 | scoresArray[k] = list[k].score; 146 | } 147 | return [popArray, scoresArray]; 148 | } 149 | 150 | 151 | function selectNindividuals(popArray, scoresArray) { 152 | let weightsArray = getWeights(scoresArray); 153 | let selectedIndividuals = []; 154 | let s = 0; 155 | while (s < sexNumber) { 156 | selectedIndividuals.push(popArray[weightedRandSelection(weightsArray)]); 157 | s++; 158 | } 159 | return selectedIndividuals; 160 | } 161 | 162 | 163 | function weightedRandSelection(weights) { 164 | var sumOfWeights = weights.reduce((a,b)=>a+b); 165 | var i, sum = 0, r = Math.random()*sumOfWeights; 166 | for (i in weights) { 167 | sum += weights[i]; 168 | if (r <= sum) return i; 169 | } 170 | } 171 | 172 | 173 | function getWeights(scores) { 174 | return scores.map(s => 1/(s^sBias)); 175 | } 176 | 177 | 178 | function mutateGenome(genome) { 179 | return genome.map(n => { 180 | if(getRandomInt(0, 100) <= mutationRate*100) { 181 | let coin = Math.random(); 182 | if(coin < 0.3) { 183 | return n + 1; 184 | } 185 | else if(coin >= 0.3 && coin < 0.6) { 186 | return n - 1; 187 | } 188 | else if(coin >= 0.6 && coin < 0.75) { 189 | return n + 2; 190 | } 191 | else if(coin >= 0.75 && coin < 0.9) { 192 | return n - 2; 193 | } 194 | else { 195 | return getRandomInt(CHAR_SPACE_MIN, CHAR_SPACE_MAX); 196 | } 197 | } 198 | return n 199 | }) 200 | } 201 | 202 | 203 | function mateGenomesGetNewGenome(...args) { 204 | let n = args.length; 205 | let chunk = Math.floor(args[0].length / n); 206 | let m = 0; 207 | let newGenome = []; 208 | while (m < n){ 209 | if(m === n-1){ 210 | newGenome = newGenome.concat(args[m].slice(m*chunk, args[0].length)); 211 | } 212 | else { 213 | newGenome = newGenome.concat(args[m].slice(m*chunk, (m+1)*chunk)); 214 | } 215 | m++; 216 | } 217 | return mutateGenome(newGenome); 218 | } 219 | 220 | 221 | function getScoresForEntirePopulation(population, targetGenome) { 222 | let newScores = []; 223 | let j = 0; 224 | while (j < population.length) { 225 | newScores.push(compare2Genomes(population[j], targetGenome)); 226 | j++; 227 | } 228 | return newScores; 229 | } 230 | 231 | 232 | function generateRandomPopulationOfSize(individualLength) { 233 | let randPopulation = []; 234 | let n = 0; 235 | let randGenome; 236 | while (n < popSize) { 237 | randPopulation.push(generateRandomGenomeOfLength(individualLength)); 238 | n++; 239 | } 240 | return randPopulation; 241 | } 242 | 243 | 244 | function generateRandomGenomeOfLength(length) { 245 | let newGenome = []; 246 | let i = 0; 247 | while (i < length) { 248 | newGenome.push(getRandomInt(CHAR_SPACE_MIN, CHAR_SPACE_MAX)); 249 | i++; 250 | } 251 | return newGenome; 252 | } 253 | 254 | 255 | function getGenomeFromString(str) { 256 | return str.split("").map(char => char.charCodeAt(0)) 257 | } 258 | 259 | 260 | function getStringFromGenome(genome) { 261 | return String.fromCharCode(...genome); 262 | } 263 | 264 | 265 | function compare2Genomes(g1, g2) { 266 | return g1.reduce((acc, curr, i) => acc + Math.abs(curr - g2[i]), 0); 267 | } 268 | 269 | 270 | function getRandomInt(min, max) { 271 | return Math.floor(Math.random() * (max - min + 1)) + min; 272 | } 273 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Name Evolver 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |

Your name

17 |
18 | 19 | 20 |
21 |
22 |
23 | mutation rate 24 | 25 |
26 |
27 | parents per child 28 | 29 |
30 |
31 | population size 32 | 33 |
34 |
35 | selection bias 36 | 37 |
38 |
39 | elites 40 | 41 |
42 |
43 | 44 |
45 | 46 |
47 |
Top Individuals
48 | Scores 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "evolve-name", 3 | "version": "1.0.0", 4 | "description": "GA to evolve your name", 5 | "main": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Nathaniel Kitzke", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | padding: 3rem; 4 | font-size: 10px; 5 | --organic-font: 'Caveat Brush', cursive; 6 | --main-font: 'Droid Sans', sans-serif; 7 | font-family: var(--main-font); 8 | } 9 | 10 | .container { 11 | text-align: center; 12 | } 13 | 14 | input { 15 | height: 2.2rem; 16 | width: 20rem; 17 | font-size: 2rem; 18 | font-family: var(--main-font);; 19 | font-weight: 500; 20 | } 21 | 22 | .top-bar { 23 | display: flex; 24 | justify-content: space-around; 25 | align-items: flex-end; 26 | } 27 | 28 | .input__container { 29 | display: flex; 30 | flex-flow: column; 31 | align-items: flex-start; 32 | } 33 | 34 | .input__container h2 { 35 | font-family: var(--main-font);; 36 | font-size: 1.3rem; 37 | margin: 0; 38 | } 39 | 40 | .name-input { 41 | display: flex; 42 | flex-flow: row nowrap; 43 | justify-content: flex-start; 44 | } 45 | 46 | .name-input input { 47 | font-family: cursive; 48 | } 49 | 50 | .top-performer { 51 | font-size: 2.2rem; 52 | font-weight: 600; 53 | color: darkorange; 54 | text-align: left; 55 | width: 80%; 56 | font-family: cursive; 57 | } 58 | 59 | .parameter { 60 | font-size: 16px; 61 | display: flex; 62 | flex-flow: column; 63 | } 64 | 65 | .parameter input { 66 | width: 100px; 67 | font-size: 16px; 68 | text-align: center; 69 | } 70 | 71 | .top-performer.title { 72 | color: black; 73 | font-size: 20px; 74 | font-family: var(--main-font); 75 | } 76 | 77 | .score.title { 78 | font-size: 20px; 79 | color: black; 80 | font-family: var(--main-font); 81 | } 82 | 83 | .top-performers { 84 | margin: 2rem 4rem; 85 | display: flex; 86 | flex-flow: column; 87 | align-items: flex-start; 88 | } 89 | 90 | .performer-row { 91 | width: 100%; 92 | display: flex; 93 | flex-flow: row nowrap; 94 | justify-content: space-between; 95 | align-items: center; 96 | } 97 | 98 | .score { 99 | color: darkblue; 100 | font-size: 1.8rem; 101 | width: 20%; 102 | } 103 | 104 | input::-webkit-outer-spin-button, 105 | input::-webkit-inner-spin-button { 106 | /* display: none; <- Crashes Chrome on hover */ 107 | -webkit-appearance: none; 108 | margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ 109 | } 110 | --------------------------------------------------------------------------------