GAFeedForwardNN.cpp
/*
libcudann
Copyright (C) 2011 Luca Donati (lucadonati85@gmail.com)
*/
/*
* GAFeedForwardNN.cpp
*
* Created on: Jan 20, 2011
* Author: donati
*/
#include "GAFeedForwardNN.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <math.h>
#define GAUSSIAN_LIM 3.0f
GAFeedForwardNN::GAFeedForwardNN() {
struct timeval tv;
gettimeofday(&tv, NULL);
srand(tv.tv_usec);
trainingSet = NULL;
testSet = NULL;
net = NULL;
bestNet = NULL;
chromosomes=NULL;
popsize=0;
generations=0;
numberofevaluations=0;
selectionalgorithm=ROULETTE_WHEEL;
pcross=0;
pmut=0;
nhidlayers=0;
maxdimhiddenlayers=0;
}
GAFeedForwardNN::~GAFeedForwardNN() {
delete []chromosomes;
}
//choose the training set
void GAFeedForwardNN::selectTrainingSet(LearningSet & s)
{
trainingSet = &s;
}
//choose the test set. if this is set the error rate is computed on test set instead of training set
void GAFeedForwardNN::selectTestSet(LearningSet & s)
{
testSet = &s;
}
//choose a net to save the best network individual trained so far. mse on test set (or train if no test is specified) is the criterion
void GAFeedForwardNN::selectBestNet(FeedForwardNN & n)
{
bestNet = &n;
}
//initialize the genetic algorithm parameters and create the first population of individuals
void GAFeedForwardNN::init(const int pop,const int gen,const int selection,const int neval,const float pc,const float pm,const int nhid,const int maxdimhid){
popsize=pop;
generations=gen;
selectionalgorithm=selection;
numberofevaluations=neval;
pcross=pc;
pmut=pm;
nhidlayers=nhid;
maxdimhiddenlayers=maxdimhid;
if(popsize<=1){printf("POPULATION SIZE SHOULD BE 2 OR MORE\n");exit(1);}
if(generations<1){printf("AT LEAST ONE GENERATION SHOULD BE EVOLVED\n");exit(1);}
if(selectionalgorithm<0||selectionalgorithm>2){printf("SELECTION ALGORITHM NOT IMPLEMENTED YET\n");exit(1);}
if(numberofevaluations<1){printf("AT LEAST ONE EVALUATION SHOULD BE DONE\n");exit(1);}
if(pcross<0||pcross>1){printf("CROSSOVER PROBABILITY SHOULD BE BETWEEN 0 AND 1\n");exit(1);}
if(pmut<0||pmut>1){printf("MUTATION PROBABILITY SHOULD BE BETWEEN 0 AND 1\n");exit(1);}
if(nhidlayers<0){printf("HIDDEN LAYERS NUMBER CAN'T BE NEGATIVE\n");exit(1);}
if(maxdimhiddenlayers<1){printf("HIDDEN LAYERS SHOULD HAVE AT LEAT 1 NEURON\n");exit(1);}
delete [] chromosomes;
chromosomes=new FloatChromosome[popsize];
//generates the population
for(int i=0;i<popsize;i++){
int chromoLenght=nhidlayers+nhidlayers+2+1;
chromosomes[i].setSize(chromoLenght);
//each value is set randomly
for(int j=0;j<chromoLenght;j++){
float min,max,value;
//dimension of each hidden layer (random int between 1 and maxdimhiddenlayers)
if(j<nhidlayers){
min=1;
max=maxdimhiddenlayers;
value=rand()%(int)(max-min+1)+min;
}
//activation function of each hidden layer (1 for sigmoid, 2 for tanh, random)
else if(j<nhidlayers+nhidlayers+2){
min=1;
max=2;
value=rand()%(int)(max-min+1)+min;
}
//learning rate (random between 0 and 1)
else{
min=0;
max=1;
value=(max-min)*((float)rand()/(RAND_MAX+1.0f))+min;
}
chromosomes[i].setElement(j,value);
}
}
}
//run the genetic algorithm initialized before with some training parameters:
//training location, training algorithm, desired error, max_epochs, epochs_between_reports
//see "FeedForwardNNTrainer" class for more details
//printtype specifies how much verbose will be the execution (PRINT_ALL,PRINT_MIN,PRINT_OFF)
void GAFeedForwardNN::evolve(const int n, const float * params, const int printtype){
if(n<5){printf("TOO FEW PARAMETERS FOR TRAINING\n");exit(1);}
int layers[nhidlayers+2];
int functs[nhidlayers+2];
float learningRate;
float fitnesses[popsize];
float totfitness=0;
float bestFitnessEver=0;
FloatChromosome newpop[popsize];
layers[0]=trainingSet->getNumOfInputsPerInstance();
layers[nhidlayers+1]=trainingSet->getNumOfOutputsPerInstance();
//for each generation
for(int gen=0;gen<generations;gen++){
float bestFitnessGeneration=0;
int bestFitGenIndex=0;
totfitness=0;
printf("GENERATION NUMBER:\t%d\n\n",gen);
//fitness evaluation of each individual
for(int i=0;i<popsize;i++){
printf("\nINDIVIDUAL N:\t%d\n",i);
//decode the chromosome hidden layers sizes
for(int j=0;j<nhidlayers;j++){
layers[j+1]=chromosomes[i].getElement(j);
}
//decode the chromosome activation functions for each layer
for(int j=0;j<nhidlayers+2;j++){
functs[j]=chromosomes[i].getElement(j+nhidlayers);
}
//decode the chromosome learning rate
learningRate=chromosomes[i].getElement(nhidlayers+nhidlayers+2);
float medium=0;
FeedForwardNN mseT;
//do a number of evaluations with different weights and average the results
for(int n=0;n<numberofevaluations;n++){
//choose what to print based on user's choice
int print=PRINT_ALL;
if(printtype==PRINT_MIN){
if(n==0)
print=PRINT_MIN;
else
print=PRINT_OFF;
}
if(printtype==PRINT_OFF)
print=PRINT_OFF;
//decode the chromosome into a real network
FeedForwardNN net(nhidlayers+2,layers,functs);
FeedForwardNNTrainer trainer;
trainer.selectTrainingSet(*trainingSet);
if(testSet!=NULL){
trainer.selectTestSet(*testSet);
}
trainer.selectNet(net);
trainer.selectBestMSETestNet(mseT);
float par[]={params[0],params[1],params[2],params[3],params[4],learningRate,0,SHUFFLE_ON,ERROR_TANH};
//do the training of the net and evaluate is MSE error
medium+=trainer.train(9,par,print)/float(numberofevaluations);
}
//the fitness is computed as the inverse of the MSE
fitnesses[i]=1.0f/medium;
printf("FITNESS:\t%.2f\n\n",fitnesses[i]);
//updates the best individual of the generation
if(fitnesses[i]>bestFitnessGeneration){bestFitnessGeneration=fitnesses[i];bestFitGenIndex=i;}
//if this is the best fitness ever it store the network in bestNet
if(bestNet!=NULL)
if(fitnesses[i]>bestFitnessEver){*bestNet=mseT;bestFitnessEver=fitnesses[i];}
totfitness+=fitnesses[i];
}
//the best individual is always carried to the next generation
newpop[0]=chromosomes[bestFitGenIndex];
//generate the new population
for(int i=1;i<popsize;i++){
//selection
int firstmate=0,secondmate=0;
//first mate
switch(selectionalgorithm){
case ROULETTE_WHEEL: firstmate=rouletteWheel(popsize,fitnesses); break;
case TOURNAMENT_SELECTION: firstmate=tournament(popsize,fitnesses,popsize/5+1); break;
default: printf("SELECTION ALGORITHM NOT IMPLEMENTED YET\n");exit(1);break;
}
//second mate
do{
switch(selectionalgorithm){
case ROULETTE_WHEEL: secondmate=rouletteWheel(popsize,fitnesses); break;
case TOURNAMENT_SELECTION: secondmate=tournament(popsize,fitnesses,popsize/5+1); break;
default: printf("SELECTION ALGORITHM NOT IMPLEMENTED YET\n");exit(1);break;
}
}while(firstmate==secondmate);
FloatChromosome child;
//do the crossover
child=crossover(chromosomes[firstmate],chromosomes[secondmate],pcross);
//and the mutation
child=mutation(child,pmut,maxdimhiddenlayers,nhidlayers);
//and put the child in the new generation
newpop[i]=child;
}
//copy the new generation over the older one, wich is the one we will still use
for(int i=0;i<popsize;i++){
chromosomes[i]=newpop[i];
}
}
}
//performs crossover between a chromosome and a mate, and return the result
FloatChromosome crossover(const FloatChromosome & first, const FloatChromosome & second, const float pcross){
float roll;
FloatChromosome ret=first;
FloatChromosome app=second;
for(int i=0;i<ret.getSize();i++){
roll=(float)rand()/(RAND_MAX+1.0f);
if(roll<pcross){
ret.setElement(i,app.getElement(i));
}
}
return ret;
}
//performs mutation on a chromosome, and return the result
FloatChromosome mutation(const FloatChromosome & first, const float pmut, const int maxdimhiddenlayers,const int nhidlayers){
float roll;
FloatChromosome ret=first;
for(int i=0;i<ret.getSize();i++){
roll=(float)rand()/(RAND_MAX+1.0f);
if(roll<pmut){
if(i<nhidlayers){
float x1,x2,y1;
x1=(float)rand()/(RAND_MAX+1.0f);
x2=(float)rand()/(RAND_MAX+1.0f);
y1 = sqrt(-2*log(x1))*cos(2*M_PI*x2);
if(y1>GAUSSIAN_LIM)y1=GAUSSIAN_LIM;
if(y1<-GAUSSIAN_LIM)y1=-GAUSSIAN_LIM;
//TODO attenzione non converga sui bordi
ret.setElement(i,(int)(ret.getElement(i)+y1*(float)maxdimhiddenlayers/(2.0f*GAUSSIAN_LIM)));
float val=ret.getElement(i);
if(val<1)ret.setElement(i,1.0f);
if(val>maxdimhiddenlayers)ret.setElement(i,maxdimhiddenlayers);
}
else if(i<nhidlayers+nhidlayers+2){
if((int)(ret.getElement(i))==1)
ret.setElement(i,2.0f);
else
ret.setElement(i,1.0f);
}
else{
float x1,x2,y1;
x1=(float)rand()/(RAND_MAX+1.0f);
x2=(float)rand()/(RAND_MAX+1.0f);
y1 = sqrt(-2*log(x1))*cos(2*M_PI*x2);
if(y1>GAUSSIAN_LIM)y1=GAUSSIAN_LIM;
if(y1<-GAUSSIAN_LIM)y1=-GAUSSIAN_LIM;
//TODO attenzione non converga sui bordi
ret.setElement(i,(ret.getElement(i)+y1/(2.0f*GAUSSIAN_LIM)));
float val=ret.getElement(i);
if(val<0.0f)ret.setElement(i,0.0f);
if(val>1.0f)ret.setElement(i,1.0f);
}
}
}
return ret;
}
//roulette wheel selection
int rouletteWheel(const int size, const float * fitnesses){
//computes the total fitness
float totfitness=0;
for(int i=0;i<size;i++){
totfitness+=fitnesses[i];
}
//spin the ball between 0 and that total fitness
float spin=totfitness*((float)rand()/(RAND_MAX+1.0f));
int chosen=0;
//pick the relative individual
for(chosen=0;chosen<size;chosen++){
spin-=fitnesses[chosen];
if(spin<0)break;
}
//and returns it
return chosen;
}
//tournament selection
int tournament(const int size, const float * fitnesses, const int toursize){
int picks[toursize];
float bestfitness=0;
int bestindex=0;
//fills the tournament
for(int i=0;i<toursize;i++){
int pick;
bool alreadyPicked=false;
do{
//with random individuals
alreadyPicked=false;
pick=rand()%(int)(size);
for(int j=0;j<i;j++){
if(picks[j]==pick){
alreadyPicked=true;
break;
}
}
//checking they are picked at most only once each
}while(alreadyPicked==true);
picks[i]=pick;
//the individual with the best fitness wins the tournament
if(fitnesses[i]>bestfitness){
bestfitness=fitnesses[i];
bestindex=picks[i];
}
}
//and is selected
return bestindex;
}