神经网络深入 ,源码
来源:互联网 发布:惠普手机打印软件 编辑:程序博客网 时间:2024/06/07 17:48
神经网络深入
源 码
说明:以下为与《神经网络深入》连载1-8相关的NEAT(神经网络拓扑演化)程序全部C++语言源码,不含头文件。这是《游戏编程中的人工智能技术》一书所附CD中有关第11章NEAT的全部源码,仅供已阅读《神经网络深入》连载1-8的读者深入阅读参考使用,我在源码中未添加任何中文解释,而且今后也不会为其中内容进行解释、答疑,甚至不关心是否有读者“到此一游”过。但可以肯定代码不会有差错,我已实际使用这些源文件编译过(用Borland C++,V3),完全能通过,并生成能正确运行的执行程序。
—— 目 录 ——
一共14个.cpp文件, 以下依次给出它们的源码
源 码
1. C2DMatrix.cpp
#include "C2DMatrix.h"
/////////////////////////////////////////////////////////////////////
//
// Matrix functions
//
/////////////////////////////////////////////////////////////////////
//create an identity matrix
void C2DMatrix::Identity()
{
m_Matrix._11 = 1; m_Matrix._12 = 0; m_Matrix._13 = 0;
m_Matrix._21 = 0; m_Matrix._22 = 1; m_Matrix._23 = 0;
m_Matrix._31 = 0; m_Matrix._32 = 0; m_Matrix._33 = 1;
}
//create a transformation matrix
void C2DMatrix::Translate(double x, double y)
{
S2DMatrix mat;
mat._11 = 1; mat._12 = 0; mat._13 = 0;
mat._21 = 0; mat._22 = 1; mat._23 = 0;
mat._31 = x; mat._32 = y; mat._33 = 1;
//and multiply
S2DMatrixMultiply(mat);
}
//create a scale matrix
void C2DMatrix::Scale(double xScale, double yScale)
{
S2DMatrix mat;
mat._11 = xScale; mat._12 = 0; mat._13 = 0;
mat._21 = 0; mat._22 = yScale; mat._23 = 0;
mat._31 = 0; mat._32 = 0; mat._33 = 1;
//and multiply
S2DMatrixMultiply(mat);
}
//create a rotation matrix
void C2DMatrix::Rotate(double rot)
{
S2DMatrix mat;
double Sin = sin(rot);
double Cos = cos(rot);
mat._11 = Cos; mat._12 = Sin; mat._13 = 0;
mat._21 = -Sin; mat._22 = Cos; mat._23 = 0;
mat._31 = 0; mat._32 = 0;mat._33 = 1;
//and multiply
S2DMatrixMultiply(mat);
}
2.CController.cpp
#include "CController.h"
//these hold the geometry of the sweepers and the mines
const int NumSweeperVerts = 16;
const SPoint sweeper[NumSweeperVerts] = {SPoint(-1, -1),
SPoint(-1, 1),
SPoint(-0.5, 1),
SPoint(-0.5, -1),
SPoint(0.5, -1),
SPoint(1, -1),
SPoint(1, 1),
SPoint(0.5, 1),
SPoint(-0.5, -0.5),
SPoint(0.5, -0.5),
SPoint(-0.5, 0.5),
SPoint(-0.25, 0.5),
SPoint(-0.25, 1.75),
SPoint(0.25, 1.75),
SPoint(0.25, 0.5),
SPoint(0.5, 0.5)};
const int NumMineVerts = 4;
const SPoint mine[NumMineVerts] = {SPoint(-1, -1),
SPoint(-1, 1),
SPoint(1, 1),
SPoint(1, -1)};
const int NumObjectVerts = 42;
const SPoint objects[NumObjectVerts] = {SPoint(80, 60),
SPoint(200,60),
SPoint(200,60),
SPoint(200,100),
SPoint(200,100),
SPoint(160,100),
SPoint(160,100),
SPoint(160,200),
SPoint(160,200),
SPoint(80,200),
SPoint(80,200),
SPoint(80,60),
SPoint(250,100),
SPoint(300,40),
SPoint(300,40),
SPoint(350,100),
SPoint(350,100),
SPoint(250, 100),
SPoint(220,180),
SPoint(320,180),
SPoint(320,180),
SPoint(320,300),
SPoint(320,300),
SPoint(220,300),
SPoint(220,300),
SPoint(220,180),
SPoint(12,15),
SPoint(380, 15),
SPoint(380,15),
SPoint(380,360),
SPoint(380,360),
SPoint(12,360),
SPoint(12,360),
SPoint(12,340),
SPoint(12,340),
SPoint(100,290),
SPoint(100,290),
SPoint(12,240),
SPoint(12,240),
SPoint(12,15)};
//---------------------------------------constructor---------------------
//
// initilaize the sweepers, their brains and the GA factory
//
//-----------------------------------------------------------------------
CController::CController(HWND hwndMain,
int cxClient,
int cyClient): m_NumSweepers(CParams::iNumSweepers),
m_bFastRender(false),
m_bRenderBest(false),
m_iTicks(0),
m_hwndMain(hwndMain),
m_hwndInfo(NULL),
m_iGenerations(0),
m_cxClient(cxClient),
m_cyClient(cyClient),
m_iViewThisSweeper(0)
{
//let's create the mine sweepers
for (int i=0; i<m_NumSweepers; ++i)
{
m_vecSweepers.push_back(CMinesweeper());
}
//and the vector of sweepers which will hold the best performing sweeprs
for (i=0; i<CParams::iNumBestSweepers; ++i)
{
m_vecBestSweepers.push_back(CMinesweeper());
}
m_pPop = new Cga(CParams::iNumSweepers,
CParams::iNumInputs,
CParams::iNumOutputs,
hwndMain,
10,10);
//create the phenotypes
vector<CNeuralNet*> pBrains = m_pPop->CreatePhenotypes();
//assign the phenotypes
for (i=0; i<m_NumSweepers; i++)
{
m_vecSweepers[i].InsertNewBrain(pBrains[i]);
}
//create a pen for the graph drawing
m_BluePen = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
m_RedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
m_GreenPen = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));
m_GreyPenDotted = CreatePen(PS_DOT, 1, RGB(100, 100, 100));
m_RedPenDotted = CreatePen(PS_DOT, 1, RGB(200, 0, 0));
m_OldPen = NULL;
//and the brushes
m_BlueBrush = CreateSolidBrush(RGB(0,0,244));
m_RedBrush = CreateSolidBrush(RGB(150,0,0));
//fill the vertex buffers
for (i=0; i<NumSweeperVerts; ++i)
{
m_SweeperVB.push_back(sweeper[i]);
}
for (i=0; i<NumObjectVerts; ++i)
{
m_ObjectsVB.push_back(objects[i]);
}
}
//--------------------------------------destructor-------------------------------------
//
//--------------------------------------------------------------------------------------
CController::~CController()
{
if (m_pPop)
{
delete m_pPop;
}
DeleteObject(m_BluePen);
DeleteObject(m_RedPen);
DeleteObject(m_GreenPen);
DeleteObject(m_OldPen);
DeleteObject(m_GreyPenDotted);
DeleteObject(m_RedPenDotted);
DeleteObject(m_BlueBrush);
DeleteObject(m_RedBrush);
}
//-------------------------------------Update---------------------------------------
//
// This is the main workhorse. The entire simulation is controlled from here.
//
//--------------------------------------------------------------------------------------
bool CController::Update()
{
//run the sweepers through NUM_TICKS amount of cycles. During this loop each
//sweepers NN is constantly updated with the appropriate information from its
//surroundings. The output from the NN is obtained and the sweeper is moved.
if (m_iTicks++ < CParams::iNumTicks)
{
for (int i=0; i<m_NumSweepers; ++i)
{
//update the NN and position
if (!m_vecSweepers[i].Update(m_ObjectsVB))
{
//error in processing the neural net
MessageBox(m_hwndMain, "Wrong amount of NN inputs!", "Error", MB_OK);
return false;
}
}
//update the NNs of the last generations best performers
if (m_iGenerations > 0)
{
for (int i=0; i<m_vecBestSweepers.size(); ++i)
{
//update the NN and position
if (!m_vecBestSweepers[i].Update(m_ObjectsVB))
{
//error in processing the neural net
MessageBox(m_hwndMain, "Wrong amount of NN inputs!", "Error", MB_OK);
return false;
}
}
}
}
//We have completed another generation so now we need to run the GA
else
{
//first add up each sweepers fitness scores. (remember for this task
//there are many different sorts of penalties the sweepers may incur
//and each one has a coefficient)
for (int swp=0; swp<m_vecSweepers.size(); ++swp)
{
m_vecSweepers[swp].EndOfRunCalculations();
}
//increment the generation counter
++m_iGenerations;
//reset cycles
m_iTicks = 0;
//perform an epoch and grab the new brains
vector<CNeuralNet*> pBrains = m_pPop->Epoch(GetFitnessScores());
//insert the new brains back into the sweepers and reset their
//positions
for (int i=0; i<m_NumSweepers; ++i)
{
m_vecSweepers[i].InsertNewBrain(pBrains[i]);
m_vecSweepers[i].Reset();
}
//grab the NNs of the best performers from the last generation
vector<CNeuralNet*> pBestBrains = m_pPop->GetBestPhenotypesFromLastGeneration();
//put them into our record of the best sweepers
for (i=0; i<m_vecBestSweepers.size(); ++i)
{
m_vecBestSweepers[i].InsertNewBrain(pBestBrains[i]);
m_vecBestSweepers[i].Reset();
}
//this will call WM_PAINT which will render our scene
InvalidateRect(m_hwndInfo, NULL, TRUE);
UpdateWindow(m_hwndInfo);
}
return true;
}
//---------------------------------- RenderNetworks ----------------------
//
// Renders the best four phenotypes from the previous generation
//------------------------------------------------------------------------
void CController::RenderNetworks(HDC &surface)
{
if (m_iGenerations < 1)
{
return;
}
//draw the network of the best 4 genomes. First get the dimensions of the
//info window
RECT rect;
GetClientRect(m_hwndInfo, &rect);
int cxInfo = rect.right;
int cyInfo = rect.bottom;
//now draw the 4 best networks
m_vecBestSweepers[0].DrawNet(surface, 0, cxInfo/2, cyInfo/2, 0);
m_vecBestSweepers[1].DrawNet(surface, cxInfo/2, cxInfo, cyInfo/2, 0);
m_vecBestSweepers[2].DrawNet(surface, 0, cxInfo/2, cyInfo, cyInfo/2);
m_vecBestSweepers[3].DrawNet(surface, cxInfo/2, cxInfo, cyInfo, cyInfo/2);
}
//------------------------------------Render()--------------------------------------
//
//----------------------------------------------------------------------------------
void CController::Render(HDC &surface)
{
//do not render if running at accelerated speed
if (!m_bFastRender)
{
string s = "Generation: " + itos(m_iGenerations);
TextOut(surface, 5, 0, s.c_str(), s.size());
//select in the blue pen
m_OldPen = (HPEN)SelectObject(surface, m_BluePen);
if (m_bRenderBest)
{
//render the best sweepers memory cells
m_vecBestSweepers[m_iViewThisSweeper].Render(surface);
//render the best sweepers from the last generation
RenderSweepers(surface, m_vecBestSweepers);
//render the best sweepers sensors
RenderSensors(surface, m_vecBestSweepers);
}
else
{
//render the sweepers
RenderSweepers(surface, m_vecSweepers);
}
SelectObject(surface, m_OldPen);
//render the objects
for (int i=0; i<NumObjectVerts; i+=2)
{
MoveToEx(surface, m_ObjectsVB[i].x, m_ObjectsVB[i].y, NULL);
LineTo(surface, m_ObjectsVB[i+1].x, m_ObjectsVB[i+1].y);
}
}//end if
else
{
PlotStats(surface);
RECT sr;
sr.top = m_cyClient-50;
sr.bottom = m_cyClient;
sr.left = 0;
sr.right = m_cxClient;
//render the species chart
m_pPop->RenderSpeciesInfo(surface, sr);
}
}
//------------------------- RenderSweepers -------------------------------
//
// given a vector of sweepers this function renders them to the screen
//------------------------------------------------------------------------
void CController::RenderSweepers(HDC &surface, vector<CMinesweeper> &sweepers)
{
for (int i=0; i<sweepers.size(); ++i)
{
//if they have crashed into an obstacle draw
if ( sweepers[i].Collided())
{
SelectObject(surface, m_RedPen);
}
else
{
SelectObject(surface, m_BluePen);
}
//grab the sweeper vertices
vector<SPoint> sweeperVB = m_SweeperVB;
//transform the vertex buffer
sweepers[i].WorldTransform(sweeperVB, sweepers[i].Scale());
//draw the sweeper left track
MoveToEx(surface, (int)sweeperVB[0].x, (int)sweeperVB[0].y, NULL);
for (int vert=1; vert<4; ++vert)
{
LineTo(surface, (int)sweeperVB[vert].x, (int)sweeperVB[vert].y);
}
LineTo(surface, (int)sweeperVB[0].x, (int)sweeperVB[0].y);
//draw the sweeper right track
MoveToEx(surface, (int)sweeperVB[4].x, (int)sweeperVB[4].y, NULL);
for (vert=5; vert<8; ++vert)
{
LineTo(surface, (int)sweeperVB[vert].x, (int)sweeperVB[vert].y);
}
LineTo(surface, (int)sweeperVB[4].x, (int)sweeperVB[4].y);
MoveToEx(surface, (int)sweeperVB[8].x, (int)sweeperVB[8].y, NULL);
LineTo(surface, (int)sweeperVB[9].x, (int)sweeperVB[9].y);
MoveToEx(surface, (int)sweeperVB[10].x, (int)sweeperVB[10].y, NULL);
for (vert=11; vert<16; ++vert)
{
LineTo(surface, (int)sweeperVB[vert].x, (int)sweeperVB[vert].y);
}
}//next sweeper
}
//----------------------------- RenderSensors ----------------------------
//
// renders the sensors of a given vector of sweepers
//------------------------------------------------------------------------
void CController::RenderSensors(HDC &surface, vector<CMinesweeper> &sweepers)
{
//render the sensors
for (int i=0; i<sweepers.size(); ++i)
{
//grab each sweepers sensor data
vector<SPoint> tranSensors = sweepers[i].Sensors();
vector<double> SensorReadings = sweepers[i].SensorReadings();
vector<double> MemoryReadings = sweepers[i].MemoryReadings();
for (int sr=0; sr<tranSensors.size(); ++sr)
{
if (SensorReadings[sr] > 0)
{
SelectObject(surface, m_RedPen);
}
else
{
SelectObject(surface, m_GreyPenDotted);
}
//make sure we clip the drawing of the sensors or we will get
//unwanted artifacts appearing
if (!((fabs(sweepers[i].Position().x - tranSensors[sr].x) >
(CParams::dSensorRange+1))||
(fabs(sweepers[i].Position().y - tranSensors[sr].y) >
(CParams::dSensorRange+1))))
{
MoveToEx(surface,
(int)sweepers[i].Position().x,
(int)sweepers[i].Position().y,
NULL);
LineTo(surface, (int)tranSensors[sr].x, (int)tranSensors[sr].y);
//render the cell sensors
RECT rect;
rect.left = (int)tranSensors[sr].x - 2;
rect.right = (int)tranSensors[sr].x + 2;
rect.top = (int)tranSensors[sr].y - 2;
rect.bottom= (int)tranSensors[sr].y + 2;
if (MemoryReadings[sr] < 0)
{
FillRect(surface, &rect, m_BlueBrush);
}
else
{
FillRect(surface, &rect, m_RedBrush);
}
}
}
}
}
//--------------------------PlotStats-------------------------------------
//
// Given a surface to draw on this function displays some simple stats
//------------------------------------------------------------------------
void CController::PlotStats(HDC surface)const
{
string s = "Generation: " + itos(m_iGenerations);
TextOut(surface, 5, 25, s.c_str(), s.size());
s = "Num Species: " + itos(m_pPop->NumSpecies());
TextOut(surface, 5, 45, s.c_str(), s.size());
s = "Best Fitness so far: " + ftos(m_pPop->BestEverFitness());
TextOut(surface, 5, 5, s.c_str(), s.size());
}
//------------------------------- GetFitnessScores -----------------------
//
// returns a std::vector containing the genomes fitness scores
//------------------------------------------------------------------------
vector<double> CController::GetFitnessScores()const
{
vector<double> scores;
for (int i=0; i<m_vecSweepers.size(); ++i)
{
scores.push_back(m_vecSweepers[i].Fitness());
}
return scores;
}
3. Cga.cpp:
#include "Cga.h"
//-------------------------------------------------------------------------
// this constructor creates a base genome from supplied values and creates
// a population of 'size' similar (same topology, varying weights) genomes
//-------------------------------------------------------------------------
Cga::Cga(int size,
int inputs,
int outputs,
HWND hwnd,
int cx,
int cy): m_iPopSize(size),
m_iGeneration(0),
m_pInnovation(NULL),
m_iNextGenomeID(0),
m_iNextSpeciesID(0),
m_iFittestGenome(0),
m_dBestEverFitness(0),
m_dTotFitAdj(0),
m_dAvFitAdj(0),
m_hwnd(hwnd),
cxClient(cx),
cyClient(cy)
{
//create the population of genomes
for (int i=0; i<m_iPopSize; ++i)
{
m_vecGenomes.push_back(CGenome(m_iNextGenomeID++, inputs, outputs));
}
//create the innovation list. First create a minimal genome
CGenome genome(1, inputs, outputs);
//create the innovations
m_pInnovation = new CInnovation(genome.Genes(), genome.Neurons());
//create the network depth lookup table
vecSplits = Split(0, 1, 0);
}
//------------------------------------- dtor -----------------------------
//
//------------------------------------------------------------------------
Cga::~Cga()
{
if (m_pInnovation)
{
delete m_pInnovation;
m_pInnovation = NULL;
}
}
//-------------------------------CreatePhenotypes-------------------------
//
// cycles through all the members of the population and creates their
// phenotypes. Returns a vector containing pointers to the new phenotypes
//-------------------------------------------------------------------------
vector<CNeuralNet*> Cga::CreatePhenotypes()
{
vector<CNeuralNet*> networks;
for (int i=0; i<m_iPopSize; i++)
{
//calculate max network depth
int depth = CalculateNetDepth(m_vecGenomes[i]);
//create new phenotype
CNeuralNet* net = m_vecGenomes[i].CreatePhenotype(depth);
networks.push_back(net);
}
return networks;
}
//-------------------------- CalculateNetDepth ---------------------------
//
// searches the lookup table for the dSplitY value of each node in the
// genome and returns the depth of the network based on this figure
//------------------------------------------------------------------------
int Cga::CalculateNetDepth(const CGenome &gen)
{
int MaxSoFar = 0;
for (int nd=0; nd<gen.NumNeurons(); ++nd)
{
for (int i=0; i<vecSplits.size(); ++i)
{
if ((gen.SplitY(nd) == vecSplits[i].val) &&
(vecSplits[i].depth > MaxSoFar))
{
MaxSoFar = vecSplits[i].depth;
}
}
}
return MaxSoFar + 2;
}
//-----------------------------------AddNeuronID----------------------------
//
// just checks to see if a node ID has already been added to a vector of
// nodes. If not then the new ID gets added. Used in Crossover.
//------------------------------------------------------------------------
void Cga::AddNeuronID(const int nodeID, vector<int> &vec)
{
for (int i=0; i<vec.size(); i++)
{
if (vec[i] == nodeID)
{
//already added
return;
}
}
vec.push_back(nodeID);
return;
}
//------------------------------------- Epoch ----------------------------
//
// This function performs one epoch of the genetic algorithm and returns
// a vector of pointers to the new phenotypes
//------------------------------------------------------------------------
vector<CNeuralNet*> Cga::Epoch(const vector<double> &FitnessScores)
{
//first check to make sure we have the correct amount of fitness scores
if (FitnessScores.size() != m_vecGenomes.size())
{
MessageBox(NULL,"Cga::Epoch(scores/ genomes mismatch)!","Error", MB_OK);
}
//reset appropriate values and kill off the existing phenotypes and
//any poorly performing species
ResetAndKill();
//update the genomes with the fitnesses scored in the last run
for (int gen=0; gen<m_vecGenomes.size(); ++gen)
{
m_vecGenomes[gen].SetFitness(FitnessScores[gen]);
}
//sort genomes and keep a record of the best performers
SortAndRecord();
//separate the population into species of similar topology, adjust
//fitnesses and calculate spawn levels
SpeciateAndCalculateSpawnLevels();
//this will hold the new population of genomes
vector<CGenome> NewPop;
//request the offspring from each species. The number of children to
//spawn is a double which we need to convert to an int.
int NumSpawnedSoFar = 0;
CGenome baby;
//now to iterate through each species selecting offspring to be mated and
//mutated
for (int spc=0; spc<m_vecSpecies.size(); ++spc)
{
//because of the number to spawn from each species is a double
//rounded up or down to an integer it is possible to get an overflow
//of genomes spawned. This statement just makes sure that doesn't
//happen
if (NumSpawnedSoFar < CParams::iNumSweepers)
{
//this is the amount of offspring this species is required to
// spawn. Rounded simply rounds the double up or down.
int NumToSpawn = Rounded(m_vecSpecies[spc].NumToSpawn());
bool bChosenBestYet = false;
while (NumToSpawn--)
{
//first grab the best performing genome from this species and transfer
//to the new population without mutation. This provides per species
//elitism
if (!bChosenBestYet)
{
baby = m_vecSpecies[spc].Leader();
bChosenBestYet = true;
}
else
{
//if the number of individuals in this species is only one
//then we can only perform mutation
if (m_vecSpecies[spc].NumMembers() == 1)
{
//spawn a child
baby = m_vecSpecies[spc].Spawn();
}
//if greater than one we can use the crossover operator
else
{
//spawn1
CGenome g1 = m_vecSpecies[spc].Spawn();
if (RandFloat() < CParams::dCrossoverRate)
{
//spawn2, make sure it's not the same as g1
CGenome g2 = m_vecSpecies[spc].Spawn();
//number of attempts at finding a different genome
int NumAttempts = 5;
while ( (g1.ID() == g2.ID()) && (NumAttempts--) )
{
g2 = m_vecSpecies[spc].Spawn();
}
if (g1.ID() != g2.ID())
{
baby = Crossover(g1, g2);
}
}
else
{
baby = g1;
}
}
++m_iNextGenomeID;
baby.SetID(m_iNextGenomeID);
//now we have a spawned child lets mutate it! First there is the
//chance a neuron may be added
if (baby.NumNeurons() < CParams::iMaxPermittedNeurons)
{
baby.AddNeuron(CParams::dChanceAddNode,
*m_pInnovation,
CParams::iNumTrysToFindOldLink);
}
//now there's the chance a link may be added
baby.AddLink(CParams::dChanceAddLink,
CParams::dChanceAddRecurrentLink,
*m_pInnovation,
CParams::iNumTrysToFindLoopedLink,
CParams::iNumAddLinkAttempts);
//mutate the weights
baby.MutateWeights(CParams::dMutationRate,
CParams::dProbabilityWeightReplaced,
CParams::dMaxWeightPerturbation);
baby.MutateActivationResponse(CParams::dActivationMutationRate,
CParams::dMaxActivationPerturbation);
}
//sort the babies genes by their innovation numbers
baby.SortGenes();
//add to new pop
NewPop.push_back(baby);
++NumSpawnedSoFar;
if (NumSpawnedSoFar == CParams::iNumSweepers)
{
NumToSpawn = 0;
}
}//end while
}//end if
}//next species
//if there is an underflow due to the rounding error and the amount
//of offspring falls short of the population size additional children
//need to be created and added to the new population. This is achieved
//simply, by using tournament selection over the entire population.
if (NumSpawnedSoFar < CParams::iNumSweepers)
{
//calculate amount of additional children required
int Rqd = CParams::iNumSweepers - NumSpawnedSoFar;
//grab them
while (Rqd--)
{
NewPop.push_back(TournamentSelection(m_iPopSize/5));
}
}
//replace the current population with the new one
m_vecGenomes = NewPop;
//create the new phenotypes
vector<CNeuralNet*> new_phenotypes;
for (gen=0; gen<m_vecGenomes.size(); ++gen)
{
//calculate max network depth
int depth = CalculateNetDepth(m_vecGenomes[gen]);
CNeuralNet* phenotype = m_vecGenomes[gen].CreatePhenotype(depth);
new_phenotypes.push_back(phenotype);
}
//increase generation counter
++m_iGeneration;
return new_phenotypes;
}
//--------------------------- SortAndRecord-------------------------------
//
// sorts the population into descending fitness, keeps a record of the
// best n genomes and updates any fitness statistics accordingly
//------------------------------------------------------------------------
void Cga::SortAndRecord()
{
//sort the genomes according to their unadjusted (no fitness sharing)
//fitnesses
sort(m_vecGenomes.begin(), m_vecGenomes.end());
//is the best genome this generation the best ever?
if (m_vecGenomes[0].Fitness() > m_dBestEverFitness)
{
m_dBestEverFitness = m_vecGenomes[0].Fitness();
}
//keep a record of the n best genomes
StoreBestGenomes();
}
//----------------------------- StoreBestGenomes -------------------------
//
// used to keep a record of the previous populations best genomes so that
// they can be displayed if required.
//------------------------------------------------------------------------
void Cga::StoreBestGenomes()
{
//clear old record
m_vecBestGenomes.clear();
for (int gen=0; gen<CParams::iNumBestSweepers; ++gen)
{
m_vecBestGenomes.push_back(m_vecGenomes[gen]);
}
}
//----------------- GetBestPhenotypesFromLastGeneration ------------------
//
// returns a std::vector of the n best phenotypes from the previous
// generation
//------------------------------------------------------------------------
vector<CNeuralNet*> Cga::GetBestPhenotypesFromLastGeneration()
{
vector<CNeuralNet*> brains;
for (int gen=0; gen<m_vecBestGenomes.size(); ++gen)
{
//calculate max network depth
int depth = CalculateNetDepth(m_vecBestGenomes[gen]);
brains.push_back(m_vecBestGenomes[gen].CreatePhenotype(depth));
}
return brains;
}
//--------------------------- AdjustSpecies ------------------------------
//
// this functions simply iterates through each species and calls
// AdjustFitness for each species
//------------------------------------------------------------------------
void Cga::AdjustSpeciesFitnesses()
{
for (int sp=0; sp<m_vecSpecies.size(); ++sp)
{
m_vecSpecies[sp].AdjustFitnesses();
}
}
//------------------ SpeciateAndCalculateSpawnLevels ---------------------
//
// separates each individual into its respective species by calculating
// a compatibility score with every other member of the population and
// niching accordingly. The function then adjusts the fitness scores of
// each individual by species age and by sharing and also determines
// how many offspring each individual should spawn.
//------------------------------------------------------------------------
void Cga::SpeciateAndCalculateSpawnLevels()
{
bool bAdded = false;
//iterate through each genome and speciate
for (int gen=0; gen<m_vecGenomes.size(); ++gen)
{
//calculate its compatibility score with each species leader. If
//compatible add to species. If not, create a new species
for (int spc=0; spc<m_vecSpecies.size(); ++spc)
{
double compatibility = m_vecGenomes[gen].GetCompatibilityScore(m_vecSpecies[spc].Leader());
//if this individual is similar to this species add to species
if (compatibility <= CParams::dCompatibilityThreshold)
{
m_vecSpecies[spc].AddMember(m_vecGenomes[gen]);
//let the genome know which species it's in
m_vecGenomes[gen].SetSpecies(m_vecSpecies[spc].ID());
bAdded = true;
break;
}
}
if (!bAdded)
{
//we have not found a compatible species so let's create a new one
m_vecSpecies.push_back(CSpecies(m_vecGenomes[gen], m_iNextSpeciesID++));
}
bAdded = false;
}
//now all the genomes have been assigned a species the fitness scores
//need to be adjusted to take into account sharing and species age.
AdjustSpeciesFitnesses();
//calculate new adjusted total & average fitness for the population
for (gen=0; gen<m_vecGenomes.size(); ++gen)
{
m_dTotFitAdj += m_vecGenomes[gen].GetAdjFitness();
}
m_dAvFitAdj = m_dTotFitAdj/m_vecGenomes.size();
//calculate how many offspring each member of the population
//should spawn
for (gen=0; gen<m_vecGenomes.size(); ++gen)
{
double ToSpawn = m_vecGenomes[gen].GetAdjFitness() / m_dAvFitAdj;
m_vecGenomes[gen].SetAmountToSpawn(ToSpawn);
}
//iterate through all the species and calculate how many offspring
//each species should spawn
for (int spc=0; spc<m_vecSpecies.size(); ++spc)
{
m_vecSpecies[spc].CalculateSpawnAmount();
}
}
//--------------------------- TournamentSelection ------------------------
//
//------------------------------------------------------------------------
CGenome Cga::TournamentSelection(const int NumComparisons)
{
double BestFitnessSoFar = 0;
int ChosenOne = 0;
//Select NumComparisons members from the population at random testing
//against the best found so far
for (int i=0; i<NumComparisons; ++i)
{
int ThisTry = RandInt(0, m_vecGenomes.size()-1);
if (m_vecGenomes[ThisTry].Fitness() > BestFitnessSoFar)
{
ChosenOne = ThisTry;
BestFitnessSoFar = m_vecGenomes[ThisTry].Fitness();
}
}
//return the champion
return m_vecGenomes[ChosenOne];
}
//-----------------------------------Crossover----------------------------
//
//------------------------------------------------------------------------
CGenome Cga::Crossover(CGenome& mum, CGenome& dad)
{
//helps make the code clearer
enum parent_type{MUM, DAD,};
//first, calculate the genome we will using the disjoint/excess
//genes from. This is the fittest genome.
parent_type best;
//if they are of equal fitness use the shorter (because we want to keep
//the networks as small as possible)
if (mum.Fitness() == dad.Fitness())
{
//if they are of equal fitness and length just choose one at
//random
if (mum.NumGenes() == dad.NumGenes())
{
best = (parent_type)RandInt(0, 1);
}
else
{
if (mum.NumGenes() < dad.NumGenes())
{
best = MUM;
}
else
{
best = DAD;
}
}
}
else
{
if (mum.Fitness() > dad.Fitness())
{
best = MUM;
}
else
{
best = DAD;
}
}
//these vectors will hold the offspring's nodes and genes
vector<SNeuronGene> BabyNeurons;
vector<SLinkGene> BabyGenes;
//temporary vector to store all added node IDs
vector<int> vecNeurons;
//create iterators so we can step through each parents genes and set
//them to the first gene of each parent
vector<SLinkGene>::iterator curMum = mum.StartOfGenes();
vector<SLinkGene>::iterator curDad = dad.StartOfGenes();
//this will hold a copy of the gene we wish to add at each step
SLinkGene SelectedGene;
//step through each parents genes until we reach the end of both
while (!((curMum == mum.EndOfGenes()) && (curDad == dad.EndOfGenes())))
{
//the end of mum's genes have been reached
if ((curMum == mum.EndOfGenes())&&(curDad != dad.EndOfGenes()))
{
//if dad is fittest
if (best == DAD)
{
//add dads genes
SelectedGene = *curDad;
}
//move onto dad's next gene
++curDad;
}
//the end of dads's genes have been reached
else if ( (curDad == dad.EndOfGenes()) && (curMum != mum.EndOfGenes()))
{
//if mum is fittest
if (best == MUM)
{
//add mums genes
SelectedGene = *curMum;
}
//move onto mum's next gene
++curMum;
}
//if mums innovation number is less than dads
else if (curMum->InnovationID < curDad->InnovationID)
{
//if mum is fittest add gene
if (best == MUM)
{
SelectedGene = *curMum;
}
//move onto mum's next gene
++curMum;
}
//if dads innovation number is less than mums
else if (curDad->InnovationID < curMum->InnovationID)
{
//if dad is fittest add gene
if (best = DAD)
{
SelectedGene = *curDad;
}
//move onto dad's next gene
++curDad;
}
//if innovation numbers are the same
else if (curDad->InnovationID == curMum->InnovationID)
{
//grab a gene from either parent
if (RandFloat() < 0.5f)
{
SelectedGene = *curMum;
}
else
{
SelectedGene = *curDad;
}
//move onto next gene of each parent
++curMum;
++curDad;
}
//add the selected gene if not already added
if (BabyGenes.size() == 0)
{
BabyGenes.push_back(SelectedGene);
}
else
{
if (BabyGenes[BabyGenes.size()-1].InnovationID !=
SelectedGene.InnovationID)
{
BabyGenes.push_back(SelectedGene);
}
}
//Check if we already have the nodes referred to in SelectedGene.
//If not, they need to be added.
AddNeuronID(SelectedGene.FromNeuron, vecNeurons);
AddNeuronID(SelectedGene.ToNeuron, vecNeurons);
}//end while
//now create the required nodes. First sort them into order
sort(vecNeurons.begin(), vecNeurons.end());
for (int i=0; i<vecNeurons.size(); i++)
{
BabyNeurons.push_back(m_pInnovation->CreateNeuronFromID(vecNeurons[i]));
}
//finally, create the genome
CGenome babyGenome(m_iNextGenomeID++,
BabyNeurons,
BabyGenes,
mum.NumInputs(),
mum.NumOutputs());
return babyGenome;
}
//--------------------------- ResetAndKill -------------------------------
//
// This function resets some values ready for the next epoch, kills off
// all the phenotypes and any poorly performing species.
//------------------------------------------------------------------------
void Cga::ResetAndKill()
{
m_dTotFitAdj = 0;
m_dAvFitAdj = 0;
//purge the species
vector<CSpecies>::iterator curSp = m_vecSpecies.begin();
while (curSp != m_vecSpecies.end())
{
curSp->Purge();
//kill off species if not improving and if not the species which contains
//the best genome found so far
if ( (curSp->GensNoImprovement() > CParams::iNumGensAllowedNoImprovement) &&
(curSp->BestFitness() < m_dBestEverFitness) )
{
curSp = m_vecSpecies.erase(curSp);
--curSp;
}
++curSp;
}
//we can also delete the phenotypes
for (int gen=0; gen<m_vecGenomes.size(); ++gen)
{
m_vecGenomes[gen].DeletePhenotype();
}
}
//------------------------------- Split ----------------------------------
//
// this function is used to create a lookup table that is used to
// calculate the depth of the network.
//------------------------------------------------------------------------
vector<SplitDepth> Cga::Split(double low, double high, int depth)
{
static vector<SplitDepth> vecSplits;
double span = high-low;
vecSplits.push_back(SplitDepth(low + span/2, depth+1));
if (depth > 6)
{
return vecSplits;
}
else
{
Split(low, low+span/2, depth+1);
Split(low+span/2, high, depth+1);
return vecSplits;
}
}
//--------------------------- RenderSpeciesInfo --------------------------
//
// does what it says on the tin
//------------------------------------------------------------------------
void Cga::RenderSpeciesInfo(HDC &surface, RECT db)
{
if (m_vecSpecies.size() < 1) return;
int numColours = 255/m_vecSpecies.size();
double SlicePerSweeper = (double)(db.right-db.left)/(double)(CParams::iNumSweepers-1);
double left = db.left;
//now draw a different colored rectangle for each species
for (int spc=0; spc<m_vecSpecies.size(); ++spc)
{
//choose a brush to draw with
HBRUSH PieBrush = CreateSolidBrush(RGB(numColours*spc, 255, 255 - numColours*spc));
HBRUSH OldBrush = (HBRUSH)SelectObject(surface, PieBrush);
if (spc == m_vecSpecies.size()-1)
{
Rectangle(surface,
left,
db.top,
db.right,
db.bottom);
}
else
{
Rectangle(surface,
left,
db.top,
left+SlicePerSweeper*m_vecSpecies[spc].NumMembers(),
db.bottom);
}
left += SlicePerSweeper * m_vecSpecies[spc].NumMembers();
SelectObject(surface, OldBrush);
DeleteObject(PieBrush);
//display best performing species stats in the same color as displayed
//in the distribution bar
if ( m_vecSpecies[spc].BestFitness() == m_dBestEverFitness)
{
string s = "Best Species ID: " + itos(m_vecSpecies[spc].ID());
TextOut(surface, 5, db.top - 80, s.c_str(), s.size());
s = "Species Age: " + itos(m_vecSpecies[spc].Age());
TextOut(surface, 5, db.top - 60, s.c_str(), s.size());
s = "Gens no improvement: " + itos(m_vecSpecies[spc].GensNoImprovement());
TextOut(surface, 5, db.top - 40, s.c_str(), s.size());
}
}
string s = "Species Distribution Bar";
TextOut(surface, 5, db.top - 20, s.c_str(), s.size());
}
4. CInnovation.cpp
#include "CInnovation.h"
//---------------------------------- ctor --------------------------------
//
// given a series of start genes and start neurons this ctor adds
// all the appropriate innovations.
//------------------------------------------------------------------------
CInnovation::CInnovation(vector<SLinkGene> start_genes,
vector<SNeuronGene> start_neurons)
{
m_NextNeuronID = 0;
m_NextInnovationNum = 0;
//add the neurons
for (int nd=0; nd<start_neurons.size(); ++nd)
{
m_vecInnovs.push_back(SInnovation(start_neurons[nd],
m_NextInnovationNum++,
m_NextNeuronID++));
}
//add the links
for (int cGen = 0; cGen<start_genes.size(); ++cGen)
{
SInnovation NewInnov(start_genes[cGen].FromNeuron,
start_genes[cGen].ToNeuron,
new_link,
m_NextInnovationNum);
m_vecInnovs.push_back(NewInnov);
++m_NextInnovationNum;
}
}
//---------------------------CheckInnovation------------------------------
//
// checks to see if this innovation has already occurred. If it has it
// returns the innovation ID. If not it returns a negative value.
//------------------------------------------------------------------------
int CInnovation::CheckInnovation(int in, int out, innov_type type)
{
//iterate through the innovations looking for a match on all
//three parameters
for (int inv=0; inv<m_vecInnovs.size(); ++inv)
{
if ( (m_vecInnovs[inv].NeuronIn == in) &&
(m_vecInnovs[inv].NeuronOut == out) &&
(m_vecInnovs[inv].InnovationType == type))
{
//found a match so assign this innovation number to id
return m_vecInnovs[inv].InnovationID;
}
}
//if no match return a negative value
return -1;
}
//--------------------------CreateNewInnovation---------------------------
//
// creates a new innovation and returns its ID
//------------------------------------------------------------------------
int CInnovation::CreateNewInnovation(int in, int out, innov_type type)
{
SInnovation new_innov(in, out, type, m_NextInnovationNum);
if (type == new_neuron)
{
new_innov.NeuronID = m_NextNeuronID;
++m_NextNeuronID;
}
m_vecInnovs.push_back(new_innov);
++m_NextInnovationNum;
return (m_NextNeuronID-1);
}
//------------------------------------------------------------------------
//
// as above but includes adding x/y position of new neuron
//------------------------------------------------------------------------
int CInnovation::CreateNewInnovation(int from,
int to,
innov_type InnovType,
neuron_type NeuronType,
double x,
double y)
{
SInnovation new_innov(from, to, InnovType, m_NextInnovationNum, NeuronType, x, y);
if (InnovType == new_neuron)
{
new_innov.NeuronID = m_NextNeuronID;
++m_NextNeuronID;
}
m_vecInnovs.push_back(new_innov);
++m_NextInnovationNum;
return (m_NextNeuronID-1);
}
//------------------------------- CreateNeuronFromID -----------------------
//
// given a neuron ID this function returns a clone of that neuron
//------------------------------------------------------------------------
SNeuronGene CInnovation::CreateNeuronFromID(int NeuronID)
{
SNeuronGene temp(hidden,0,0,0);
for (int inv=0; inv<m_vecInnovs.size(); ++inv)
{
if (m_vecInnovs[inv].NeuronID == NeuronID)
{
temp.NeuronType = m_vecInnovs[inv].NeuronType;
temp.iID = m_vecInnovs[inv].NeuronID;
temp.dSplitY = m_vecInnovs[inv].dSplitY;
temp.dSplitX = m_vecInnovs[inv].dSplitX;
return temp;
}
}
return temp;
}
5.CMapper.cpp
.#include "CMapper.h"
//--------------------------- Init ---------------------------------------
//
// This method needs to be called before you can use the instance.
//------------------------------------------------------------------------
void CMapper::Init(int MaxRangeX, int MaxRangeY)
{
//if already initialized return
if(m_NumCellsX) return;
m_dCellSize = CParams::dCellSize;
//first calculate how many segments we will require
m_NumCellsX = (int)(MaxRangeX/m_dCellSize)+1;
m_NumCellsY = (int)(MaxRangeY/m_dCellSize)+1;
//create the 2d vector of blank segments
for (int x=0; x<m_NumCellsX; ++x)
{
vector<SCell> temp;
for (int y=0; y<m_NumCellsY; ++y)
{
temp.push_back(SCell(x*m_dCellSize, (x+1)*m_dCellSize, y*m_dCellSize, (y+1)*m_dCellSize));
}
m_2DvecCells.push_back(temp);
}
m_iTotalCells = m_NumCellsX * m_NumCellsY;
}
//-------------------------------------------------------------
void CMapper::Update(double xPos, double yPos)
{
//check to make sure positions are within range
if ( (xPos < 0) || (xPos > CParams::WindowWidth) ||
(yPos < 0) || (yPos > CParams::WindowHeight) )
{
return;
}
int cellX = (int)(xPos / m_dCellSize );
int cellY = (int)(yPos / m_dCellSize );
m_2DvecCells[cellX][cellY].Update();
return;
}
//---------------------------------------------------------------
int CMapper::TicksLingered(double xPos, double yPos)const
{
//bounds check the incoming values
if ( (xPos > CParams::WindowWidth) || (xPos < 0) ||
(yPos > CParams::WindowHeight)|| (yPos < 0))
{
return 999;
}
int cellX = (int)(xPos / m_dCellSize);
int cellY = (int)(yPos / m_dCellSize);
return m_2DvecCells[cellX][cellY].iTicksSpentHere;
}
//------------------------- Visited --------------------------------------
//
//------------------------------------------------------------------------
bool CMapper::BeenVisited(double xPos, double yPos)const
{
int cellX = (int)(xPos / m_dCellSize);
int cellY = (int)(yPos / m_dCellSize);
if (m_2DvecCells[cellX][cellY].iTicksSpentHere > 0)
{
return true;
}
else
{
return false;
}
}
//--------------------------------- Render -------------------------------
//
// renders the visited cells. The color gets darker the more frequently
// the cell has been visited.
//------------------------------------------------------------------------
void CMapper::Render(HDC surface)
{
for (int x=0; x<m_NumCellsX; ++x)
{
for (int y=0; y<m_NumCellsY; ++y)
{
if (m_2DvecCells[x][y].iTicksSpentHere > 0)
{
int shading = 2 * m_2DvecCells[x][y].iTicksSpentHere;
if (shading >220)
{
shading = 220;
}
HBRUSH lightbrush = CreateSolidBrush(RGB(240,220-shading,220-shading));
FillRect(surface, &m_2DvecCells[x][y].Cell, lightbrush);
DeleteObject(lightbrush);
}
}
}
}
//----------------------------------- Reset ------------------------------
void CMapper::Reset()
{
for (int x=0; x<m_NumCellsX; ++x)
{
for (int y=0; y<m_NumCellsY; ++y)
{
m_2DvecCells[x][y].Reset();
}
}
}
int CMapper::NumCellsVisited() const
{
int total = 0;
for (int x=0; x<m_NumCellsX; ++x)
{
for (int y=0; y<m_NumCellsY; ++y)
{
if (m_2DvecCells[x][y].iTicksSpentHere > 0)
{
++total;
}
}
}
return total;
}
6.CMinesweeper.cpp
#include "CMinesweeper.h"
//-----------------------------------constructor-------------------------
//
//-----------------------------------------------------------------------
CMinesweeper::CMinesweeper():
m_dRotation(0),
m_lTrack(0),
m_rTrack(0),
m_dFitness(0),
m_dScale(CParams::iSweeperScale),
m_bCollided(false)
{
//create a static start position
m_vPosition = SVector2D(180, 200);
//create the sensors
CreateSensors(m_Sensors, CParams::iNumSensors, CParams::dSensorRange);
//initialize its memory
m_MemoryMap.Init(CParams::WindowWidth,
CParams::WindowHeight);
}
//-------------------------------- CreateSensors ------------------------
//
// This function returns a vector of points which make up the segments of
// the sweepers sensors.
//------------------------------------------------------------------------
void CMinesweeper::CreateSensors(vector<SPoint> &sensors,
int NumSensors,
double range)
{
//make sure vector of sensors is empty before proceeding
sensors.clear();
double SegmentAngle = CParams::dPi / (NumSensors-1);
//going clockwise from 90deg left of position calculate the fan of
//points radiating out (not including the origin)
for (int i=0; i<CParams::iNumSensors; i++)
{
//calculate vertex position
SPoint point;
point.x = -sin(i * SegmentAngle - CParams::dHalfPi) * range;
point.y = cos(i * SegmentAngle - CParams::dHalfPi) * range;
sensors.push_back(point);
}//next segment
}
//-----------------------------Reset()------------------------------------
//
// Resets the sweepers position, fitness and rotation
//
//------------------------------------------------------------------------
void CMinesweeper::Reset()
{
//reset the sweepers positions
m_vPosition = SVector2D(180, 200);
//and the fitness
m_dFitness = 0;
//and the rotation
m_dRotation = 0;
//reset its memory
m_MemoryMap.Reset();
}
//------------------------- RenderMemory ---------------------------------
//
//------------------------------------------------------------------------
void CMinesweeper::Render(HDC surface)
{
//render the memory
m_MemoryMap.Render(surface);
string s = itos(m_MemoryMap.NumCellsVisited());
s = "Num Cells Visited: " + s;
TextOut(surface, 220,0,s.c_str(), s.size());
}
//---------------------WorldTransform--------------------------------
//
// sets up a translation matrix for the sweeper according to its
// scale, rotation and position. Returns the transformed vertices.
//-------------------------------------------------------------------
void CMinesweeper::WorldTransform(vector<SPoint> &sweeper, double scale)
{
//create the world transformation matrix
C2DMatrix matTransform;
//scale
matTransform.Scale(scale, scale);
//rotate
matTransform.Rotate(m_dRotation);
//and translate
matTransform.Translate(m_vPosition.x, m_vPosition.y);
//now transform the ships vertices
matTransform.TransformSPoints(sweeper);
}
//-------------------------------Update()--------------------------------
//
// First we take sensor readings and feed these into the sweepers brain.
//
// The inputs are:
//
// The readings from the minesweepers sensors
//
// We receive two outputs from the brain.. lTrack & rTrack.
// So given a force for each track we calculate the resultant rotation
// and acceleration and apply to current velocity vector.
//
//-----------------------------------------------------------------------
bool CMinesweeper::Update(vector<SPoint> &objects)
{
//this will store all the inputs for the NN
vector<double> inputs;
//grab sensor readings
TestSensors(objects);
//input sensors into net
for (int sr=0; sr<m_vecdSensors.size(); ++sr)
{
inputs.push_back(m_vecdSensors[sr]);
inputs.push_back(m_vecFeelers[sr]);
}
inputs.push_back(m_bCollided);
//update the brain and get feedback
vector<double> output = m_pItsBrain->Update(inputs, CNeuralNet::active);
//make sure there were no errors in calculating the
//output
if (output.size() < CParams::iNumOutputs)
{
return false;
}
//assign the outputs to the sweepers left & right tracks
m_lTrack = output[0];
m_rTrack = output[1];
//calculate steering forces
double RotForce = m_lTrack - m_rTrack;
//clamp rotation
Clamp(RotForce, -CParams::dMaxTurnRate, CParams::dMaxTurnRate);
m_dRotation += RotForce;
//update Look At
m_vLookAt.x = -sin(m_dRotation);
m_vLookAt.y = cos(m_dRotation);
//if the sweepers haven't collided with an obstacle
//update their position
if (!m_bCollided)
{
m_dSpeed = m_lTrack + m_rTrack;
//update position
m_vPosition += (m_vLookAt * m_dSpeed);
//test range of x,y values - because of 'cheap' collision detection
//this can go into error when using < 4 sensors
TestRange();
}
//update the memory map
m_MemoryMap.Update(m_vPosition.x, m_vPosition.y);
return true;
}
//----------------------- TestSensors ------------------------------------
//
// This function checks for any intersections between the sweeper's
// sensors and the objects in its environment
//------------------------------------------------------------------------
void CMinesweeper::TestSensors(vector<SPoint> &objects)
{
m_bCollided = false;
//first we transform the sensors into world coordinates
m_tranSensors = m_Sensors;
WorldTransform(m_tranSensors, 1); //scale is 1
//flush the sensors
m_vecdSensors.clear();
m_vecFeelers.clear();
//now to check each sensor against the objects in the world
for (int sr=0; sr<m_tranSensors.size(); ++sr)
{
bool bHit = false;
double dist = 0;
for (int seg=0; seg<objects.size(); seg+=2)
{
if (LineIntersection2D(SPoint(m_vPosition.x, m_vPosition.y),
m_tranSensors[sr],
objects[seg],
objects[seg+1],
dist))
{
bHit = true;
break;
}
}
if (bHit)
{
m_vecdSensors.push_back(dist);
//implement very simple collision detection
if (dist < CParams::dCollisionDist)
{
m_bCollided = true;
}
}
else
{
m_vecdSensors.push_back(-1);
}
//check how many times the minesweeper has visited the cell
//at the current position
int HowOften = m_MemoryMap.TicksLingered(m_tranSensors[sr].x,
m_tranSensors[sr].y);
//Update the memory info according to HowOften. The maximum
//value is 1 (because we want all the inputs into the
//ANN to be scaled between -1 < n < 1)
if (HowOften == 0)
{
m_vecFeelers.push_back(-1);
continue;
}
if (HowOften < 10)
{
m_vecFeelers.push_back(0);
continue;
}
if (HowOften < 20)
{
m_vecFeelers.push_back(0.2);
continue;
}
if (HowOften < 30)
{
m_vecFeelers.push_back(0.4);
continue;
}
if (HowOften < 50)
{
m_vecFeelers.push_back(0.6);
continue;
}
if (HowOften < 80)
{
m_vecFeelers.push_back(0.8);
continue;
}
m_vecFeelers.push_back(1);
}//next sensor
}
//-------------------------------- TestRange -----------------------------
//
//------------------------------------------------------------------------
void CMinesweeper::TestRange()
{
if (m_vPosition.x < 0)
{
m_vPosition.x = 0;
}
if (m_vPosition.x > CParams::WindowWidth)
{
m_vPosition.x = CParams::WindowWidth;
}
if (m_vPosition.y < 0)
{
m_vPosition.y = 0;
}
if (m_vPosition.y > CParams::WindowHeight)
{
m_vPosition.y = CParams::WindowHeight;
}
}
//------------------------- EndOfRunCalculations() -----------------------
//
//------------------------------------------------------------------------
void CMinesweeper::EndOfRunCalculations()
{
m_dFitness += m_MemoryMap.NumCellsVisited();
}
7.cpp
#include "collision.h"
8.CParams.cpp
.#include "CParams.h"
double CParams::dPi = 0;
double CParams::dHalfPi = 0;
double CParams::dTwoPi = 0;
int CParams::WindowWidth = 400;
int CParams::WindowHeight = 400;
int CParams::iFramesPerSecond = 0;
int CParams::iNumInputs = 0;
int CParams::iNumOutputs = 0;
double CParams::dBias = -1;
double CParams::dMaxTurnRate = 0;
int CParams::iSweeperScale = 0;
int CParams::iNumSensors = 0;
double CParams::dSensorRange = 0;
int CParams::iNumSweepers = 0;
int CParams::iNumTicks = 0;
double CParams::dCollisionDist = 0;
double CParams::dCellSize = 0;
double CParams::dSigmoidResponse = 1;
int CParams::iNumAddLinkAttempts = 0;
int CParams::iNumTrysToFindLoopedLink = 5;
int CParams::iNumTrysToFindOldLink = 5;
double CParams::dYoungFitnessBonus = 0;
int CParams::iYoungBonusAgeThreshhold = 0;
double CParams::dSurvivalRate = 0;
int CParams::InfoWindowWidth = 400;
int CParams::InfoWindowHeight = 400;
int CParams::iNumGensAllowedNoImprovement = 0;
int CParams::iMaxPermittedNeurons = 0;
double CParams::dChanceAddLink = 0;
double CParams::dChanceAddNode = 0;
double CParams::dChanceAddRecurrentLink = 0;
double CParams::dMutationRate = 0;
double CParams::dMaxWeightPerturbation = 0;
double CParams::dProbabilityWeightReplaced= 0;
double CParams::dActivationMutationRate = 0;
double CParams::dMaxActivationPerturbation= 0;
double CParams::dCompatibilityThreshold = 0;
int CParams::iNumBestSweepers = 4;
int CParams::iOldAgeThreshold = 0;
double CParams::dOldAgePenalty = 0;
double CParams::dCrossoverRate = 0;
//this function loads in the parameters from a given file name. Returns
//false if there is a problem opening the file.
bool CParams::LoadInParameters(char* szFileName)
{
ifstream grab(szFileName);
//check file exists
if (!grab)
{
return false;
}
//load in from the file
char ParamDescription[40];
grab >> ParamDescription;
grab >> iFramesPerSecond;
grab >> ParamDescription;
grab >> dMaxTurnRate;
grab >> ParamDescription;
grab >> iSweeperScale;
grab >> ParamDescription;
grab >> iNumSensors;
grab >> ParamDescription;
grab >> dSensorRange;
grab >> ParamDescription;
grab >> iNumSweepers;
grab >> ParamDescription;
grab >> iNumTicks;
grab >> ParamDescription;
grab >> dCellSize;
grab >> ParamDescription;
grab >> iNumAddLinkAttempts;
grab >> ParamDescription;
grab >> dSurvivalRate;
grab >> ParamDescription;
grab >> iNumGensAllowedNoImprovement;
grab >> ParamDescription;
grab >> iMaxPermittedNeurons;
grab >> ParamDescription;
grab >> dChanceAddLink;
grab >> ParamDescription;
grab >> dChanceAddNode;
grab >> ParamDescription;
grab >> dChanceAddRecurrentLink;
grab >> ParamDescription;
grab >> dMutationRate;
grab >> ParamDescription;
grab >> dMaxWeightPerturbation;
grab >> ParamDescription;
grab >> dProbabilityWeightReplaced;
grab >> ParamDescription;
grab >> dActivationMutationRate;
grab >> ParamDescription;
grab >> dMaxActivationPerturbation;
grab >> ParamDescription;
grab >> dCompatibilityThreshold;
grab >> ParamDescription;
grab >>iOldAgeThreshold;
grab >> ParamDescription;
grab >>dOldAgePenalty;
grab >> ParamDescription;
grab >> dYoungFitnessBonus;
grab >> ParamDescription;
grab >> iYoungBonusAgeThreshhold;
grab >> ParamDescription;
grab >>dCrossoverRate;
return true;
}
9.CSpecies.cpp
.#include "CSpecies.h"
//------------------------------------------------------------------------
//
// this ctor creates an instance of a new species. A local copy of
// the initializing genome is kept in m_Leader and the first element
// of m_vecMembers is a pointer to that genome.
//------------------------------------------------------------------------
CSpecies::CSpecies(CGenome &FirstOrg,
int SpeciesID):m_iSpeciesID(SpeciesID),
m_dBestFitness(FirstOrg.Fitness()),
m_iGensNoImprovement(0),
m_iAge(0),
m_Leader(FirstOrg),
m_dSpawnsRqd(0)
{
m_vecMembers.push_back(&FirstOrg);
m_Leader = FirstOrg;
}
//------------------------ AddMember -------------------------------------
//
// this function adds a new member to this species and updates the member
// variables accordingly
//------------------------------------------------------------------------
void CSpecies::AddMember(CGenome &NewMember)
{
//is the new member's fitness better than the best fitness?
if (NewMember.Fitness() > m_dBestFitness)
{
m_dBestFitness = NewMember.Fitness();
m_iGensNoImprovement = 0;
m_Leader = NewMember;
}
m_vecMembers.push_back(&NewMember);
}
//-------------------------- Purge ---------------------------------------
//
// this functions clears out all the members from the last generation,
// updates the age and gens no improvement.
//------------------------------------------------------------------------
void CSpecies::Purge()
{
m_vecMembers.clear();
//update age etc
++m_iAge;
++m_iGensNoImprovement;
m_dSpawnsRqd = 0;
}
//--------------------------- AdjustFitness ------------------------------
//
// This function adjusts the fitness of each individual by first
// examining the species age and penalising if old, boosting if young.
// Then we perform fitness sharing by dividing the fitness by the number
// of individuals in the species. This ensures a species does not grow
// too large
//------------------------------------------------------------------------
void CSpecies::AdjustFitnesses()
{
double total = 0;
for (int gen=0; gen<m_vecMembers.size(); ++gen)
{
double fitness = m_vecMembers[gen]->Fitness();
//boost the fitness scores if the species is young
if (m_iAge < CParams::iYoungBonusAgeThreshhold)
{
fitness *= CParams::dYoungFitnessBonus;
}
//punish older species
if (m_iAge > CParams::iOldAgeThreshold)
{
fitness *= CParams::dOldAgePenalty;
}
total += fitness;
//apply fitness sharing to adjusted fitnesses
double AdjustedFitness = fitness/m_vecMembers.size();
m_vecMembers[gen]->SetAdjFitness(AdjustedFitness);
}
}
//------------------------ CalculateSpawnAmount --------------------------
//
// Simply adds up the expected spawn amount for each individual in the
// species to calculate the amount of offspring this species should
// spawn
//------------------------------------------------------------------------
void CSpecies::CalculateSpawnAmount()
{
for (int gen=0; gen<m_vecMembers.size(); ++gen)
{
m_dSpawnsRqd += m_vecMembers[gen]->AmountToSpawn();
}
}
//------------------------ Spawn -----------------------------------------
//
// Returns a random genome selected from the best individuals
//------------------------------------------------------------------------
CGenome CSpecies::Spawn()
{
CGenome baby;
if (m_vecMembers.size() == 1)
{
baby = *m_vecMembers[0];
}
else
{
int MaxIndexSize = (int) (CParams::dSurvivalRate * m_vecMembers.size())+1;
int TheOne = RandInt(0, MaxIndexSize);
baby = *m_vecMembers[TheOne];
}
return baby;
}
10.CTimer.cpp
//---------------------- default constructor ------------------------------
//
//-------------------------------------------------------------------------
CTimer::CTimer(): m_FPS(0),
m_TimeElapsed(0.0f),
m_FrameTime(0),
m_LastTime(0),
m_PerfCountFreq(0)
{
//how many ticks per sec do we get
QueryPerformanceFrequency( (LARGE_INTEGER*) &m_PerfCountFreq);
m_TimeScale = 1.0f/m_PerfCountFreq;
}
//---------------------- constructor -------------------------------------
//
// use to specify FPS
//
//-------------------------------------------------------------------------
CTimer::CTimer(float fps): m_FPS(fps),
m_TimeElapsed(0.0f),
m_LastTime(0),
m_PerfCountFreq(0)
{
//how many ticks per sec do we get
QueryPerformanceFrequency( (LARGE_INTEGER*) &m_PerfCountFreq);
m_TimeScale = 1.0f/m_PerfCountFreq;
//calculate ticks per frame
m_FrameTime = (LONGLONG)(m_PerfCountFreq / m_FPS);
}
//------------------------Start()-----------------------------------------
//
// call this immediately prior to game loop. Starts the timer (obviously!)
//
//--------------------------------------------------------------------------
void CTimer::Start()
{
//get the time
QueryPerformanceCounter( (LARGE_INTEGER*) &m_LastTime);
//update time to render next frame
m_NextTime = m_LastTime + m_FrameTime;
return;
}
//-------------------------ReadyForNextFrame()-------------------------------
//
// returns true if it is time to move on to the next frame step. To be used if
// FPS is set.
//
//----------------------------------------------------------------------------
bool CTimer::ReadyForNextFrame()
{
if (!m_FPS)
{
MessageBox(NULL, "No FPS set in timer", "Doh!", 0);
return false;
}
QueryPerformanceCounter( (LARGE_INTEGER*) &m_CurrentTime);
if (m_CurrentTime > m_NextTime)
{
m_TimeElapsed = (m_CurrentTime - m_LastTime) * m_TimeScale;
m_LastTime = m_CurrentTime;
//update time to render next frame
m_NextTime = m_CurrentTime + m_FrameTime;
return true;
}
return false;
}
//--------------------------- TimeElapsed --------------------------------
//
// returns time elapsed since last call to this function. Use in main
// when calculations are to be based on dt.
//
//-------------------------------------------------------------------------
double CTimer::TimeElapsed()
{
QueryPerformanceCounter( (LARGE_INTEGER*) &m_CurrentTime);
m_TimeElapsed = (m_CurrentTime - m_LastTime) * m_TimeScale;
m_LastTime = m_CurrentTime;
return m_TimeElapsed;
}
11.genotype.cpp
//------------------------------------------------------------------------
//
// default ctor
//------------------------------------------------------------------------
CGenome::CGenome():m_pPhenotype(NULL),
m_GenomeID(0),
m_dFitness(0),
m_dAdjustedFitness(0),
m_iNumInputs(0),
m_iNumOutPuts(0),
m_dAmountToSpawn(0)
{}
//-----------------------------constructor--------------------------------
// this constructor creates a minimal genome where there are output +
// input neurons and each input neuron is connected to each output neuron.
//------------------------------------------------------------------------
CGenome::CGenome(int id, int inputs, int outputs):m_pPhenotype(NULL),
m_GenomeID(id),
m_dFitness(0),
m_dAdjustedFitness(0),
m_iNumInputs(inputs),
m_iNumOutPuts(outputs),
m_dAmountToSpawn(0),
m_iSpecies(0)
{
//create the input neurons
double InputRowSlice = 1/(double)(inputs+2);
for (int i=0; i<inputs; i++)
{
m_vecNeurons.push_back(SNeuronGene(input, i, 0, (i+2)*InputRowSlice));
}
//create the bias
m_vecNeurons.push_back(SNeuronGene(bias, inputs, 0, InputRowSlice));
//create the output neurons
double OutputRowSlice = 1/(double)(outputs+1);
for (i=0; i<outputs; i++)
{
m_vecNeurons.push_back(SNeuronGene(output, i+inputs+1, 1, (i+1)*OutputRowSlice));
}
//create the link genes, connect each input neuron to each output neuron and
//assign a random weight -1 < w < 1
for (i=0; i<inputs+1; i++)
{
for (int j=0; j<outputs; j++)
{
m_vecLinks.push_back(SLinkGene(m_vecNeurons[i].iID,
m_vecNeurons[inputs+j+1].iID,
true,
inputs+outputs+1+NumGenes(),
RandomClamped()));
}
}
}
//------------------------------------------------------------------------
//
// this constructor creates a genome from a vector of SLinkGenes, a
// vector of SNeuronGenes and an ID number.
//------------------------------------------------------------------------
CGenome::CGenome(int id,
vector<SNeuronGene> neurons,
vector<SLinkGene> genes,
int inputs,
int outputs):m_GenomeID(id),
m_pPhenotype(NULL),
m_vecLinks(genes),
m_vecNeurons(neurons),
m_dAmountToSpawn(0),
m_dFitness(0),
m_dAdjustedFitness(0),
m_iNumInputs(inputs),
m_iNumOutPuts(outputs)
{}
//-------------------------------dtor-----------------------------------------------------
//
//----------------------------------------------------------------------------------------
CGenome::~CGenome()
{
if (m_pPhenotype)
{
delete m_pPhenotype;
m_pPhenotype = NULL;
}
}
//---------------------------------copy ctor---------------------------------------------
//
//---------------------------------------------------------------------------------------
CGenome::CGenome(const CGenome& g)
{
m_GenomeID = g.m_GenomeID;
m_vecNeurons = g.m_vecNeurons;
m_vecLinks = g.m_vecLinks;
m_pPhenotype = NULL; //no need to perform a deep copy
m_dFitness = g.m_dFitness;
m_dAdjustedFitness = g.m_dAdjustedFitness;
m_iNumInputs = g.m_iNumInputs;
m_iNumOutPuts = g.m_iNumOutPuts;
m_dAmountToSpawn = g.m_dAmountToSpawn;
}
//---------------------------------assignment operator-----------------------------------
//
//----------------------------------------------------------------------------------------
CGenome& CGenome::operator =(const CGenome& g)
{
//self assignment guard
if (this != &g)
{
m_GenomeID = g.m_GenomeID;
m_vecNeurons = g.m_vecNeurons;
m_vecLinks = g.m_vecLinks;
m_pPhenotype = NULL; //no need to perform a deep copy
m_dFitness = g.m_dFitness;
m_dAdjustedFitness = g.m_dAdjustedFitness;
m_iNumInputs = g.m_iNumInputs;
m_iNumOutPuts = g.m_iNumOutPuts;
m_dAmountToSpawn = g.m_dAmountToSpawn;
}
return *this;
}
//-------------------------------CreatePhenotype--------------------------
//
// Creates a neural network based upon the information in the genome.
// Returns a pointer to the newly created ANN
//------------------------------------------------------------------------
CNeuralNet* CGenome::CreatePhenotype(int depth)
{
//first make sure there is no existing phenotype for this genome
DeletePhenotype();
//this will hold all the neurons required for the phenotype
vector<SNeuron*> vecNeurons;
//first, create all the required neurons
for (int i=0; i<m_vecNeurons.size(); i++)
{
SNeuron* pNeuron = new SNeuron(m_vecNeurons[i].NeuronType,
m_vecNeurons[i].iID,
m_vecNeurons[i].dSplitY,
m_vecNeurons[i].dSplitX,
m_vecNeurons[i].dActivationResponse);
vecNeurons.push_back(pNeuron);
}
//now to create the links.
for (int cGene=0; cGene<m_vecLinks.size(); ++cGene)
{
//make sure the link gene is enabled before the connection is created
if (m_vecLinks[cGene].bEnabled)
{
//get the pointers to the relevant neurons
int element = GetElementPos(m_vecLinks[cGene].FromNeuron);
SNeuron* FromNeuron = vecNeurons[element];
element = GetElementPos(m_vecLinks[cGene].ToNeuron);
SNeuron* ToNeuron = vecNeurons[element];
//create a link between those two neurons and assign the weight stored
//in the gene
SLink tmpLink(m_vecLinks[cGene].dWeight,
FromNeuron,
ToNeuron,
m_vecLinks[cGene].bRecurrent);
//add new links to neuron
FromNeuron->vecLinksOut.push_back(tmpLink);
ToNeuron->vecLinksIn.push_back(tmpLink);
}
}
//now the neurons contain all the connectivity information, a neural
//network may be created from them.
m_pPhenotype = new CNeuralNet(vecNeurons, depth);
return m_pPhenotype;
}
//--------------------------- DeletePhenotype ----------------------------
//
//------------------------------------------------------------------------
void CGenome::DeletePhenotype()
{
if (m_pPhenotype)
{
delete m_pPhenotype;
}
m_pPhenotype = NULL;
}
//---------------------------- GetElementPos -----------------------------
//
// given a neuron ID this little function just finds its position in
// m_vecNeurons
//------------------------------------------------------------------------
int CGenome::GetElementPos(int neuron_id)
{
for (int i=0; i<m_vecNeurons.size(); i++)
{
if (m_vecNeurons[i].iID == neuron_id)
{
return i;
}
}
MessageBox(NULL, "Error in CGenome::GetElementPos", "Problem!", MB_OK);
return -1;
}
//------------------------------DuplicateLink-----------------------------
//
// returns true if the link is already part of the genome
//------------------------------------------------------------------------
bool CGenome::DuplicateLink(int NeuronIn, int NeuronOut)
{
for (int cGene = 0; cGene < m_vecLinks.size(); ++cGene)
{
if ((m_vecLinks[cGene].FromNeuron == NeuronIn) &&
(m_vecLinks[cGene].ToNeuron == NeuronOut))
{
//we already have this link
return true;
}
}
return false;
}
//--------------------------------AddLink---------------------------------
//
// create a new link with the probability of CParams::dChanceAddLink
//------------------------------------------------------------------------
void CGenome::AddLink(double MutationRate,
double ChanceOfLooped,
CInnovation &innovation,
int NumTrysToFindLoop,
int NumTrysToAddLink)
{
//just return dependent on the mutation rate
if (RandFloat() > MutationRate) return;
//define holders for the two neurons to be linked. If we have find two
//valid neurons to link these values will become >= 0.
int ID_neuron1 = -1;
int ID_neuron2 = -1;
//flag set if a recurrent link is selected (looped or normal)
bool bRecurrent = false;
//first test to see if an attempt shpould be made to create a
//link that loops back into the same neuron
if (RandFloat() < ChanceOfLooped)
{
//YES: try NumTrysToFindLoop times to find a neuron that is not an
//input or bias neuron and that does not already have a loopback
//connection
while(NumTrysToFindLoop--)
{
//grab a random neuron
int NeuronPos = RandInt(m_iNumInputs+1, m_vecNeurons.size()-1);
//check to make sure the neuron does not already have a loopback
//link and that it is not an input or bias neuron
if (!m_vecNeurons[NeuronPos].bRecurrent &&
(m_vecNeurons[NeuronPos].NeuronType != bias) &&
(m_vecNeurons[NeuronPos].NeuronType != input))
{
ID_neuron1 = ID_neuron2 = m_vecNeurons[NeuronPos].iID;
m_vecNeurons[NeuronPos].bRecurrent = true;
bRecurrent = true;
NumTrysToFindLoop = 0;
}
}
}
else
{
//No: try to find two unlinked neurons. Make NumTrysToAddLink
//attempts
while(NumTrysToAddLink--)
{
//choose two neurons, the second must not be an input or a bias
ID_neuron1 = m_vecNeurons[RandInt(0, m_vecNeurons.size()-1)].iID;
ID_neuron2 =
m_vecNeurons[RandInt(m_iNumInputs+1, m_vecNeurons.size()-1)].iID;
if (ID_neuron2 == 2)
{
continue;
}
//make sure these two are not already linked and that they are
//not the same neuron
if ( !( DuplicateLink(ID_neuron1, ID_neuron2) ||
(ID_neuron1 == ID_neuron2)))
{
NumTrysToAddLink = 0;
}
else
{
ID_neuron1 = -1;
ID_neuron2 = -1;
}
}
}
//return if unsuccessful in finding a link
if ( (ID_neuron1 < 0) || (ID_neuron2 < 0) )
{
return;
}
//check to see if we have already created this innovation
int id = innovation.CheckInnovation(ID_neuron1, ID_neuron2, new_link);
//is this link recurrent?
if (m_vecNeurons[GetElementPos(ID_neuron1)].dSplitY >
m_vecNeurons[GetElementPos(ID_neuron2)].dSplitY)
{
bRecurrent = true;
}
if ( id < 0)
{
//we need to create a new innovation
innovation.CreateNewInnovation(ID_neuron1, ID_neuron2, new_link);
//then create the new gene
int id = innovation.NextNumber() - 1;
SLinkGene NewGene(ID_neuron1,
ID_neuron2,
true,
id,
RandomClamped(),
bRecurrent);
m_vecLinks.push_back(NewGene);
}
else
{
//the innovation has already been created so all we need to
//do is create the new gene using the existing innovation ID
SLinkGene NewGene(ID_neuron1,
ID_neuron2,
true,
id,
RandomClamped(),
bRecurrent);
m_vecLinks.push_back(NewGene);
}
return;
}
//---------------------------------AddNeuron------------------------------
//
// this function adds a neuron to the genotype by examining the network,
// splitting one of the links and inserting the new neuron.
//------------------------------------------------------------------------
void CGenome::AddNeuron(double MutationRate,
CInnovation &innovations,
int NumTrysToFindOldLink)
{
//just return dependent on mutation rate
if (RandFloat() > MutationRate) return;
//if a valid link is found into which to insert the new neuron
//this value is set to true.
bool bDone = false;
//this will hold the index into m_vecLinks of the chosen link gene
int ChosenLink = 0;
//first a link is chosen to split. If the genome is small the code makes
//sure one of the older links is split to ensure a chaining effect does
//not occur. Here, if the genome contains less than 5 hidden neurons it
//is considered to be too small to select a link at random
const int SizeThreshold = m_iNumInputs + m_iNumOutPuts + 5;
if (m_vecLinks.size() < SizeThreshold)
{
while(NumTrysToFindOldLink--)
{
//choose a link with a bias towards the older links in the genome
ChosenLink = RandInt(0, NumGenes()-1-(int)sqrt(NumGenes()));
//make sure the link is enabled and that it is not a recurrent link
//or has a bias input
int FromNeuron = m_vecLinks[ChosenLink].FromNeuron;
if ( (m_vecLinks[ChosenLink].bEnabled) &&
(!m_vecLinks[ChosenLink].bRecurrent) &&
(m_vecNeurons[GetElementPos(FromNeuron)].NeuronType != bias))
{
bDone = true;
NumTrysToFindOldLink = 0;
}
}
if (!bDone)
{
//failed to find a decent link
return;
}
}
else
{
//the genome is of sufficient size for any link to be acceptable
while (!bDone)
{
ChosenLink = RandInt(0, NumGenes()-1);
//make sure the link is enabled and that it is not a recurrent link
//or has a BIAS input
int FromNeuron = m_vecLinks[ChosenLink].FromNeuron;
if ( (m_vecLinks[ChosenLink].bEnabled) &&
(!m_vecLinks[ChosenLink].bRecurrent) &&
(m_vecNeurons[GetElementPos(FromNeuron)].NeuronType != bias))
{
bDone = true;
}
}
}
//disable this gene
m_vecLinks[ChosenLink].bEnabled = false;
//grab the weight from the gene (we want to use this for the weight of
//one of the new links so that the split does not disturb anything the
//NN may have already learned...
double OriginalWeight = m_vecLinks[ChosenLink].dWeight;
//identify the neurons this link connects
int from = m_vecLinks[ChosenLink].FromNeuron;
int to = m_vecLinks[ChosenLink].ToNeuron;
//calculate the depth and width of the new neuron. We can use the depth
//to see if the link feeds backwards or forwards
double NewDepth = (m_vecNeurons[GetElementPos(from)].dSplitY +
m_vecNeurons[GetElementPos(to)].dSplitY) /2;
double NewWidth = (m_vecNeurons[GetElementPos(from)].dSplitX +
m_vecNeurons[GetElementPos(to)].dSplitX) /2;
//Now to see if this innovation has been created previously by
//another member of the population
int id = innovations.CheckInnovation(from,
to,
new_neuron);
/*it is possible for NEAT to repeatedly do the following:
1. Find a link. Lets say we choose link 1 to 5
2. Disable the link,
3. Add a new neuron and two new links
4. The link disabled in Step 2 maybe re-enabled when this genome
is recombined with a genome that has that link enabled.
5 etc etc
Therefore, this function must check to see if a neuron ID is already
being used. If it is then the function creates a new innovation
for the neuron. */
if (id >= 0)
{
int NeuronID = innovations.GetNeuronID(id);
if (AlreadyHaveThisNeuronID(NeuronID))
{
id = -1;
}
}
if (id < 0)
{
//add the innovation for the new neuron
int NewNeuronID = innovations.CreateNewInnovation(from,
to,
new_neuron,
hidden,
NewWidth,
NewDepth);
//create the new neuron gene and add it.
m_vecNeurons.push_back(SNeuronGene(hidden,
NewNeuronID,
NewDepth,
NewWidth));
//Two new link innovations are required, one for each of the
//new links created when this gene is split.
//-----------------------------------first link
//get the next innovation ID
int idLink1 = innovations.NextNumber();
//create the new innovation
innovations.CreateNewInnovation(from,
NewNeuronID,
new_link);
//create the new link gene
SLinkGene link1(from,
NewNeuronID,
true,
idLink1,
1.0);
m_vecLinks.push_back(link1);
//-----------------------------------second link
//get the next innovation ID
int idLink2 = innovations.NextNumber();
//create the new innovation
innovations.CreateNewInnovation(NewNeuronID,
to,
new_link);
//create the new gene
SLinkGene link2(NewNeuronID,
to,
true,
idLink2,
OriginalWeight);
m_vecLinks.push_back(link2);
}
else
{
//this innovation has already been created so grab the relevant neuron
//and link info from the innovation database
int NewNeuronID = innovations.GetNeuronID(id);
//get the innovation IDs for the two new link genes.
int idLink1 = innovations.CheckInnovation(from, NewNeuronID, new_link);
int idLink2 = innovations.CheckInnovation(NewNeuronID, to, new_link);
//this should never happen because the innovations *should* have already
//occurred
if ( (idLink1 < 0) || (idLink2 < 0) )
{
MessageBox(NULL, "Error in CGenome::AddNeuron", "Problem!", MB_OK);
return;
}
//now we need to create 2 new genes to represent the new links
SLinkGene link1(from, NewNeuronID, true, idLink1, 1.0);
SLinkGene link2(NewNeuronID, to, true, idLink2, OriginalWeight);
m_vecLinks.push_back(link1);
m_vecLinks.push_back(link2);
//create the new neuron
SNeuronGene NewNeuron(hidden, NewNeuronID, NewDepth, NewWidth);
//and add it
m_vecNeurons.push_back(NewNeuron);
}
return;
}
//--------------------------- AlreadyHaveThisNeuronID ----------------------
//
// tests to see if the parameter is equal to any existing neuron ID's.
// Returns true if this is the case.
//------------------------------------------------------------------------
bool CGenome::AlreadyHaveThisNeuronID(const int ID)
{
for (int n=0; n<m_vecNeurons.size(); ++n)
{
if (ID == m_vecNeurons[n].iID)
{
return true;
}
}
return false;
}
//------------------------------- MutateWeights---------------------------
// Iterates through the genes and purturbs the weights given a
// probability mut_rate.
//
// prob_new_mut is the chance that a weight may get replaced by a
// completely new weight.
//
// dMaxPertubation is the maximum perturbation to be applied.
//
// type is the type of random number algorithm we use
//------------------------------------------------------------------------
void CGenome::MutateWeights(double mut_rate,
double prob_new_mut,
double MaxPertubation)
{
for (int cGen=0; cGen<m_vecLinks.size(); ++cGen)
{
//do we mutate this gene?
if (RandFloat() < mut_rate)
{
//do we change the weight to a completely new weight?
if (RandFloat() < prob_new_mut)
{
//change the weight using the random distribtion defined by 'type'
m_vecLinks[cGen].dWeight = RandomClamped();
}
else
{
//perturb the weight
m_vecLinks[cGen].dWeight += RandomClamped() * MaxPertubation;
}
}
}
return;
}
void CGenome::MutateActivationResponse(double mut_rate,
double MaxPertubation)
{
for (int cGen=0; cGen<m_vecNeurons.size(); ++cGen)
{
if (RandFloat() < mut_rate)
{
m_vecNeurons[cGen].dActivationResponse += RandomClamped() * MaxPertubation;
}
}
}
//------------------------- GetCompatibilityScore ------------------------
//
// this function returns a score based on the compatibility of this
// genome with the passed genome
//------------------------------------------------------------------------
double CGenome::GetCompatibilityScore(const CGenome &genome)
{
//travel down the length of each genome counting the number of
//disjoint genes, the number of excess genes and the number of
//matched genes
double NumDisjoint = 0;
double NumExcess = 0;
double NumMatched = 0;
//this records the summed difference of weights in matched genes
double WeightDifference = 0;
//position holders for each genome. They are incremented as we
//step down each genomes length.
int g1 = 0;
int g2 = 0;
while ( (g1 < m_vecLinks.size()-1) || (g2 < genome.m_vecLinks.size()-1) )
{
//we've reached the end of genome1 but not genome2 so increment
//the excess score
if (g1 == m_vecLinks.size()-1)
{
++g2;
++NumExcess;
continue;
}
//and vice versa
if (g2 == genome.m_vecLinks.size()-1)
{
++g1;
++NumExcess;
continue;
}
//get innovation numbers for each gene at this point
int id1 = m_vecLinks[g1].InnovationID;
int id2 = genome.m_vecLinks[g2].InnovationID;
//innovation numbers are identical so increase the matched score
if (id1 == id2)
{
++g1;
++g2;
++NumMatched;
//get the weight difference between these two genes
WeightDifference += fabs(m_vecLinks[g1].dWeight - genome.m_vecLinks[g2].dWeight);
}
//innovation numbers are different so increment the disjoint score
if (id1 < id2)
{
++NumDisjoint;
++g1;
}
if (id1 > id2)
{
++NumDisjoint;
++g2;
}
}//end while
//get the length of the longest genome
int longest = genome.NumGenes();
if (NumGenes() > longest)
{
longest = NumGenes();
}
//these are multipliers used to tweak the final score.
const double mDisjoint = 1;
const double mExcess = 1;
const double mMatched = 0.4;
//finally calculate the scores
double score = (mExcess * NumExcess/(double)longest) +
(mDisjoint * NumDisjoint/(double)longest) +
(mMatched * WeightDifference / NumMatched);
return score;
}
//--------------------------- SortGenes ----------------------------------
//
// does exactly that
//------------------------------------------------------------------------
void CGenome::SortGenes()
{
sort (m_vecLinks.begin(), m_vecLinks.end());
}
12.main.cpp
#include <time.h>
#include "utils.h"
#include "CController.h"
#include "CTimer.h"
#include "resource.h"
#include "CParams.h"
///////////////////////GLOBALS ////////////////////////////////////
char* szApplicationName = "Chapter 11 - NEAT evolution";
char* szWindowClassName = "sweeper";
char* szInfoWindowClassName = "Info Window";
//The controller class for this simulation
CController* g_pController= NULL;
CParams g_Params;
//global handle to the info window
HWND g_hwndInfo = NULL;
//global handle to the main window
HWND g_hwndMain = NULL;
//---------------------------- Cleanup ----------------------------------
//
// simply cleans up any memory issues when the application exits
//-----------------------------------------------------------------------
void Cleanup()
{
if (g_pController)
delete g_pController;
}
//-----------------------------------WinProc-----------------------------
//
//-----------------------------------------------------------------------
LRESULT CALLBACK WindowProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//these hold the dimensions of the client window area
static int cxClient, cyClient;
//used to create the back buffer
static HDC hdcBackBuffer;
static HBITMAPhBitmap;
static HBITMAPhOldBitmap;
switch(msg)
{
case WM_CREATE:
{
//seed the random number generator
srand((unsigned) time(NULL));
//get the size of the client window
RECT rect;
GetClientRect(hwnd, &rect);
cxClient = rect.right;
cyClient = rect.bottom;
//setup the controller
g_pController = new CController(hwnd, cxClient, cyClient);
//create a surface for us to render to(backbuffer)
hdcBackBuffer = CreateCompatibleDC(NULL);
HDC hdc = GetDC(hwnd);
hBitmap = CreateCompatibleBitmap(hdc,
cxClient,
cyClient);
ReleaseDC(hwnd, hdc);
hOldBitmap = (HBITMAP)SelectObject(hdcBackBuffer, hBitmap);
}
break;
//check key press messages
case WM_KEYUP:
{
switch(wparam)
{
case VK_ESCAPE:
{
PostQuitMessage(0);
}
break;
case 'F':
{
g_pController->FastRenderToggle();
}
break;
case 'B':
{
g_pController->RenderBestToggle();
}
break;
case 'R':
{
if (g_pController)
{
delete g_pController;
}
//setup the new controller
g_pController = new CController(hwnd, cxClient, cyClient);
//give the info window's handle to the controller
g_pController->PassInfoHandle(g_hwndInfo);
//clear info window
InvalidateRect(g_hwndInfo, NULL, TRUE);
UpdateWindow(g_hwndInfo);
}
break;
case '1':
{
g_pController->ViewBest(1);
}
break;
case '2':
{
g_pController->ViewBest(2);
}
break;
case '3':
{
g_pController->ViewBest(3);
}
break;
case '4':
{
g_pController->ViewBest(4);
}
break;
}//end WM_KEYUP switch
}
break;
//has the user resized the client area?
case WM_SIZE:
{
cxClient = LOWORD(lparam);
cyClient = HIWORD(lparam);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
//fill our backbuffer with white
BitBlt(hdcBackBuffer,
0,
0,
cxClient,
cyClient,
NULL,
NULL,
NULL,
WHITENESS);
//render the sweepers
g_pController->Render(hdcBackBuffer);
//now blit backbuffer to front
BitBlt(ps.hdc, 0, 0, cxClient, cyClient, hdcBackBuffer, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
}
break;
case WM_DESTROY:
{
SelectObject(hdcBackBuffer, hOldBitmap);
//clean up our backbuffer objects
DeleteDC(hdcBackBuffer);
DeleteObject(hBitmap);
// kill the application, this sends a WM_QUIT message
PostQuitMessage(0);
}
break;
default:break;
}//end switch
// default msg handler
return (DefWindowProc(hwnd, msg, wparam, lparam));
}//end WinProc
//-----------------------------------InfoWinProc-----------------------------
//
//-----------------------------------------------------------------------
LRESULT CALLBACK InfoWindowProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
//these hold the dimensions of the client window area
static int cxClient, cyClient;
switch(msg)
{
case WM_CREATE:
{
//get the size of the client window
RECT rect;
GetClientRect(hwnd, &rect);
cxClient = rect.right;
cyClient = rect.bottom;
}
break;
//has the user resized the client area?
case WM_SIZE:
{
cxClient = LOWORD(lparam);
cyClient = HIWORD(lparam);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
g_pController->RenderNetworks(ps.hdc);
EndPaint(hwnd, &ps);
}
break;
default:break;
}//end switch
// default msg handler
return (WindowProc(hwnd, msg, wparam, lparam));
}//end WinProc
//---------------------------------CreateInfoWindow---------------------------
//
// creates and displays the info window
//
//----------------------------------------------------------------------------
void CreateInfoWindow(HWND hwndParent)
{
// Create and register the window class
WNDCLASSEX wcInfo = {sizeof(WNDCLASSEX),
CS_HREDRAW | CS_VREDRAW,
InfoWindowProc,
0,
0,
GetModuleHandle(NULL),
NULL,
NULL,
(HBRUSH)(GetStockObject(WHITE_BRUSH)),
NULL,
"Info",
NULL };
RegisterClassEx( &wcInfo );
// Create the application's info window
g_hwndInfo = CreateWindow("Info",
"Previous generation's best four phenotypes",
WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
GetSystemMetrics(SM_CXSCREEN)/2,
GetSystemMetrics(SM_CYSCREEN)/2 - CParams::WindowHeight/2,
CParams::InfoWindowWidth,
CParams::InfoWindowHeight,
hwndParent,
NULL,
wcInfo.hInstance,
NULL );
// Show the info
ShowWindow(g_hwndInfo, SW_SHOWDEFAULT);
UpdateWindow(g_hwndInfo);
//give the info window's handle to the controller
g_pController->PassInfoHandle(g_hwndInfo);
return;
}
//-----------------------------------WinMain-----------------------------------------
// Entry point for our windows application
//-----------------------------------------------------------------------------------
int WINAPI WinMain( HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
WNDCLASSEX winclass;
HWND hwnd;
MSG msg;
//load in the parameters for the program
if (!g_Params.Initialize())
{
return false;
}
// first fill in the window class stucture
winclass.cbSize = sizeof(WNDCLASSEX);
winclass.style = CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc= WindowProc;
winclass.cbClsExtra= 0;
winclass.cbWndExtra= 0;
winclass.hInstance= hinstance;
winclass.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(IDI_ICON1));
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground= NULL;
winclass.lpszMenuName= NULL;
winclass.lpszClassName= szWindowClassName;
winclass.hIconSm = LoadIcon(hinstance, MAKEINTRESOURCE(IDI_ICON1));
// register the window class
if (!RegisterClassEx(&winclass))
{
MessageBox(NULL, "Error Registering Class!", "Error", 0);
return 0;
}
// create the window (one that cannot be resized)
if (!(hwnd = CreateWindowEx(NULL,
szWindowClassName,
szApplicationName,
WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
GetSystemMetrics(SM_CXSCREEN)/2 - CParams::WindowWidth,
GetSystemMetrics(SM_CYSCREEN)/2 - CParams::WindowHeight/2,
CParams::WindowWidth,
CParams::WindowHeight,
NULL,
NULL,
hinstance,
NULL)))
{
MessageBox(NULL, "Error Creating Window!", "Error", 0);
return 0;
}
//keep a global record of the window handle
g_hwndMain = hwnd;
//create and show the info window
CreateInfoWindow(hwnd);
//Show the window
ShowWindow(hwnd, SW_SHOWDEFAULT );
UpdateWindow(hwnd);
//create a timer
CTimer timer(CParams::iFramesPerSecond);
//start the timer
timer.Start();
// Enter the message loop
bool bDone = FALSE;
while(!bDone)
{
while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if( msg.message == WM_QUIT )
{
// Stop loop if it's a quit message
bDone = TRUE;
}
else
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
if (timer.ReadyForNextFrame() || g_pController->FastRender())
{
if(!g_pController->Update())
{
//we have a problem, end app
bDone = TRUE;
}
//this will call WM_PAINT which will render our scene
InvalidateRect(hwnd, NULL, TRUE);
UpdateWindow(hwnd);
}
}//end while
// Clean up everything and exit the app
Cleanup();
UnregisterClass( szWindowClassName, winclass.hInstance );
return 0;
} // end WinMain
13. phenotype.cpp
..#include "phenotype.h"
//------------------------------------Sigmoid function------------------------
//
//----------------------------------------------------------------------------
float Sigmoid(float netinput, float response)
{
return ( 1 / ( 1 + exp(-netinput / response)));
}
//--------------------------------- ctor ---------------------------------
//
//------------------------------------------------------------------------
CNeuralNet::CNeuralNet(vector<SNeuron*> neurons,
int depth):m_vecpNeurons(neurons),
m_iDepth(depth)
{}
//--------------------------------- dtor ---------------------------------
//
//------------------------------------------------------------------------
CNeuralNet::~CNeuralNet()
{
//delete any live neurons
for (int i=0; i<m_vecpNeurons.size(); ++i)
{
if (m_vecpNeurons[i])
{
delete m_vecpNeurons[i];
m_vecpNeurons[i] = NULL;
}
}
}
//----------------------------------Update--------------------------------
// takes a list of doubles as inputs into the network then steps through
// the neurons calculating each neurons next output.
//
// finally returns a std::vector of doubles as the output from the net.
//------------------------------------------------------------------------
vector<double> CNeuralNet::Update(const vector<double> &inputs,
const run_type type)
{
//create a vector to put the outputs into
vector<double> outputs;
//if the mode is snapshot then we require all the neurons to be
//iterated through as many times as the network is deep. If the
//mode is set to active the method can return an output after
//just one iteration
int FlushCount = 0;
if (type == snapshot)
{
FlushCount = m_iDepth;
}
else
{
FlushCount = 1;
}
//iterate through the network FlushCount times
for (int i=0; i<FlushCount; ++i)
{
//clear the output vector
outputs.clear();
//this is an index into the current neuron
int cNeuron = 0;
//first set the outputs of the 'input' neurons to be equal
//to the values passed into the function in inputs
while (m_vecpNeurons[cNeuron]->NeuronType == input)
{
m_vecpNeurons[cNeuron]->dOutput = inputs[cNeuron];
++cNeuron;
}
//set the output of the bias to 1
m_vecpNeurons[cNeuron++]->dOutput = 1;
//then we step through the network a neuron at a time
while (cNeuron < m_vecpNeurons.size())
{
//this will hold the sum of all the inputs x weights
double sum = 0;
//sum this neuron's inputs by iterating through all the links into
//the neuron
for (int lnk=0; lnk<m_vecpNeurons[cNeuron]->vecLinksIn.size(); ++lnk)
{
//get this link's weight
double Weight = m_vecpNeurons[cNeuron]->vecLinksIn[lnk].dWeight;
//get the output from the neuron this link is coming from
double NeuronOutput =
m_vecpNeurons[cNeuron]->vecLinksIn[lnk].pIn->dOutput;
//add to sum
sum += Weight * NeuronOutput;
}
//now put the sum through the activation function and assign the
//value to this neuron's output
m_vecpNeurons[cNeuron]->dOutput =
Sigmoid(sum, m_vecpNeurons[cNeuron]->dActivationResponse);
if (m_vecpNeurons[cNeuron]->NeuronType == output)
{
//add to our outputs
outputs.push_back(m_vecpNeurons[cNeuron]->dOutput);
}
//next neuron
++cNeuron;
}
}//next iteration through the network
//the network needs to be flushed if this type of update is performed
//otherwise it is possible for dependencies to be built on the order
//the training data is presented
if (type == snapshot)
{
for (int n=0; n<m_vecpNeurons.size(); ++n)
{
m_vecpNeurons[n]->dOutput = 0;
}
}
//return the outputs
return outputs;
}
//----------------------------- TidyXSplits -----------------------------
//
// This is a fix to prevent neurons overlapping when they are displayed
//-----------------------------------------------------------------------
void TidyXSplits(vector<SNeuron*> &neurons)
{
//stores the index of any neurons with identical splitY values
vector<int> SameLevelNeurons;
//stores all the splitY values already checked
vector<double> DepthsChecked;
//for each neuron find all neurons of identical ySplit level
for (int n=0; n<neurons.size(); ++n)
{
double ThisDepth = neurons[n]->dSplitY;
//check to see if we have already adjusted the neurons at this depth
bool bAlreadyChecked = false;
for (int i=0; i<DepthsChecked.size(); ++i)
{
if (DepthsChecked[i] == ThisDepth)
{
bAlreadyChecked = true;
break;
}
}
//add this depth to the depths checked.
DepthsChecked.push_back(ThisDepth);
//if this depth has not already been adjusted
if (!bAlreadyChecked)
{
//clear this storage and add the neuron's index we are checking
SameLevelNeurons.clear();
SameLevelNeurons.push_back(n);
//find all the neurons with this splitY depth
for (int i=n+1; i<neurons.size(); ++i)
{
if (neurons[i]->dSplitY == ThisDepth)
{
//add the index to this neuron
SameLevelNeurons.push_back(i);
}
}
//calculate the distance between each neuron
double slice = 1.0/(SameLevelNeurons.size()+1);
//separate all neurons at this level
for (i=0; i<SameLevelNeurons.size(); ++i)
{
int idx = SameLevelNeurons[i];
neurons[idx]->dSplitX = (i+1) * slice;
}
}
}//next neuron to check
}
//----------------------------- DrawNet ----------------------------------
//
// creates a representation of the ANN on a device context
//
//------------------------------------------------------------------------
void CNeuralNet::DrawNet(HDC &surface, int Left, int Right, int Top, int Bottom)
{
//the border width
const int border = 10;
//max line thickness
const int MaxThickness = 5;
TidyXSplits(m_vecpNeurons);
//go through the neurons and assign x/y coords
int spanX = Right - Left;
int spanY = Top - Bottom - (2*border);
for (int cNeuron=0; cNeuron<m_vecpNeurons.size(); ++cNeuron)
{
m_vecpNeurons[cNeuron]->iPosX = Left + spanX*m_vecpNeurons[cNeuron]->dSplitX;
m_vecpNeurons[cNeuron]->iPosY = (Top - border) - (spanY * m_vecpNeurons[cNeuron]->dSplitY);
}
//create some pens and brushes to draw with
HPEN GreyPen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
HPEN RedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
HPEN GreenPen = CreatePen(PS_SOLID, 1, RGB(0, 200, 0));
HPEN OldPen = NULL;
//create a solid brush
HBRUSH RedBrush = CreateSolidBrush(RGB(255, 0, 0));
HBRUSH OldBrush = NULL;
OldPen = (HPEN) SelectObject(surface, RedPen);
OldBrush = (HBRUSH)SelectObject(surface, GetStockObject(HOLLOW_BRUSH));
//radius of neurons
int radNeuron = spanX/60;
int radLink = radNeuron * 1.5;
//now we have an X,Y pos for every neuron we can get on with the
//drawing. First step through each neuron in the network and draw
//the links
for (cNeuron=0; cNeuron<m_vecpNeurons.size(); ++cNeuron)
{
//grab this neurons position as the start position of each
//connection
int StartX = m_vecpNeurons[cNeuron]->iPosX;
int StartY = m_vecpNeurons[cNeuron]->iPosY;
//is this a bias neuron? If so, draw the link in green
bool bBias = false;
if (m_vecpNeurons[cNeuron]->NeuronType == bias)
{
bBias = true;
}
//now iterate through each outgoing link to grab the end points
for (int cLnk=0; cLnk<m_vecpNeurons[cNeuron]->vecLinksOut.size(); ++ cLnk)
{
int EndX = m_vecpNeurons[cNeuron]->vecLinksOut[cLnk].pOut->iPosX;
int EndY = m_vecpNeurons[cNeuron]->vecLinksOut[cLnk].pOut->iPosY;
//if link is forward draw a straight line
if( (!m_vecpNeurons[cNeuron]->vecLinksOut[cLnk].bRecurrent) && !bBias)
{
int thickness = (int)(fabs(m_vecpNeurons[cNeuron]->vecLinksOut[cLnk].dWeight));
Clamp(thickness, 0, MaxThickness);
HPEN Pen;
//create a yellow pen for inhibitory weights
if (m_vecpNeurons[cNeuron]->vecLinksOut[cLnk].dWeight <= 0)
{
Pen = CreatePen(PS_SOLID, thickness, RGB(240, 230, 170));
}
//grey for excitory
else
{
Pen = CreatePen(PS_SOLID, thickness, RGB(200, 200, 200));
}
HPEN tempPen = (HPEN)SelectObject(surface, Pen);
//draw the link
MoveToEx(surface, StartX, StartY, NULL);
LineTo(surface, EndX, EndY);
SelectObject(surface, tempPen);
DeleteObject(Pen);
}
else if( (!m_vecpNeurons[cNeuron]->vecLinksOut[cLnk].bRecurrent) && bBias)
{
SelectObject(surface, GreenPen);
//draw the link
MoveToEx(surface, StartX, StartY, NULL);
LineTo(surface, EndX, EndY);
}
//recurrent link draw in red
else
{
if ((StartX == EndX) && (StartY == EndY))
{
int thickness = (int)(fabs(m_vecpNeurons[cNeuron]->vecLinksOut[cLnk].dWeight));
Clamp(thickness, 0, MaxThickness);
HPEN Pen;
//blue for inhibitory
if (m_vecpNeurons[cNeuron]->vecLinksOut[cLnk].dWeight <= 0)
{
Pen = CreatePen(PS_SOLID, thickness, RGB(0,0,255));
}
//red for excitory
else
{
Pen = CreatePen(PS_SOLID, thickness, RGB(255, 0, 0));
}
HPEN tempPen = (HPEN)SelectObject(surface, Pen);
//we have a recursive link to the same neuron draw an ellipse
int x = m_vecpNeurons[cNeuron]->iPosX ;
int y = m_vecpNeurons[cNeuron]->iPosY - (1.5 * radNeuron);
Ellipse(surface, x-radLink, y-radLink, x+radLink, y+radLink);
SelectObject(surface, tempPen);
DeleteObject(Pen);
}
else
{
int thickness = (int)(fabs(m_vecpNeurons[cNeuron]->vecLinksOut[cLnk].dWeight));
Clamp(thickness, 0, MaxThickness);
HPEN Pen;
//blue for inhibitory
if (m_vecpNeurons[cNeuron]->vecLinksOut[cLnk].dWeight <= 0)
{
Pen = CreatePen(PS_SOLID, thickness, RGB(0,0,255));
}
//red for excitory
else
{
Pen = CreatePen(PS_SOLID, thickness, RGB(255, 0, 0));
}
HPEN tempPen = (HPEN)SelectObject(surface, Pen);
//draw the link
MoveToEx(surface, StartX, StartY, NULL);
LineTo(surface, EndX, EndY);
SelectObject(surface, tempPen);
DeleteObject(Pen);
}
}
}
}
//now draw the neurons and their IDs
SelectObject(surface, RedBrush);
SelectObject(surface, GetStockObject(BLACK_PEN));
for (cNeuron=0; cNeuron<m_vecpNeurons.size(); ++cNeuron)
{
int x = m_vecpNeurons[cNeuron]->iPosX;
int y = m_vecpNeurons[cNeuron]->iPosY;
//display the neuron
Ellipse(surface, x-radNeuron, y-radNeuron, x+radNeuron, y+radNeuron);
}
//cleanup
SelectObject(surface, OldPen);
SelectObject(surface, OldBrush);
DeleteObject(RedPen);
DeleteObject(GreyPen);
DeleteObject(GreenPen);
DeleteObject(OldPen);
DeleteObject(RedBrush);
DeleteObject(OldBrush);
}
14.utils.cpp
#include "utils.h"
#include <math.h>
- 神经网络深入 ,源码
- 深入探究递归神经网络
- 深入理解卷积神经网络
- 深入理解LSTM神经网络
- 神经网络深入(连载9)神经网络类
- 循环神经网络源码剖析
- 神经网络入门 ,源码1
- 神经网络入门 ,源码2
- 神经网络入门 ,源码3
- 神经网络入门 ,源码4
- 神经网络入门 ,源码5
- 神经网络入门 ,源码6
- 神经网络入门 ,源码7
- 神经网络入门 ,源码8
- 人工神经网络的深入理解
- 人工神经网络的深入理解
- 循环神经网络简单到深入
- 深度|人工神经网络深入分析
- 安装交叉编译工具arm-none-linux-gnueabi-gcc——Linux上编译在android上运行的c程序
- C++11 中值得关注的几大变化(详解)
- 类中成员函数调用问题
- armeabi与armeabi-v7a的区别,绝对干货!
- 【Android】Android清除本地数据缓存代码
- 神经网络深入 ,源码
- DBoW2算法原理介绍
- tensor理解
- 编写可读性代码的艺术(一)
- Linux系统--进程地址空间之存储器区域
- 死锁
- tensorflow学习笔记十六:tensorflow官方文档学习 Mandelbrot Set
- linux调试工具
- 继承关系下的构造器风云