Advertisement

[solved] Stuck with trying to write a genetic algorithm routine

Started by February 11, 2006 07:34 AM
-1 comments, last by Mizipzor 18Β years, 9Β months ago
I tried to make a neural net with back propagation, something wasnt working and I gave up. Tried to train the net through genetic algorithms instead but Im stuck here to. :P This is for a school assignment to, and Im running out of time. Would really appreciate any kind of help here. When writing the class that handles the evolution of the neural nets, I followed the tutorial at ai-junkie. Had to do some adaptions since it had to work with the neural net class I wrote, but most of the structure I took from there. I try to learn it AND, two binary inputs and one output. The problem is, when I train it, the best genome returned always outputs the same (or very similiar) value on all possible combinations of inputs. The most logical place for the error to lie is within the Breed function, take two genomes and create an offspring, add mutation. Or within the CalculateFitness function. But Im unable to find anything in there. In a generation, I start by calculating the fitness which is: 1/(NetOutput - TargetOutput) I have four combinations of training sets, 0 0, 1 0, 0 1, 1 1, I calculate, and add up, the fitness for all four combinations. Then, I do the so called "roulette wheel" to select two random genes that should mate. I randomize a crossover value, and loop through the nets weight. As long as the weight id is below the crossover value, I take the weight from the mother, if it is above, I take it from the father. Then I loop through the new set of weights and see if anything should be mutated, if it should be, I multiply the weight with a random number. Now, the new population is generated and I start all over. Any obvious errors in my routine or is it the code thats wrong? Heres the code for the genetic algorithm class, Ive attached all of it if anyone wants to see anything besides the Breed and Calculatefitness functions.

#include "GANeuralNet.h"

GANeuralNet::GANeuralNet(int NetsInGeneration, int Inputs, int HiddenLayers, int NeuronsInHidden, bool _Debug) {
	NetInputs = Inputs;
	Debug = _Debug;

	Gene* NewGene;
	for(int i = 0; i < NetsInGeneration; ++i) {
		// Create a gene for the net
		NewGene = new Gene(Inputs, HiddenLayers, NeuronsInHidden, 0);
		GenePool.push_back(NewGene);
	}

	srand( (unsigned)time( NULL ) );
}

GANeuralNet::~GANeuralNet() {
	for(unsigned int i = 0; i < GenePool.size(); ++i)
		delete GenePool;
	GenePool.clear();
}

//---------------------------------------
// This function serve as the roulette wheel and return a random gene.
// Higher fitness score of a gene equals a higher probability of it being selected
//
// Input: All the fitness scores added up
//
// Output: Returns the selected genes id
//----------------------------------------

int GANeuralNet::GetRouletteGene(double TotalFitness) {
	// the number to point out a gene
	double Random = TotalFitness*((float)rand()/(float)RAND_MAX);

	for(int j = 0; j < GenePool.size(); ++j) {
		if(j != 0) {
			//cout << Random << endl << GenePool[j]->WheelSlot << endl << GenePool[j-1]->WheelSlot << endl << endl;
			// are we withing the slice?
			if(Random < GenePool[j]->WheelSlot && Random > GenePool[j-1]->WheelSlot) 
				return j;
		}
		else {
			if(Random < GenePool[0]->WheelSlot) 
				return 0;
		}
	}
	cout << "error in roulette.\n";
	return 0;
}

// ------------------------------
// This function do the breeding
//
// Input: The two parent genes
//
// Output: It returns a new gene with crossovered chromosomes from the parents with added mutation
// -------------------------------

Gene* GANeuralNet::Breed(Gene* Parent1, Gene* Parent2) {
	// the determine how many weights we take from each parent
	int CrossoverValue = rand()%(Parent1->Net.GetWeights().size());
	//cout << CrossoverValue << endl;

	vector<float> NewWeights;
	vector<float> p1 = Parent1->Net.GetWeights();
	vector<float> p2 = Parent2->Net.GetWeights();

	// copy over the weights
	for(int i = 0; i < p1.size(); ++i) {
		if(i <= CrossoverValue)
			NewWeights.push_back(p1);
		else
			NewWeights.push_back(p2);
	}

	// mutate
	for(int i = 0; i < NewWeights.size(); ++i) {
		if((rand()%100) < 1) {
			float MutVal = 10.0f*((float)rand()/(float)RAND_MAX)-5.0f;
			NewWeights *= MutVal;

			if(Debug)
				cout << "Weight " << i << " mutated with " << MutVal << endl;
		}
	}


	Gene* NewGene = new Gene;				// make the new gene
	NewGene->Net.SetWeights(NewWeights);	// make it use the new weights
	return NewGene;							// return it
}

// ------------------------------------------------
// This function calculates every genes fitness from a given input, 
// it also assign every gene a "wheelslot" which is used later when we spin the roulette wheel to
// randomly determine two genes that should breed. The wheelslot is the upper limit, if the random roulette wheel
// number is below this (but still above the previous genes slot of course) this gene is selected.
//
// Input: A vector with all the inputs and the desired output
//
// Output: None, it writes the data directly
// --------------------------------------------------

void GANeuralNet::CalculateFitness(vector<float> NetIn, float Target) {
	if(Debug)
		cout << "Input: " << NetIn[0] << " " << NetIn[1] << "  Target: " << Target << endl;

	for(int i = 0; i < GenePool.size(); ++i) {
		if(i != 0) {	// not first one
			GenePool->Fitness += 1/(abs(GenePool->Net.Process(NetIn) - Target));

			if(GenePool->Fitness > MAX_FITNESS)	// max fitness
				GenePool->Fitness = MAX_FITNESS;

			GenePool->WheelSlot = GenePool->Fitness + GenePool[i-1]->WheelSlot;
		}
		else {
			GenePool->Fitness += 1/(abs(GenePool->Net.Process(NetIn) - Target));

			if(GenePool->Fitness > MAX_FITNESS)	// max fitness
				GenePool->Fitness = MAX_FITNESS;

			GenePool->WheelSlot = GenePool->Fitness;
		}
		if(Debug)
			cout << "Gene " << i << "\toutputted " << GenePool->Net.Process(NetIn) << "\tfitness score: " << GenePool->Fitness << endl;
	}
}

// ------------------------------------------------
// This function ties the above functions together, it gets two genes and breed them. All over, until we
// have a population of genes as large as when we started. Then it saves it as the new generation.
//
// Input: None
// 
// Output: None
//--------------------------------------------------

void GANeuralNet::NextGeneration() {
	vector<Gene*> NewGenePool;	// here we will have all the new genomes
	Gene* A = NULL;				// first to mate
	Gene* B = NULL;				// second one
	int a=0, b=0;
	double TotalFitness = 0;	
	vector<int> ChildCount;	// se we know how many children each gene got

	if(Debug) {
		ChildCount.resize(GenePool.size(), 0);
		cout << endl;
	}

	for(int i = 0; i < GenePool.size(); ++i) 
		TotalFitness += GenePool->Fitness;

	for(int i = 0; i < GenePool.size(); ++i) {	// we should end up with the same number of genomes as when we started
		// calculate which two genomes should mate
 		a = GetRouletteGene(TotalFitness);
		b = GetRouletteGene(TotalFitness);

		if(Debug) {
			ChildCount[a]++;
			ChildCount++;
		}

		A = GenePool[ a ];
		B = GenePool;	

		<span class="cpp-comment">// crossover and store in new gene pool</span>
		NewGenePool.push_back(Breed(A, B));
	}
	<span class="cpp-keyword">if</span>(Debug)
		cout &lt;&lt; endl;
	<span class="cpp-comment">// clear old genepool</span>
	<span class="cpp-keyword">for</span>(<span class="cpp-keyword">unsigned</span> <span class="cpp-keyword">int</span> i = <span class="cpp-number">0</span>; i &lt; GenePool.size(); ++i) {
		<span class="cpp-keyword">if</span>(Debug)
			cout &lt;&lt; <span class="cpp-literal">"Gene "</span> &lt;&lt; i &lt;&lt; <span class="cpp-literal">"\tgot "</span> &lt;&lt; ChildCount<span style="font-weight:bold;"> &lt;&lt; <span class="cpp-literal">"\tchildren.\n"</span>;
		<span class="cpp-keyword">delete</span> GenePool<span style="font-weight:bold;">;
	}
	GenePool.clear();

	<span class="cpp-comment">// copy and make it the new generation</span>
	GenePool = NewGenePool;
	<span class="cpp-comment">//cout &lt;&lt; GenePool.size();</span>

	<span class="cpp-keyword">if</span>(Debug)
		cout &lt;&lt; endl;
}

<span class="cpp-comment">// β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”-</span>
<span class="cpp-comment">// This function manages the training</span>
<span class="cpp-comment">//</span>
<span class="cpp-comment">// Input: A vector with all the training sets, a value of how many training runs</span>
<span class="cpp-comment">//</span>
<span class="cpp-comment">// Output: Returns the best neural net after training is done</span>
<span class="cpp-comment">// β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”</span>

NeuralNet GANeuralNet::Train(vector&lt;NetInput&gt; NetIn, <span class="cpp-keyword">int</span> Iterations) {
	<span class="cpp-keyword">double</span> AverageFitness = <span class="cpp-number">0</span>;

	<span class="cpp-keyword">if</span>(NetIn[<span class="cpp-number">0</span>].Input.size() != NetInputs) {
		cout &lt;&lt; <span class="cpp-literal">"Error: Invalid amounts of net inputs.\nReturning first net.\n"</span>;
		<span class="cpp-keyword">return</span> GenePool[<span class="cpp-number">0</span>]-&gt;Net;
	}

	cout &lt;&lt; <span class="cpp-literal">"Training will be run for "</span> &lt;&lt; Iterations &lt;&lt; <span class="cpp-literal">" generations.\n"</span>;
	cout &lt;&lt; <span class="cpp-literal">"Working with "</span> &lt;&lt; NetIn.size() &lt;&lt; <span class="cpp-literal">" number of data sets.\n"</span>;
	getch();

	<span class="cpp-keyword">for</span>(<span class="cpp-keyword">int</span> iter = <span class="cpp-number">0</span>; iter &lt; Iterations; ++iter) {
		<span class="cpp-keyword">if</span>(Debug)
			cout &lt;&lt; <span class="cpp-literal">"———– Generation "</span> &lt;&lt; iter &lt;&lt; <span class="cpp-literal">" β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”\n"</span>;

		<span class="cpp-keyword">for</span>(<span class="cpp-keyword">int</span> in = <span class="cpp-number">0</span>; in &lt; NetIn.size(); ++in) {		<span class="cpp-comment">// once for every kind of input, add up the fitnesses for every kind of input</span>
			CalculateFitness(NetIn[in].Input, NetIn[in].TargetOutput);
			<span class="cpp-keyword">if</span>(Debug)
				cout &lt;&lt; endl;
		}

		<span class="cpp-keyword">for</span>(<span class="cpp-keyword">int</span> i = <span class="cpp-number">0</span>; i &lt; GenePool.size(); ++i) <span class="cpp-comment">// reset fitness</span>
			AverageFitness += GenePool<span style="font-weight:bold;">-&gt;Fitness;
		AverageFitness /= GenePool.size();
		cout &lt;&lt; <span class="cpp-literal">"AverageFitness is "</span> &lt;&lt; AverageFitness &lt;&lt; endl;

		<span class="cpp-comment">// then replace the current generation with better genomes</span>
		NextGeneration();

		<span class="cpp-keyword">if</span>(Debug)
			getch();
	}

	<span class="cpp-comment">// training done! return best performing genome</span>
	<span class="cpp-keyword">return</span> BestGene();
}

NeuralNet GANeuralNet::BestGene() {
	<span class="cpp-keyword">float</span> HighestFitnessSoFar = <span class="cpp-number">0</span>;
	<span class="cpp-keyword">int</span> BestGeneID = <span class="cpp-number">0</span>;
	<span class="cpp-keyword">for</span>(<span class="cpp-keyword">int</span> i = <span class="cpp-number">0</span>; i &lt; GenePool.size(); ++i) {
		<span class="cpp-keyword">if</span>(GenePool<span style="font-weight:bold;">-&gt;Fitness &gt; HighestFitnessSoFar) { <span class="cpp-comment">// this gene is better</span>
			HighestFitnessSoFar = GenePool<span style="font-weight:bold;">-&gt;Fitness;
			BestGeneID = i;
		}
	}

	<span class="cpp-keyword">return</span> GenePool[BestGeneID]-&gt;Net;
}


</pre></div><!–ENDSCRIPT–>

If you want to see even more code, the full source and executable can be downloaded <a href="http://web.telia.com/~u85427399/GANet.rar">here.</a>

I really want to solve this &#111;ne, please help a fellow coder out.

[edit] something went wrong with source tags

<!–EDIT–><span class=editedby><!–/EDIT–>[Edited by - Mizipzor on February 12, 2006 10:09:58 AM]<!–EDIT–></span><!–/EDIT–>

This topic is closed to new replies.

Advertisement