-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Genetic Algorithm - Sample Code #911
andrewtacon
started this conversation in
General
-
Wrote this, not sure if it will be useful to anyone. It:
- sets up to create a set of random neural networks
- breeds to most successful networks together to create a new generation
/**
* This creates a random model for a neural network
* @param {*} config - normal config from a model
* @param {*} inputs - list of inputs as text array
* @param {*} outputs - list of outputs as text array
* @param {*} qty - how many to produce - default returns 1 model, other values return an array containing requested models
*/
function createRandomModel(config, inputs, outputs, qty = 1) {
let modelSet = []
for (let number = 0; number < qty; number++) {
let model = {}
model.type = "NeuralNetwork"
model.options = {
"inputSize": 0,
"outputSize": 0,
"binaryThresh": 0.5
}
//options should include config
for (let [key, value] of Object.entries(config)) {
model.options[key] = value
}
model.trainOpts = {
"activation": "sigmoid",
"iterations": 20000,
"errorThresh": 0.005,
"log": false,
"logPeriod": 10,
"leakyReluAlpha": 0.01,
"learningRate": 0.3,
"momentum": 0.1,
"callbackPeriod": 10,
"timeout": "Infinity",
"beta1": 0.9,
"beta2": 0.999,
"epsilon": 1e-8
}
if (!isNaN(inputs)) {
model.inputLookupLength = inputs.length
model.inputLookup = {}
for (let i = 0; i < inputs.length; i++) {
model.inputLookup[inputs[i]] = i
}
} else {
model.inputLookupLength = 0
model.inputLookup = null
}
if (isNaN(outputs)) {
model.outputLookupLength = outputs.length
model.outputLookup = {}
for (let i = 0; i < outputs.length; i++) {
model.outputLookup[outputs[i]] = i
}
} else {
model.outputLookupLength = 0
model.outputLookup = null
}
model.sizes = []
if (isNaN(inputs)) {
model.sizes.push(inputs.length)
} else {
model.sizes.push(inputs)
}
if (!model.options.hiddenLayers) {
model.sizes.push(3)
} else {
model.sizes.push(...model.options.hiddenLayers)
}
if (isNaN(outputs)) {
model.sizes.push(outputs.length)
} else {
model.sizes.push(outputs)
}
//build the layers models
model.layers = []
for (let i = 0; i < model.sizes.length; i++) {
model.layers.push(
{
weights: [],
biases: []
}
)
}
//populate the biases and weights
//biases match the number of elements in the layer
//weights match the number of elements in the previous layer
for (let i = 1; i < model.sizes.length; i++) {
let current = model.sizes[i]
let previous = model.sizes[i - 1]
//populate biases
for (let j = 0; j < current; j++) {
let bias = Math.random() * 20 - 10 //between -10 and 10
model.layers[i].biases.push(bias)
}
//populate weights
for (let j = 0; j < current; j++) {
let weights = []
for (let k = 0; k < previous; k++) {
let weight = Math.random() * 20 - 10 //between -10 and 10
weights.push(weight)
}
model.layers[i].weights.push(weights)
}
}
modelSet.push(model)
}
if (qty === 1) {
return modelSet[0]
} else {
return modelSet
}
}
/**
* this functions takes any two given models and creates a hybrid by
* randomly assigning weights and biases to the new individual based on the
* parents and then adjusting this value by a small margin (up to 5% above or below current value)
*/
function breedPair(model1, model2, bigVariation) {
let model3 = JSON.parse(JSON.stringify(model1))
for (let i = 1; i < model3.layers.length; i++) {
let layerOn3 = model3.layers[i]
let layerOn2 = model2.layers[i]
//combine weights randomly
for (let w = 0; w < layerOn2.weights.length; w++) {
let weightsOn3 = layerOn3.weights[w]
let weightsOn2 = layerOn2.weights[w]
for (let v = 0; v < weightsOn3.length; v++) {
if (!bigVariation) {
weightsOn3[v] = (weightsOn2[v] + weightsOn3[v]) / 2 * (0.95 + Math.random() / 10)
} else {
weightsOn3[v] = (weightsOn2[v] + weightsOn3[v]) / 2 * (0.5 + Math.random())
}
}
}
//combine biases randomly
for (let b = 0; b < layerOn2.biases.length; b++) {
if (!bigVariation) {
layerOn3.biases[b] = (layerOn3.biases[b] + layerOn2.biases[b]) / 2 * (0.95 + Math.random() / 10)
} else {
layerOn3.biases[b] = (layerOn3.biases[b] + layerOn2.biases[b]) / 2 * (0.5 + Math.random())
}
}
}
return model3
}
/**
*
* @param {Array} models - an array of models to breed off of
* @param {Array} fitnesses - an array of numbers for the fitness of the models - this is used to determine the probability that a model will contribute to the next
* generation
*/
let generation = 0
function breedNextGeneration(models, fitnesses, keepbest = true) {
//check - if all fitness the same value the create entire new random generation
let f = fitnesses[0]
let bigVariation = true
for (let i = 0; i < fitnesses.length; i++) {
if (fitnesses[i] !== f) {
bigVariation = false
}
}
let initialValue = 0
let totalFitness = fitnesses.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue
)
console.log(`Generation ${generation++} average fitness: ${totalFitness / fitnesses.length}`)
console.log(`Commencing generation ${generation}...`)
let fit = [] //this is an array that normalises the fitnesses to be proportional to a range of 0-1
let count = 0
let max = 0
let bestModel
for (let i = 0; i < fitnesses.length; i++) {
if (fitnesses[i] > max) {
max = fitnesses[i]
bestModel = models[i]
}
count += fitnesses[i]
let percent = count / totalFitness
fit.push(percent * 2) //multiply by two so only top half breed
}
let newModels = []
//keep the previous best model
if (keepbest) { newModels.push(bestModel) }
while (newModels.length < models.length) {
let p = Math.random()
let pos = 0
while (p > fit[pos]) {
pos++
}
let model1 = models[pos]
p = Math.random()
pos = 0
while (p > fit[pos]) {
pos++
}
let model2 = models[pos]
newModels.push(breedPair(model1, model2, bigVariation))
}
return newModels
}
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment