diff --git a/Assets/scripts/ForrestCTRL.cs b/Assets/scripts/ForrestCTRL.cs index fd19532..37b9cad 100644 --- a/Assets/scripts/ForrestCTRL.cs +++ b/Assets/scripts/ForrestCTRL.cs @@ -6,23 +6,23 @@ [RequireComponent(typeof(Rigidbody))] public class ForrestCTRL : MonoBehaviour { - ctrl C; - Rigidbody rb; - Collider col; - public NN nn = new NN(5,4); + protected ctrl C; + protected Rigidbody rb; + protected Collider col; + protected float[] ini; + protected bool menu; + + public NN nn; public string myName; public float movement; public float fitness; - float[] ini; public bool ended, stLap; public Vector2 lap; public float[] inp; public string brain; - bool menu; - // Use this for initialization - void Start() { + virtual protected void Start() { menu = SceneManager.GetActiveScene().name == "menu"; if (!menu) @@ -56,9 +56,12 @@ void Start() { } // Update is called once per frame - void Update() + virtual protected void FixedUpdate() { + if(nn.inputs < 1) { Debug.Log("Forrest doesn't have a brain"); return; } + brain = nn.ReadBrain(); + float[] nnOutputs; // Rotate the charater based on Horizonal Input & later NN Output transform.rotation = Quaternion.Euler(transform.eulerAngles + Vector3.up * movement * 2.5f); @@ -113,14 +116,15 @@ void Update() // Add to our fitness every frame fitness += (ended) ? 0 : inp2fit(inp); + nnOutputs = nn.CalculateNN(inp); // This sets the output text display to be the output of our NN if (!menu) { - movement = ended ? 0 : ((C.intelli == ctrl.IntelMode.Human) ? Input.GetAxis("Horizontal") : nn.CalculateNN(inp)); + movement = ended ? 0 : ((C.intelli == ctrl.IntelMode.Human) ? Input.GetAxis("Horizontal") : nnOutputs[0]); } else { - movement = ended ? 0 : (nn.CalculateNN(inp)); + movement = ended ? 0 : nnOutputs[0]; } // @@ -173,7 +177,7 @@ void OnTriggerEnter(Collider col) } } - public void Reset() + virtual public void Reset() { fitness = 0; lap.x = 1; @@ -189,7 +193,7 @@ public void Reset() } } - void CheckIfLast() + protected void CheckIfLast() { // Grab a ref to every Active Forrest GameObject[] f = GameObject.FindGameObjectsWithTag("Active"); @@ -219,7 +223,7 @@ public void Freeze() r.material = Resources.Load("forrest_full 1"); } - nn.SetFitness(fitness); + nn.SetFitness(fitness); // Remove self from All Forrest Attempts Controller Listings C.allFatt.Remove(this); @@ -230,8 +234,9 @@ public void Freeze() /// Set the Genetic Code for the attempt /// /// - public void SetBrain(float[] i) + public void SetBrain(int[] brainConfig, float[] i) { + nn = new NN(brainConfig[0],brainConfig[1],brainConfig[2]); ini = i; nn.IniWeights(ini); } @@ -240,10 +245,9 @@ public void SetBrain(float[] i) /// Set the Genetic Code for the attempt with name /// /// - public void SetBrain(float[] i, string n) + public void SetBrain(int[] brainConfig, float[] i, string n) { - ini = i; - nn.IniWeights(ini); + SetBrain(brainConfig, i); nn.SetName(n); myName = nn.name; gameObject.name = myName; diff --git a/Assets/scripts/NN.cs b/Assets/scripts/NN.cs index 04efe64..2dad4c1 100644 --- a/Assets/scripts/NN.cs +++ b/Assets/scripts/NN.cs @@ -13,12 +13,19 @@ public class NN int _inputs; public int inputs { get { return _inputs; } } float[][] _hLw; - public int weightCount { get { return TotalWeightCount(); } } float[] _hLb; - float[] Ow; - float Ob; + int _outputs; + public int outputs { get { return _outputs; } } + float[][] Ow; + float[] Ob; float _fitness; public double fitness { get { return _fitness; } } + public int geneSize { get { return (_inputs * _hL.Length) + (_hL.Length) + (_hL.Length * _outputs) + Ob.Length; } } + + public static int GetGeneSize(int inputs, int hiddenLayers, int outputs) + { + return (inputs * hiddenLayers) + hiddenLayers + (hiddenLayers * outputs) + outputs; + } /// /// Sometimes you just want access to variables like fitness & don't need to pass in node counts @@ -33,11 +40,14 @@ public NN() /// /// /// - public NN(int inpsNumb, int _hLNodes) + public NN(int inpsNumb, int _hLNodes, int outputs = 1) { _hL = new float[_hLNodes]; - IniWeights(inpsNumb); _inputs = inpsNumb; + _outputs = outputs; + Ow = new float[outputs][]; + Ob = new float[outputs]; + IniWeights(inpsNumb); } /// @@ -58,8 +68,12 @@ void IniWeights(int inps) // _inputs = inps; - // - Ow = new float[_hL.Length]; + // + Ow = new float[_outputs][]; + for(int i = 0; i < _outputs; i++) { + Ow[i] = new float[_hL.Length]; + } + Ob = new float[Ow.Length]; // Set a double nested array, [hidden layer length][input length] _hLw = new float[_hL.Length][]; @@ -72,7 +86,6 @@ void IniWeights(int inps) { _hLw[i] = new float[inps]; _hLb[i] = 0;// r.Next(-cap, cap); - Ow[i] = 0;// r.Next(-cap, cap); // for (int j = 0; j < _hLw[i].Length; j++) @@ -80,6 +93,15 @@ void IniWeights(int inps) _hLw[i][j] = 0;// r.Next(-cap, cap); } } + + for(int i = 0; i < _outputs; i++) + { + for(int j = 0; j < _hL.Length; j++) + { + Ow[i][j] = 0; + } + Ob[i] = 0; + } } /// @@ -88,6 +110,9 @@ void IniWeights(int inps) /// public void IniWeights(float[] w) { + int outputWeightOffset = (inputs*_hL.Length)+_hLb.Length; + int outputBiasOffset = w.Length - Ob.Length; + // Okay don't freak out from this code let's walk through it step by step. // first were going to run a for loop the length of the number of nodes in our Hidden Layer @@ -99,17 +124,22 @@ public void IniWeights(float[] w) for (int j = 0; j < _hLw[i].Length; j++) { // this is exactly what I described above but in math form - _hLw[i][j] = w[j + (i * 6)]; + _hLw[i][j] = w[j + (i * (inputs+1))]; } // then we're going to map all the biases for ALL nodes in the Hidden Layer, as described above but in math form - _hLb[i] = w[5 + (i * 6)]; - + _hLb[i] = w[inputs + (i * (inputs+1))]; + } + for(int i = 0; i < _outputs; i++) { // Then we're going to map all of the Output weights, as described above but in math form - Ow[i] = w[i + 24]; + for(int j = 0; j < _hL.Length; j++) { + Ow[i][j] = w[j + (i * _hL.Length) + outputWeightOffset]; + } + } + for(int i = 0; i < Ob.Length; i++) { // & lastly the Output bias is the last on the input w sequence & we're done! - Ob = w[w.Length - 1]; + Ob[i] = w[w.Length - outputBiasOffset + i]; } } @@ -119,36 +149,13 @@ public void IniWeights(float[] w) /// public void IniWeights(string inp) { - float[] w = new float[weightCount]; - + float[] w = new float[geneSize]; + string[] splitWeights = inp.Split(','); for (int i = 0; i < w.Length; i++) { - w[i] = float.Parse(inp.Split(',')[i]); - } - - // Okay don't freak out from this code let's walk through it step by step. - - // first were going to run a for loop the length of the number of nodes in our Hidden Layer - for (int i = 0; i < _hLw.Length; i++) - { - // then we're going to map all of the hidden layer weights from our float vector input w - // & because I encode the input vector w as all _hL[0] weights then _hL[0] bias, then _hL[1] weight -> _hL[1] bias - // all the way to it's length, then do all Output weights, then Output bias, I am going to map my Neural Net accordingly - for (int j = 0; j < _hLw[i].Length; j++) - { - // this is exactly what I described above but in math form - _hLw[i][j] = w[j + (i * 6)]; - } - - // then we're going to map all the biases for ALL nodes in the Hidden Layer, as described above but in math form - _hLb[i] = w[5 + (i * 6)]; - - // Then we're going to map all of the Output weights, as described above but in math form - Ow[i] = w[i + 24]; - - // & lastly the Output bias is the last on the input w sequence & we're done! - Ob = w[w.Length - 1]; + w[i] = float.Parse(splitWeights[i]); } + IniWeights(w); } /// @@ -156,18 +163,23 @@ public void IniWeights(string inp) /// /// /// - public float CalculateNN(float[] inps) + public float[] CalculateNN(float[] inps) { + float[] Outputs = new float[_outputs]; // Set the value of all hidden layer outputs for (int i = 0; i < _hL.Length; i++) { _hL[i] = ReLU(Sum(inps, _hLw[i]) + _hLb[i]); } - float O = SoftSign(Sum(Ow, _hL) + Ob); + for(int i = 0; i < _outputs; i++) { + Outputs[i] = SoftSign(Sum(Ow[i], _hL) + Ob[i]); + } + //float O = SoftSign(Sum(Ow, _hL) + Ob); //float O = ACTIVATION(Sum(Ow, _hL) + Ob); - return O; + //return O; + return Outputs; } /// @@ -223,12 +235,17 @@ public string ReadBrain() for (int i = 0; i < Ow.Length; i++) { - dna += Ow[i] + ","; + for (int j = 0; j < _hL.Length; j++) { + dna += Ow[i][j] + ","; + } } - dna += Ob; + for (int i = 0; i < Ob.Length; i++) + { + dna += Ob[i] + ","; + } - return dna; + return dna.TrimEnd(','); } /// @@ -249,15 +266,6 @@ public float[] GetBrain() return ret; } - /// - /// Returns the total weight count - /// - /// - int TotalWeightCount() - { - return (_hLw[0].Length * _inputs) + Ow.Length; - } - /// /// Sets the name of the NN /// diff --git a/Assets/scripts/ctrl.cs b/Assets/scripts/ctrl.cs index 07d858e..3f497fa 100644 --- a/Assets/scripts/ctrl.cs +++ b/Assets/scripts/ctrl.cs @@ -44,6 +44,8 @@ public enum SelMode { Percent, Top2}; Image[] aRs; menuParams mP; + public int[] brainConfig = new int[] { 5, 4, 1 }; + // 1, 2, 3, 4, 5 GameObject[] graphs = new GameObject[2]; int graphWidth = 860; @@ -151,7 +153,8 @@ void Start() // then every brian will hold 29 values, in our case floats, so we'll loop through every brain & set their value size to hold 29 floats, which will be a fully randomized brain for (int i = 0; i < attIniData.Length; i++) { - attIniData[i] = new float[29]; + //attIniData[i] = new float[33]; + attIniData[i] = new float[NN.GetGeneSize(brainConfig[0],brainConfig[1],brainConfig[2])]; } // @@ -455,18 +458,18 @@ public void SpawnForrest(int num) // Set the brain to the corresponding attention ini data if (mode == GameMode.Train) { - f.SetBrain(attIniData[i]); + f.SetBrain(brainConfig,attIniData[i]); } else if (mode == GameMode.Test) { - f.SetBrain(attIniData[i], mP.brains[i].Split('\n')[0]); + f.SetBrain(brainConfig,attIniData[i], mP.brains[i].Split('\n')[0]); } else if (mode == GameMode.Campaign) { string name = mP.campNN.Split('\n')[0]; string pass = mP.campNN.Split('\n')[1]; - f.SetBrain(ParseBrain1(pass, 29), name); + f.SetBrain(brainConfig,ParseBrain1(pass, NN.GetGeneSize(brainConfig[0],brainConfig[1],brainConfig[2])), name); } // Add the Forrest attemp to the list of All Forrest Attempts @@ -496,12 +499,12 @@ public void SpawnForrest(int num) SetHighest(); SetGraphs(); attempt.x = -1; - startFatt.Clear(); + startFatt.Clear(); day++; } attempt.x++; - ForrestCTRL f = Instantiate(forrest).GetComponent(); + ForrestCTRL f = Instantiate(forrest).GetComponent(); // Set the position to be that of the starting sphere f.transform.position = new Vector3(startSp.transform.position.x, f.transform.position.y, startSp.transform.position.z); @@ -512,11 +515,11 @@ public void SpawnForrest(int num) // Set the brain to the corresponding attention ini data if (mode == GameMode.Train) { - f.SetBrain(attIniData[(int)attempt.x]); + f.SetBrain(brainConfig,attIniData[(int)attempt.x]); } else if (mode == GameMode.Test) { - f.SetBrain(attIniData[(int)attempt.x], mP.brains[(int)attempt.x].Split('\n')[0]); + f.SetBrain(brainConfig,attIniData[(int)attempt.x], mP.brains[(int)attempt.x].Split('\n')[0]); } allFatt.Add(f); @@ -539,7 +542,7 @@ public void RespawnForrest(int num) // loop through the num count & spawn a every Forrest attempt for that day for (int i = 0; i < num; i++) { - ForrestCTRL f = grabPast[i].GetComponent(); + ForrestCTRL f = grabPast[i].GetComponent(); // Set the position to be that of the starting sphere f.transform.position = new Vector3(startSp.transform.position.x, f.transform.position.y, startSp.transform.position.z); @@ -548,7 +551,7 @@ public void RespawnForrest(int num) f.transform.eulerAngles = new Vector3(f.transform.eulerAngles.x, startSp.transform.eulerAngles.y, f.transform.eulerAngles.z); // Set the brain to the corresponding attention ini data - f.SetBrain(attIniData[i]); + f.SetBrain(brainConfig,attIniData[i]); // f.Reset(); @@ -580,7 +583,7 @@ public void RespawnForrest(int num) } attempt.x++; - ForrestCTRL f = grabPast[(int)attempt.x].GetComponent(); + ForrestCTRL f = grabPast[(int)attempt.x].GetComponent(); // Set the position to be that of the starting sphere f.transform.position = new Vector3(startSp.transform.position.x, f.transform.position.y, startSp.transform.position.z); @@ -591,11 +594,11 @@ public void RespawnForrest(int num) // Set the brain to the corresponding attention ini data if (mode == GameMode.Train) { - f.SetBrain(attIniData[(int)attempt.x]); + f.SetBrain(brainConfig,attIniData[(int)attempt.x]); } else if (mode == GameMode.Test) { - f.SetBrain(attIniData[(int)attempt.x], mP.brains[(int)attempt.x].Split('\n')[0]); + f.SetBrain(brainConfig,attIniData[(int)attempt.x], mP.brains[(int)attempt.x].Split('\n')[0]); } allFatt.Add(f); @@ -698,7 +701,7 @@ private void SetHighest() if (tF.fitness > highestFit.fitness) { // Create a new NN to store the highest brain from the training session - highestFit = new NN(tF.nn.inputs, tF.nn.hL); + highestFit = new NN(tF.nn.inputs, tF.nn.hL, tF.nn.outputs); // Set that fitness to the new record Forrest which is tF highestFit.SetFitness(tF.fitness); @@ -907,7 +910,7 @@ void LearnFromAttempts() allNN.Add(pastAttempts[i].GetComponent().nn); } - NN[] parents= new NN[0]; + //NN[] parents= new NN[0]; // Get the sum of all fitness scores @@ -1145,7 +1148,8 @@ void SliceCrossOver(List aNN) void RandomizeAParentWeights(NN[] parents) { // set r that will hold a randomly generated brain - float[] r = new float[29]; + + float[] r = new float[parents[0].geneSize]; // loop through & generate a brain for (int k = 0; k < r.Length; k++) @@ -1168,8 +1172,8 @@ void RandomizeAParentWeights(NN[] parents) string GenerateOffspringBrain(NN[] parents) { // First create slice start & stop points - int start = Random.Range(0, 29); - int stop = Random.Range(start, 29); + int start = Random.Range(0, parents[0].geneSize); + int stop = Random.Range(start, parents[0].geneSize); // Then create the offspring brain string string offBrain = ""; @@ -1187,10 +1191,11 @@ string GenerateOffspringBrain(NN[] parents) } // Finally loop through the first selected parent values again from the end of the cut to the end of the brain sequence & add that to the offspring's brain - for (int i = stop; i < 29; i++) + int geneSize = parents[0].geneSize; + for (int i = stop; i < geneSize; i++) { // Checks for if at the end of loop or not for comma - bool com = i != 28; + bool com = i != (geneSize - 1); // The adding part offBrain += parents[0].ReadBrain().Split(',')[i] + (com ? "," : string.Empty);