Generates neural net instances through genetic algorithm for use in task learning by m3pi. Reward determined by detection of black areas on arena floor, or by amount of area explored in limited time. Due to memory size, limited # of nets.

Dependencies:   Ping m3pimaze mbed

Code/CGenAlg.cpp

Committer:
parkbanks
Date:
2015-04-23
Revision:
0:ac93d9db8dbd

File content as of revision 0:ac93d9db8dbd:

#include "CGenAlg.h"

//const unsigned int numElite = 1;
//const unsigned int numCopies = 2;

// constructor
// set up population with random floats
CGenAlg::CGenAlg(int popsize, float MutRat, float CrossRat, int numweights) : m_iPopSize(popsize),
                                                                                m_dMutationRate(MutRat),
                                                                                m_dCrossoverRate(CrossRat),
                                                                                m_iChromoLength(numweights),
                                                                                m_dTotalFitness(0),
                                                                                m_cGeneration(0),
                                                                                m_iFittestGenome(0),
                                                                                m_dBestFitness(0),
                                                                                m_dWorstFitness(99999)
{
    //initialise population with randomly weighted chromosomes, set fitness to 0
    for (int i=0; i<m_iPopSize; ++i)
    {
        m_vecPop.push_back(SGenome());

        for (int j=0; j<m_iChromoLength; ++j)
        {
            m_vecPop[i].vecWeights.push_back(RandomClamped());
        }
    }
}

// Mutates a chromosome by perturbing its weights, m_dMutationRate given in main
void CGenAlg::Mutate(vector<float> &chromo)
{
    //traverse the chromosome and mutate each weight dependent
    //on the mutation rate
    for (int i=0; i<chromo.size(); ++i)
    {
        //do we perturb this weight?
        if (RandFloat() < m_dMutationRate)
        {
            //add or subtract a small value to the weight
            chromo[i] += (RandomClamped() * 0.3);
        }
    }
}

// returns a chromo based on roulette wheel sampling
SGenome CGenAlg::GetChromoRoulette()
{
    //generate a random number between 0 & total fitness count
    float Slice = (float)(RandFloat() * m_dTotalFitness);

    //this will be set to the chosen chromosome
    SGenome TheChosenOne;
    
    //go through the chromosones adding up the fitness so far
    float FitnessSoFar = 0;
    
    for (int i=0; i<m_iPopSize; ++i)
    {
        FitnessSoFar += m_vecPop[i].dFitness;
        
        //if the fitness so far > random number return the chromo at 
        //this point
        if (FitnessSoFar >= Slice)
        {
            TheChosenOne = m_vecPop[i];

      break;
        }
        
    }

    return TheChosenOne;
}

// given parents and storage for the offspring this method performs
// crossover according to the GAs crossover rate
void CGenAlg::Crossover(const vector<float> &mum,
                        const vector<float> &dad,
                        vector<float>       &baby1,
                        vector<float>       &baby2)
{
    //just return parents as offspring dependent on the rate
    //or if parents are the same
    if ( (RandFloat() > m_dCrossoverRate) || (mum == dad)) 
    {
        baby1 = mum;
        baby2 = dad;

        return;
    }

    //determine a crossover point
    int cp = RandInt(0, m_iChromoLength - 1);

    //create the offspring
    for (int i=0; i<cp; ++i)
    {
        baby1.push_back(mum[i]);
        baby2.push_back(dad[i]);
    }

    int i;
    for (i=cp; i<mum.size(); ++i)
    {
        baby1.push_back(dad[i]);
        baby2.push_back(mum[i]);
    }
    
    
    return;
}


// takes a population of chromosones and runs the algorithm through one cycle.
//  Returns a new population of chromosones.
vector<SGenome> CGenAlg::Epoch(vector<SGenome> &old_pop)
{
    //assign the given population to the classes population
  m_vecPop = old_pop;

  //reset the appropriate variables
  Reset();

  //sort the population (for scaling and elitism)
  sort(m_vecPop.begin(), m_vecPop.end());

  //calculate best, worst, average and total fitness
    CalculateBestWorstAvTot();
  
  //create a temporary vector to store new chromosones
    vector <SGenome> vecNewPop;

    //Now to add a little elitism we shall add in some copies of the
    //fittest genomes. Make sure we add an EVEN number or the roulette
  //wheel sampling will crash

    if (m_vecPop[1].dFitness > 130 && m_vecPop[2].dFitness > 130)//2 copies of 1 elite
    {
        GrabNBest(2, 2, vecNewPop);
    }
    else if (m_vecPop[1].dFitness > 130)//2 copies of 1 elite
    {
        GrabNBest(1, 4, vecNewPop);
    }
    

    //now we enter the GA loop
    
    //repeat until a new population is generated
    while (vecNewPop.size() < m_iPopSize)
    {
        //grab two chromosones
        SGenome mum = GetChromoRoulette();
        SGenome dad = GetChromoRoulette();

        //create some offspring via crossover
        vector<float>      baby1, baby2;

        Crossover(mum.vecWeights, dad.vecWeights, baby1, baby2);

        //now we mutate
        Mutate(baby1);
        Mutate(baby2);

        //now copy into vecNewPop population
        vecNewPop.push_back(SGenome(baby1, 0));
        vecNewPop.push_back(SGenome(baby2, 0));
    }

    //finished so assign new pop back into m_vecPop
    m_vecPop = vecNewPop;

    return m_vecPop;
}

// Elitism, inserts NumCopies of NBest fittest genomes into a pop vector
void CGenAlg::GrabNBest(int             NBest,
                        const int         NumCopies,
                        vector<SGenome> &Pop)
{
  //add the required amount of copies of the n most fittest 
    //to the supplied vector
    while(NBest--)
    {
        for (int i=0; i<NumCopies; ++i)
        {
            Pop.push_back(m_vecPop[(m_iPopSize - 1) - NBest]);
      }
    }
}

//  calculates fittest and weakest genomes
void CGenAlg::CalculateBestWorstAvTot()
{
    m_dTotalFitness = 0;
    
    float HighestSoFar = 0;
    float LowestSoFar  = 99999;
    
    for (int i=0; i<m_iPopSize; ++i)
    {
        //update fittest if necessary
        if (m_vecPop[i].dFitness > HighestSoFar)
        {
            HighestSoFar     = m_vecPop[i].dFitness;
            
            m_iFittestGenome = i;

            m_dBestFitness   = HighestSoFar;
        }
        
        //update worst if necessary
        if (m_vecPop[i].dFitness < LowestSoFar)
        {
            LowestSoFar = m_vecPop[i].dFitness;
            
            m_dWorstFitness = LowestSoFar;
        }
        
        m_dTotalFitness += m_vecPop[i].dFitness;
        
        
    }//next chromosome
}

void CGenAlg::Reset()
{
    m_dTotalFitness     = 0;
    m_dBestFitness      = 0;
    m_dWorstFitness     = 99999;
}