/// <summary> /// Toggles the frozen status of a single gene if possible. /// </summary> /// <param name="genome">The genome to be mutated.</param> /// <param name="rando">A random number generator.</param> public static void MutateToggleFreeze(Genome genome, Random rando) { int geneId = rando.Next(genome.Size()); Gene gene = genome.Genes[geneId]; gene.Frozen = !gene.Frozen; if (!genome.Validate(true)) { gene.Frozen = !gene.Frozen; } }
/// <summary> /// Common genes are selected at random. /// </summary> /// <param name="child">The Gene object where information will be stored.</param> /// <param name="stronger">The stronger of the two parents.</param> /// <param name="weaker">The weaker of the two parents.</param> /// <param name="rando">A random number generator.</param> private static void MateMultiPointRegular(Gene child, Gene stronger, Gene weaker, Random rando) { if (rando.NextDouble() < SelectRandomlyTheStronger) { child.Copy(stronger); } else { child.Copy(weaker); } child.Frozen = false; }
/// <summary> /// Get a unique innovation number based on the innovation numbers /// of the start and end node. /// This is used for historical marking. /// </summary> private static int GetInnovationNumber(Gene target) { Gene matchingGene = kindsOfGenes.FirstOrDefault(x => (x.StartNode.InnovationNb == target.StartNode.InnovationNb) && (x.EndNode.InnovationNb == target.EndNode.InnovationNb)); if (matchingGene != null) { return(matchingGene.InnovationNb); } kindsOfGenes.Add(target); return(kindsOfGenes.Count - 1); }
/// <summary> /// Mutates a genome by breaking a connection up into two separate connections. /// </summary> /// <param name="genome">The genome to be modified.</param> /// <param name="innovationsSeen">A list of previously seen innovations.</param> /// <param name="rando">A random number generator.</param> public static void MutateAddNode(Genome genome, InnovationPool innovationsSeen, Random rando) { List <Gene> possibleConnections = new List <Gene>(genome.Genes); possibleConnections.RemoveAll(x => x.Frozen || NodePool.FindById(x.link.InNode).Type == NodeType.BIAS); if (possibleConnections.Count == 0) { return; } //TODO: Note in original algorithm saying uniform distribution is not optimal here. Gene geneToSplit = possibleConnections[rando.Next(possibleConnections.Count)]; geneToSplit.Frozen = true; ActivationStyle style = ActivationFunctions.ChooseActivationStyle(rando); InnovationInformation innovation = new InnovationInformation(geneToSplit.link, style); int firstConId = -1; int secondConId = -1; int registeredInnovationId = innovationsSeen.FindByInnovation(innovation); if (registeredInnovationId == -1) { int newNodeId = NodePool.Add(new NodeInformation(NodeType.HIDDEN, style)); ConnectionInformation firstConnect = new ConnectionInformation(geneToSplit.link.InNode, newNodeId); firstConId = ConnectionPool.Add(firstConnect); ConnectionInformation secondConnect = new ConnectionInformation(newNodeId, geneToSplit.link.OutNode); secondConId = ConnectionPool.Add(secondConnect); innovation.NewNodeDetails.NewNodeId = newNodeId; innovation.NewNodeDetails.FirstConnectionId = firstConId; innovation.NewNodeDetails.SecondConnectionId = secondConId; innovationsSeen.Add(innovation); } else { InnovationInformation registeredInnovation = innovationsSeen.FindById(registeredInnovationId); firstConId = registeredInnovation.NewNodeDetails.FirstConnectionId; secondConId = registeredInnovation.NewNodeDetails.SecondConnectionId; } genome.Genes.Add(new Gene(ConnectionPool.FindById(firstConId), firstConId, 1.0, false)); genome.Genes.Add(new Gene(ConnectionPool.FindById(secondConId), secondConId, geneToSplit.Weight, false)); }
/// <summary> /// Validates a single gene. /// </summary> /// <param name="gene">The gene to validate.</param> /// <param name="excludeFrozen">Exclude frozen genes from validation (true network).</param> /// <returns>True if the gene is connected to an input and output node.</returns> private bool ValidateGene(Gene gene, bool excludeFrozen) { List <Gene> checkedGenes = new List <Gene>(); if (!ValidateGeneForward(gene, checkedGenes, excludeFrozen)) { return(false); } checkedGenes = new List <Gene>(); if (!ValidateGeneBackward(gene, checkedGenes, excludeFrozen)) { return(false); } return(true); }
public static void DisplayGene(Gene gene, EditorGeneData editorGeneData) { editorGeneData.isFoldedOut = EditorGUILayout.Foldout(editorGeneData.isFoldedOut, "Gene: " + gene.InnovationNb); if (!editorGeneData.isFoldedOut) { return; } EditorGUI.indentLevel++; EditorGUILayout.Toggle("Is enabled: ", gene.IsEnabled); EditorGUILayout.LabelField("InNode: " + gene.StartNode.InnovationNb); EditorGUILayout.LabelField("OutNode: " + gene.EndNode.InnovationNb); EditorGUI.indentLevel--; }
/// <summary> /// Unfreezes a random frozen gene if possible. /// </summary> /// <param name="genome">The genome to be mutated.</param> /// <param name="rando">A random number generator.</param> public static void MutateUnFreeze(Genome genome, Random rando) { List <Gene> possibilities = genome.Genes.Where(x => x.Frozen).ToList(); if (possibilities.Count == 0) { return; } int possibleId = rando.Next(possibilities.Count); Gene gene = possibilities[possibleId]; gene.Frozen = false; if (!genome.Validate(true)) { gene.Frozen = !gene.Frozen; } }
/// <summary> /// Initialize the network with the starting genes. /// All inputs, but the bias, will be connected to output neurons. /// </summary> private void InitStartingGenes(float geneWeightRandomVal) { Genes = new List <Gene>(InputCount * OutputCount); var inputNeurons = InputNeurons.ToArray(); var outputNeurons = OutputNeurons.ToArray(); for (int i = 0; i < InputCount; i++) { for (int j = 0; j < OutputCount; j++) { Gene newGene = new Gene( inputNeurons[i], outputNeurons[j], Random.Range(-geneWeightRandomVal, geneWeightRandomVal) ); Genes.Add(newGene); inputNeurons[i].OutGenes.Add(newGene); outputNeurons[j].InGenes.Add(newGene); } } }
/// <summary> /// Validates that a gene is connected to an output node. /// </summary> /// <param name="gene">The gene being validated.</param> /// <param name="pathsChecked">A list of genes that have been traversed in the recursion.</param> /// <param name="excludeFrozen">Exclude frozen genes from validation (true network).</param> /// <returns>True if the gene is connected to an output node.</returns> private bool ValidateGeneForward(Gene gene, List <Gene> pathsChecked, bool excludeFrozen) { pathsChecked.Add(gene); if (NodePool.FindById(gene.link.OutNode).Type == NodeType.OUTPUT) { return(true); } foreach (var path in Genes.Where(x => x.link.InNode == gene.link.OutNode && !pathsChecked.Contains(x))) { if (excludeFrozen && path.Frozen) { continue; } if (ValidateGeneForward(path, pathsChecked, excludeFrozen)) { return(true); } } return(false); }
/// <summary> /// Yarr... this be copied almost exactly as the original NEAT. /// In the original NEAT, none of the settings had chance this would happen. /// </summary> /// <param name="style">The specific type of multi-point mating style.</param> /// <param name="mother">A parent genome.</param> /// <param name="father">A parent genome.</param> /// <param name="rando">A random number generator.</param> /// <returns>A product of the given multi-point mating style.</returns> private static Genome MateSinglePoint(MatingStyle style, Genome mother, Genome father, Random rando) { mother.SortGenesByConnectionId(); father.SortGenesByConnectionId(); Genome largerParent = mother; Genome smallerParent = father; if (father.Size() > mother.Size()) { largerParent = father; smallerParent = mother; } List <Gene> childGenes = new List <Gene>(); int largerIndex = 0; int smallerIndex = 0; int crossoverPoint = rando.Next(smallerParent.Size()); int crossoverCounter = 0; while (smallerParent.Size() != smallerIndex) { Gene chosenGene = new Gene(); if (largerIndex == largerParent.Size()) { chosenGene.Copy(smallerParent.Genes[smallerIndex]); smallerIndex++; } else if (smallerIndex == smallerParent.Size()) { //Should never happen chosenGene.Copy(largerParent.Genes[largerIndex]); largerIndex++; } else { int largerGeneId = largerParent.Genes[largerIndex].Id; int smallerGeneId = smallerParent.Genes[smallerIndex].Id; if (largerGeneId == smallerGeneId) { if (crossoverCounter < crossoverPoint) { chosenGene.Copy(largerParent.Genes[largerIndex]); } else if (crossoverCounter > crossoverPoint) { chosenGene.Copy(smallerParent.Genes[smallerIndex]); } else { switch (style) { case MatingStyle.SinglePoint: //Function is equivalent to what is needed here despite the misleading name. MateMultiPointAverage(chosenGene, largerParent.Genes[largerIndex], smallerParent.Genes[smallerIndex], rando); break; default: throw new Exception("MateSinglePoint was not given a valid mating style."); } } chosenGene.Frozen = false; //The NEAT implementation did not call for a chance this would happen. It just did it. [ NEAT.1.2.1 => Genome::mate_singlepoint(...) ] if (largerParent.Genes[largerIndex].Frozen || smallerParent.Genes[smallerIndex].Frozen) { chosenGene.Frozen = true; } largerIndex++; smallerIndex++; crossoverCounter++; } else if (largerGeneId < smallerGeneId) { if (crossoverCounter < crossoverPoint) { chosenGene.Copy(largerParent.Genes[largerIndex]); largerIndex++; crossoverCounter++; } else { chosenGene.Copy(smallerParent.Genes[smallerIndex]); smallerIndex++; } } else { smallerIndex++; continue; } } if (GeneIsUnique(chosenGene, childGenes)) { childGenes.Add(chosenGene); } } return(new Genome(childGenes)); }
/// <summary> /// Logic for disjoint/excess genes between mother/father in multi-point mating styles. /// Delegates common genes to a more specific function. /// </summary> /// <param name="style">The specific type of multi-point mating style.</param> /// <param name="mother">A parent genome.</param> /// <param name="father">A parent genome.</param> /// <param name="rando">A random number generator.</param> /// <returns>A product of the given multi-point mating style.</returns> private static Genome MateMultiPoint(MatingStyle style, Genome mother, Genome father, Random rando) { mother.SortGenesByConnectionId(); father.SortGenesByConnectionId(); Genome strongerParent = mother; Genome weakerParent = father; if (father.Fitness.Score > mother.Fitness.Score) { strongerParent = father; weakerParent = mother; } List <Gene> childGenes = new List <Gene>(); int strongerIndex = 0; int weakerIndex = 0; while (strongerIndex < strongerParent.Size() || weakerIndex < weakerParent.Size()) { Gene chosenGene = new Gene(); if (strongerIndex == strongerParent.Size()) { break; } else if (weakerIndex == weakerParent.Size()) { chosenGene.Copy(strongerParent.Genes[strongerIndex]); strongerIndex++; } else { int strongerGeneId = strongerParent.Genes[strongerIndex].Id; int weakerGeneId = weakerParent.Genes[weakerIndex].Id; if (strongerGeneId == weakerGeneId) { switch (style) { case MatingStyle.MultiPoint: MateMultiPointRegular(chosenGene, strongerParent.Genes[strongerIndex], weakerParent.Genes[weakerIndex], rando); break; case MatingStyle.MultiPointAverage: MateMultiPointAverage(chosenGene, strongerParent.Genes[strongerIndex], weakerParent.Genes[weakerIndex], rando); break; default: throw new Exception("MateMultiPoint was not given a valid mating style."); } //TODO: If both parents freeze their kid... should it be automatically frozen? chosenGene.Frozen = false; if (strongerParent.Genes[strongerIndex].Frozen && weakerParent.Genes[weakerIndex].Frozen) { chosenGene.Frozen = true; } else if (strongerParent.Genes[strongerIndex].Frozen || weakerParent.Genes[weakerIndex].Frozen) { if (rando.NextDouble() < FreezeGeneOfFrozenParentProbability) { chosenGene.Frozen = true; } } strongerIndex++; weakerIndex++; } else if (strongerGeneId < weakerGeneId) { chosenGene.Copy(strongerParent.Genes[strongerIndex]); strongerIndex++; } else { weakerIndex++; continue; } } if (GeneIsUnique(chosenGene, childGenes)) { childGenes.Add(chosenGene); } } return(new Genome(childGenes)); }
/// <summary> /// Classic compatibility function as implemented in the original NEAT. /// </summary> /// <param name="p1">The first genome.</param> /// <param name="p2">The second genome.</param> /// <returns>A measure of how far apart the two Genomes are.</returns> public static double Compatibility(Genome p1, Genome p2) { p1.SortGenesByConnectionId(); p2.SortGenesByConnectionId(); int p1Gene = 0; int p2Gene = 0; int p1End = p1.Size(); int p2End = p2.Size(); int disjointCount = 0; int excessCount = 0; int commonGeneCount = 0; double totalWeightDifferences = 0; while (p1Gene != p1End || p2Gene != p2End) { if (p1Gene == p1End) { p2Gene++; excessCount++; } else if (p2Gene == p2End) { p1Gene++; excessCount++; } else { Gene g1 = p1.Genes[p1Gene]; Gene g2 = p2.Genes[p2Gene]; if (g1.Id == g2.Id) { commonGeneCount++; totalWeightDifferences += Math.Abs(g1.Weight - g2.Weight); p1Gene++; p2Gene++; } else if (g1.Id < g2.Id) { disjointCount++; p1Gene++; } else { disjointCount++; p2Gene++; } } } int N = 1; if (p1End >= GenomeSizeNormalizationThresh || p2End >= GenomeSizeNormalizationThresh) { N = Math.Max(p1End, p2End); } double ret = DisjointCoefficient * disjointCount / N + ExcessCoefficient * excessCount / N + AvgWeightDiffCoefficient * (totalWeightDifferences / commonGeneCount); return(ret); }
public Gene(Gene other) { GeneInit(other.StartNode, other.EndNode, other.Weight, other.IsEnabled); InnovationNb = other.InnovationNb; }
/// <summary> /// Remove gene from the network with all its dependencies. /// </summary> public Gene RemoveGene(Gene targetGene) { targetGene.Disconnect(); Genes.Remove(targetGene); return(targetGene); }
/// <summary> /// Constructs the network using the implied topography. /// </summary> /// <param name="allGenes">A list of all the genes in the genome.</param> /// <param name="sensorNodeIndices">An ordered list of sensor nodes needed.</param> /// <param name="outputNodeIndices">An ordered list of output nodes needed.</param> public Network(List <Gene> allGenes, Dictionary <int, NodeInformation> allNodes) { //TODO: Fix comments. foreach (var node in allNodes) { if (node.Value.IsInput()) { SensorNodes.Add(node.Key, new Node(node.Key, node.Value)); } else if (node.Value.Type == NodeType.OUTPUT) { OutputNodes.Add(node.Key, new Node(node.Key, node.Value)); } } for (int i = 0; i < allGenes.Count; i++) { Gene gene = allGenes[i]; if (gene.Frozen) { continue; } ConnectionInformation link = gene.link; Node from = null; Node to = null; foreach (var node in SensorNodes) { if (node.Key == link.InNode) { from = node.Value; } if (node.Key == link.OutNode) { to = node.Value; } } foreach (var node in HiddenNodes) { if (node.Key == link.InNode) { from = node.Value; } if (node.Key == link.OutNode) { to = node.Value; } } foreach (var node in OutputNodes) { if (node.Key == link.InNode) { from = node.Value; } if (node.Key == link.OutNode) { to = node.Value; } } if (from == null) { from = new Node(link.InNode, allNodes[link.InNode]); HiddenNodes.Add(from.Id, from); } if (to == null) { to = new Node(link.OutNode, allNodes[link.OutNode]); if (!HiddenNodes.Select(x => x.Key).ToList().Contains(to.Id)) { HiddenNodes.Add(to.Id, to); } } Connections.Add(new Connection(from, to, gene.Weight)); } }