public void Mutate() { // When no genes, add gene mutation if (Connections.Count == 0) { AddConnection(); } double chance = MyRand.UniformRnd(0, 1); // 80% chance to modify weight or bias if (chance < 0.8) { ModifyConnection(); } chance = MyRand.UniformRnd(0, 1); // 8% chance to add new connection if (chance < 0.08) { AddConnection(); } chance = MyRand.UniformRnd(0, 1); // 2% chance to add new node if (chance < 0.02) { AddNode(); } // No mutations today }
// Add new connection to the network public Result AddConnection() { // Select a random node which will have an extra connections // Do not select nodes in the output layer // Check if network is fully connected if (IsNetworkFullyConnected()) { return(Result.fail); } Node fromNode = null; do { // Select a random node int rndNodeNumb = MyRand.Next(0, Nodes.Count); fromNode = Nodes[rndNodeNumb]; //Find new node if this node is fully connected, OR in the output layer } while(IsNodeInOutputLayer(fromNode) || IsNodeFullyConnected(fromNode) ); // Find a node to connect to Node toNode = null; do { // Select a random node int rndNodeNumb = MyRand.Next(0, Nodes.Count); toNode = Nodes[rndNodeNumb]; // Find new node if this node is in the same layer, OR both already connected } while((toNode.Layer == fromNode.Layer) || (IsConnected(fromNode, toNode)) ); //If the tonode is in a lower layer than the fromnode, change positon if (toNode.Layer < fromNode.Layer) { Node tmp = toNode; toNode = fromNode; fromNode = tmp; } // Add connections between the two nodes with random weight Connection newConn = new Connection(fromNode); newConn.ONode = toNode; fromNode.Connections.Add(newConn); Connections.Add(newConn); // Assign innovation ID for new connection InnovationHandler.GetInnovationID(newConn); return(Result.success); }
/// <summary> /// Random uniform weight, and enabled /// </summary> /// <param name="node">The input node</param> public Connection(Node node) { INode = node; Weight = MyRand.UniformRnd(-1, 1); Bias = MyRand.UniformRnd(-1, 1); Enabled = true; ONode = null; // Output nodes are not necessary defined immediately InnovID = 0; // It will be overwritten }
/// <summary> /// Adds a new player to the niche by crossover or cloning, could return null! /// </summary> public Player GetBaby() { // Need at least one player in this species if (Members.Count < 1) { return(null); } Player newPlayer = null; double chance = MyRand.UniformRnd(0, 1); // TODO opt. In this case it is unnecessary to decide which species should this belong // with 25% chance just clone a player || When we only have one member if (chance < 0.25 || Members.Count == 1) { int rndPlayer = MyRand.Next(0, Members.Count); newPlayer = Members[rndPlayer].DeepCopy(); newPlayer.SetFitness(0); newPlayer.LifeSpawn = 0; } // Crossover 75% chance else { // We cant do crossover if there are less than 2 players if (Members.Count < 2) { return(null); } Player parentA = null; Player parentB = null; do { int rndPlayerNumb = MyRand.Next(0, Members.Count); parentA = Members[rndPlayerNumb]; rndPlayerNumb = MyRand.Next(0, Members.Count); parentB = Members[rndPlayerNumb]; } while(parentA.ID == parentB.ID); if (parentA.Fitness > parentB.Fitness) { newPlayer = parentA.Crossover(parentB); } else { newPlayer = parentB.Crossover(parentA); } } return(newPlayer); }
// Toggle connection public Result ToggleConnection() { // Check if we have connections or not if (Connections.Count == 0) { //Cannot Add new node return(Result.fail); } int rndConNumb = MyRand.Next(0, Connections.Count); Connection conn = Connections[rndConNumb]; conn.Enabled = !conn.Enabled; return(Result.success); }
// Crossover this and NetworkB where this is the better parent public Network Crossover(Network networkB) { // Create a child network, which is very similar to this network Network child = new Network(InputNodes, OutputNodes, OutputLayer, NodeCnt); // At first we should Add the nodes, but how to choose which nodes to add? // Chose the better parent which is this foreach (var nodeA in Nodes) { Node childN = nodeA.DeepCopy(nodeA.ID); // we will add the connections later childN.Connections.Clear(); child.Nodes.Add(childN); } //Go through the genes, and decide how to use them foreach (var conA in Connections) { bool enableGene = true; Connection gene = null; //Check if both parents have this gene Connection conB = networkB.FindConnection(conA.InnovID); if (conB != null) { //Both have this gene //Check if it is disabled in any of the parent and with 75% chance if ((!conA.Enabled || !conB.Enabled) && (MyRand.UniformRnd(0, 1) < Cfg.DisableUnnecessaryGene) ) { // These gene doesnt look necessary enableGene = false; } //Decide who will give the gene if (MyRand.UniformRnd(0, 1) < 0.50) { // Parent A Node iNode = child.getNodeByID(conA.INode.ID); Node oNode = child.getNodeByID(conA.ONode.ID); gene = conA.DeepCopy(iNode, oNode); iNode.Connections.Add(gene); } else { // Parent B Node iNode = child.getNodeByID(conB.INode.ID); Node oNode = child.getNodeByID(conB.ONode.ID); gene = conB.DeepCopy(iNode, oNode); iNode.Connections.Add(gene); } } else { //Only parentA has this gene, disjoint or excess gene Node iNode = child.getNodeByID(conA.INode.ID); Node oNode = child.getNodeByID(conA.ONode.ID); gene = conA.DeepCopy(iNode, oNode); iNode.Connections.Add(gene); } gene.Enabled = enableGene; child.Connections.Add(gene); } return(child); }
// Modify an existing connection, can also be turned Off or On public Result ModifyConnection() { // Check if we have connections or not if (Connections.Count == 0) { //Cannot Add new node return(Result.fail); } // Check if every connection is disabled, like in LoL bool allDisabled = true; foreach (var c in Connections) { if (c.Enabled) { allDisabled = false; break; } } if (allDisabled) { return(Result.fail); } // Select a connection which is not disabled Connection conn = null; do { int rndConNumb = MyRand.Next(0, Connections.Count); conn = Connections[rndConNumb]; } while(!conn.Enabled); // Slightly modify this connection's weight or bias if (MyRand.UniformRnd(0, 1) > 0.3) { // Weight change conn.Weight = MyRand.NormalDist(conn.Weight, 0.1); // Not sure if limiting the weight is a good idea or not if (conn.Weight > 1.0) { conn.Weight = 1.0; } if (conn.Weight < -1.0) { conn.Weight = -1.0; } } else { // Bias change conn.Bias = MyRand.NormalDist(conn.Bias, 0.1); // Not sure if limiting the bias is a good idea or not if (conn.Bias > 5.0) { conn.Bias = 5.0; } if (conn.Bias < -5.0) { conn.Bias = -5.0; } } return(Result.success); }
// Add new node on an existing connection public Result AddNode() { if (Connections.Count == 0) { //Cannot Add new node return(Result.fail); } //Get a random connection, it can be disabled too int rndConnNumb = MyRand.Next(0, Connections.Count); Connection rndConn = Connections[rndConnNumb]; //Deactivate this connection rndConn.Enabled = false; //Add a new node on it Node newNode = new Node(NodeCnt); NodeCnt++; newNode.Layer = rndConn.INode.Layer + 1; //Connect INode and new node Connection conn1 = new Connection(rndConn.INode); conn1.Weight = 1.0; conn1.Bias = 0.0; conn1.Enabled = true; conn1.ONode = newNode; InnovationHandler.GetInnovationID(conn1); rndConn.INode.Connections.Add(conn1); //TODO i could consider if the connection was disabled to create two "low" effect connections //Connect new node and Onode Connection conn2 = new Connection(newNode); conn2.Weight = rndConn.Weight; conn2.Bias = rndConn.Bias; conn2.Enabled = true; conn2.ONode = rndConn.ONode; InnovationHandler.GetInnovationID(conn2); newNode.Connections.Add(conn2); //If the connection we are breaking is between layers that are next to eachother //it means we need to add a new layer //else we need to increase the first intermediate layer if ((rndConn.INode.Layer + 1) == rndConn.ONode.Layer) { //New layer is needed //Shift every layer by one after the new layer foreach (var node in Nodes) { if (node.Layer > rndConn.INode.Layer) { node.Layer++; } } OutputLayer++; } else if ((rndConn.INode.Layer + 1) < rndConn.ONode.Layer) { //Layer should already exist } else { throw new Exception("Error: The connection is wrong!"); } //Finally add new object to the lists Nodes.Add(newNode); Connections.Add(conn1); Connections.Add(conn2); return(Result.success); }
// Kills the badly graded player/species and creates new ones public void UnNaturalSelection() { // Kill player and species for (int i = Species.Count - 1; i >= 0; i--) { // Determine the share of this species from the population int share = (int)Math.Floor((Species[i].AvgFitness / SumAvgFitness) * Size); // We should delete species with less members if (share < Math.Ceiling(2 / (1 - Cfg.PlayersLeftAlive))) { Species.RemoveAt(i); } else { // Kill the halflings Species[i].Bloodlust(share); } // DONT put anything after this, i-th element could be removed by now } // Iterate backwards so i can remove elements meanwhile for (int i = 0; i < Species.Count; i++) { // Determine the share of this species from the population int share = (int)Math.Floor((Species[i].AvgFitness / SumAvgFitness) * Size); // Create new players replacing the dead int newToCreate = share - Species[i].Members.Count; for (int j = 0; j < newToCreate; j++) { Player newPlayer = Species[i].GetBaby(); // This shouldn't happen as we took precoutions just before if (newPlayer == null) { throw new Exception($"Cannot generate a new player in this niche!"); } // Mutate the new player newPlayer.Mutate(); // Add this new player to a species, // it could happen that it will have his own, // or a different from where it was born AddToSpecies(newPlayer); } } // Checking number of players int playerNum = 0; foreach (var s in Species) { playerNum += s.Members.Count; } if (playerNum > Size) { throw new Exception("Too many players were assigned!"); } // Because of rounding errors, some spaces are not filled it, fill them in for (int space = 0; space < Size - playerNum; space++) { // Select a random species int i = MyRand.Next(Species.Count); // Get baby from the species Player newPlayer = Species[i].GetBaby(); // Mutate the new player newPlayer.Mutate(); // Add this new player to a species, AddToSpecies(newPlayer); } }