home *** CD-ROM | disk | FTP | other *** search
/ AI Game Programming Wisdom / AIGameProgrammingWisdom.iso / SourceCode / 11 Learning / 08 Manslow / CMLP.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2001-10-01  |  16.0 KB  |  551 lines

  1. //Tanks
  2. //Copyright John Manslow
  3. //29/09/2001
  4.  
  5. ////////////////////////////////////////////////////
  6. //Remove this include if not compiling under windows
  7. #include "stdafx.h"
  8. #define false FALSE
  9. #define new DEBUG_NEW
  10. ////////////////////////////////////////////////////
  11. #include "CMLP.h"
  12. #include "stdlib.h"
  13. #include "math.h"
  14. #include "stdio.h"
  15. #include "assert.h"
  16. #include "fstream.h"
  17. #include "time.h"
  18.  
  19. //Where backups will be made during training (guards against crashes, etc.)
  20. #define FileForBackupSaves "TrainingBackup.mlp"
  21.  
  22. CMLP::CMLP(
  23.             const unsigned long ulNewNumberOfInputs,
  24.             const unsigned long ulNewNumberOfHiddenNodes,
  25.             const unsigned long ulNewNumberOfOutputs
  26.             )
  27. {
  28.     TRACE("\t\tCreating MLP...");
  29.  
  30.     //Record the structure (number of inputs, hidden neurons, and outputs) of the network
  31.     ulNumberOfInputs=ulNewNumberOfInputs;
  32.     ulNumberOfHiddenNodes=ulNewNumberOfHiddenNodes;
  33.     ulNumberOfOutputs=ulNewNumberOfOutputs;
  34.  
  35.     //Allocate memory to store the network's "current" weights and the best weights.found during training
  36.     AllocateMemory();
  37.  
  38.     //Set the network's weights to random values and reset all variables used in training.
  39.     Reset();
  40.  
  41.     //Set these character pointers to NULL so we know they're not used yet
  42.     pTrainingStartTime=NULL;
  43.     pTrainingStartDate=NULL;
  44.  
  45.     TRACE("successful.\n");
  46. }
  47.  
  48. void CMLP::AllocateMemory(void)
  49. {
  50.     unsigned long i;
  51.     //Allocate memory to store the current values of the input to hidden layer weights and
  52.     //their best values
  53.     ppdwih=new double*[ulNumberOfHiddenNodes];
  54.     ppdBestwih=new double*[ulNumberOfHiddenNodes];
  55.     assert(ppdwih && ppdBestwih);
  56.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  57.     {
  58.         ppdwih[i]=new double[ulNumberOfInputs+1];
  59.         ppdBestwih[i]=new double[ulNumberOfInputs+1];
  60.         assert(ppdwih[i] && ppdBestwih[i]);
  61.     }
  62.  
  63.     //Do the same for the hidden to output layer weights
  64.     ppdwho=new double*[ulNumberOfOutputs];
  65.     ppdBestwho=new double*[ulNumberOfOutputs];
  66.     assert(ppdwho && ppdBestwho);
  67.     for(i=0;i<ulNumberOfOutputs;i++)
  68.     {
  69.         ppdwho[i]=new double[ulNumberOfHiddenNodes+1];
  70.         ppdBestwho[i]=new double[ulNumberOfHiddenNodes+1];
  71.         assert(ppdwho[i] && ppdBestwho[i]);
  72.     }
  73. }
  74.  
  75. void CMLP::DeallocateMemory(void)
  76. {
  77.     //Deallocate the storage used for current and best weight values.
  78.     unsigned long i;
  79.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  80.     {
  81.         delete []ppdwih[i];
  82.         delete []ppdBestwih[i];
  83.     }
  84.     delete []ppdwih;
  85.     delete []ppdBestwih;
  86.     for(i=0;i<ulNumberOfOutputs;i++)
  87.     {
  88.         delete []ppdwho[i];
  89.         delete []ppdBestwho[i];
  90.     }
  91.     delete []ppdwho;
  92.     delete []ppdBestwho;
  93.  
  94.     //If we've recorded the time and date of the start of training, delete them too.
  95.     if(pTrainingStartTime)
  96.     {
  97.         delete []pTrainingStartTime;
  98.     }
  99.     if(pTrainingStartDate)
  100.     {
  101.         delete []pTrainingStartDate;
  102.     }
  103. }
  104.  
  105. CMLP::~CMLP()
  106. {
  107.     TRACE("\t\tDestroying MLP...");
  108.     DeallocateMemory();
  109.     TRACE("successful.\n");
  110. }
  111.  
  112. void CMLP::Reset(void)
  113. {
  114.     unsigned long i,j;
  115.  
  116.     //Give the network weights random values between -1 and +1. Since this effectively resets 
  117.     //training the best recorded weights (stored in ppdBest...) are set to the new random values.
  118.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  119.     {
  120.         for(j=0;j<ulNumberOfInputs+1;j++)
  121.         {
  122.             ppdwih[i][j]=1.0*(double(rand())/double(RAND_MAX)-0.5);
  123.             ppdBestwih[i][j]=ppdwih[i][j];
  124.         }
  125.     }
  126.  
  127.     //Do the same for the hidden to output layer weights
  128.     for(i=0;i<ulNumberOfOutputs;i++)
  129.     {
  130.         for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  131.         {
  132.             ppdwho[i][j]=1.0*(double(rand())/double(RAND_MAX)-0.5);
  133.             ppdBestwho[i][j]=ppdwho[i][j];
  134.         }
  135.     }
  136.  
  137.     //Reset the best recorded error to an impossible value (error is always positive) to indicate
  138.     //that no training has taken place with the current values of the weights.
  139.     dBestError=-1.0;
  140.  
  141.     //Reset the step size to a conservative value.
  142.     dStepSize=0.001;
  143. }
  144.  
  145. //This is the function that allows the network to learn. It uses a perturbation search
  146. //to find values of the network's weights that allow it to reproduce the set of example 
  147. //input-output pairs to the desired accuracy. Each call to this function improves the network
  148. //only very slightly, so this function will often have to be called many hundreds of thousands
  149. //of times before the network is good enough.
  150. double CMLP::dTrainingStep(
  151.                             const unsigned long ulNumberOfPatternsInTrainingSet,
  152.                             double ** const ppdTrainingInputs,
  153.                             double ** const ppdTrainingTargets
  154.                             )
  155. {
  156.     //This function performs one step of a perturbation search: it randomly changes the
  157.     //neural network's weights. If the network's performance has improved, the new
  158.     //values are kept. If not, the old values are restored.
  159.     unsigned long i,j;
  160.     double dNewError;
  161.  
  162.     //If dBestError=-1, this is the first training step so we need to do some initialisation
  163.     //of the perturbation search.
  164.     if(dBestError==-1.0)
  165.     {
  166.         //Make sure we deallocate memory pointed to by these pointers (if any) before we 
  167.         //reassign them
  168.         if(pTrainingStartTime)
  169.         {
  170.             delete []pTrainingStartTime;
  171.         }
  172.         if(pTrainingStartDate)
  173.         {
  174.             delete []pTrainingStartDate;
  175.         }
  176.  
  177.         //Record time and date that training started.
  178.         pTrainingStartTime=new char[256];
  179.         _strtime(pTrainingStartTime);
  180.         pTrainingStartDate=new char[256];
  181.         _strdate(pTrainingStartDate);
  182.  
  183.         //Measure the performance of the network with the weights set to their current values.
  184.         //Since this is the only performance measurement so far, it must be the best. Store it.
  185.         dBestError=dGetPerformance(
  186.                                     ulNumberOfPatternsInTrainingSet,
  187.                                     ppdTrainingInputs,
  188.                                     ppdTrainingTargets
  189.                                     );
  190.     }
  191.  
  192.     //Perturb the network's weights by adding a random value between +dStepSize and -StepSize
  193.     //to each one.
  194.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  195.     {
  196.         for(j=0;j<ulNumberOfInputs+1;j++)
  197.         {
  198.             ppdwih[i][j]+=dStepSize*(double(rand())/double(RAND_MAX)-0.5)*2.0;
  199.         }
  200.     }
  201.  
  202.     //And for the hidden to output layer weights
  203.     for(i=0;i<ulNumberOfOutputs;i++)
  204.     {
  205.         for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  206.         {
  207.             ppdwho[i][j]+=dStepSize*(double(rand())/double(RAND_MAX)-0.5)*2.0;
  208.         }
  209.     }
  210.  
  211.     //Measure the performance of the network with the new weights
  212.     dNewError=dGetPerformance(
  213.                                 ulNumberOfPatternsInTrainingSet,
  214.                                 ppdTrainingInputs,
  215.                                 ppdTrainingTargets
  216.                                 );
  217.  
  218.     //If the performance is worse (the new error is larger)
  219.     if(dNewError>dBestError)
  220.     {
  221.         //Reduce the size of the perturbation a bit - we need to be more conservative!
  222.         dStepSize*=0.9;
  223.  
  224.         //and set the weights back to their old values
  225.         for(i=0;i<ulNumberOfHiddenNodes;i++)
  226.         {
  227.             for(j=0;j<ulNumberOfInputs+1;j++)
  228.             {
  229.                 ppdwih[i][j]=ppdBestwih[i][j];
  230.             }
  231.         }
  232.         for(i=0;i<ulNumberOfOutputs;i++)
  233.         {
  234.             for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  235.             {
  236.                 ppdwho[i][j]=ppdBestwho[i][j];
  237.             }
  238.         }
  239.     }
  240.     else
  241.     {
  242.         //Otherwise the new weights performed at least as well as the old ones, so record the
  243.         //performance of the network with the new weights,
  244.         dBestError=dNewError;
  245.  
  246.         //Increase the step size a little - we're doing well and can afford to be more 
  247.         //adventurous
  248.         dStepSize*=1.2;
  249.  
  250.         //Record the new weights as the best so far discovered
  251.         for(i=0;i<ulNumberOfHiddenNodes;i++)
  252.         {
  253.             for(j=0;j<ulNumberOfInputs+1;j++)
  254.             {
  255.                 ppdBestwih[i][j]=ppdwih[i][j];
  256.             }
  257.         }
  258.         for(i=0;i<ulNumberOfOutputs;i++)
  259.         {
  260.             for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  261.             {
  262.                 ppdBestwho[i][j]=ppdwho[i][j];
  263.             }
  264.         }
  265.  
  266.         //Save the network just in case we have a crash (or power failure or something)
  267.         Save(FileForBackupSaves);
  268.     }
  269.  
  270.     //Tell the calling function what the performance of the network currently is, so it can 
  271.     //decide whether to continue training. This function always leaves the network with the 
  272.     //best weights found so far, so there's no need to restore them externally
  273.     return dBestError;
  274. }
  275.  
  276. double *CMLP::pdGetOutputs(
  277.                             const double * const pdInputs
  278.                             )
  279. {
  280.  
  281.     unsigned long i,j;
  282.     
  283.     //Declare storage for the activities of the hidden and output neurons
  284.     double *pdah=new double[ulNumberOfHiddenNodes];
  285.     double *pdao=new double[ulNumberOfOutputs];
  286.  
  287.     //Declare storage for the amount of stimulation coming onto a neuron
  288.     double dStimulus;
  289.  
  290.     //Calculate the activity of the network's hidden neurons.
  291.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  292.     {
  293.         //Each hidden neuron receives internal stimulation:
  294.         dStimulus=ppdwih[i][0];
  295.  
  296.         //And stimulation from input neurons (the activites of which are just the inputs to the 
  297.         //network, pdInputs) via the weights connecting it to each input (ppdwih). Remember
  298.         //that pdInputs contains scaled versions of x-displacement, y-displacement and wind 
  299.         //speed.
  300.         for(j=1;j<ulNumberOfInputs+1;j++)
  301.         {
  302.             dStimulus+=ppdwih[i][j]*pdInputs[j-1];
  303.         }
  304.  
  305.         //The stimulation that a hidden neuron receives is translated into its level of activity
  306.         //by an "activation function":
  307.         pdah[i]=1.0/(1.0+exp(-dStimulus));
  308.         //The logistic function (used in the line above) is by far the most common, though almost
  309.         //any function can be used. In fact, each hidden neuron can use a different function (though
  310.         //such a network can strictly no longer be considered neural). Of course, the weights learnt during
  311.         //training are specific to the types of activation function used, so weights learnt
  312.         //by a network with logistic activation functions won't work well in one with sine functions,
  313.         //for example
  314.  
  315.     }
  316.     //The activity of the output neuron of the network is computed in
  317.     //essentially the same way as the activities of the hidden neurons, except that the
  318.     //output receives stimulation from the hidden neurons (pdah) via the hidden to output
  319.     //layer weights (ppdwho). Note that a network may have several outputs but this application
  320.     //requires only one - to represent the angle of the AI tank's barrel.
  321.     for(i=0;i<ulNumberOfOutputs;i++)
  322.     {
  323.         //Account for the neuron's internal stimulation.
  324.         dStimulus=ppdwho[i][0];
  325.  
  326.         //And that coming from the hidden neurons
  327.         for(j=1;j<ulNumberOfHiddenNodes+1;j++)
  328.         {
  329.             dStimulus+=ppdwho[i][j]*pdah[j-1];
  330.         }
  331.  
  332.         //Translate this stimulation into the activity of the output neuron using the activation 
  333.         //function:
  334.         pdao[i]=dStimulus;
  335.         //In this case, the activation function of the output neuron is just the identity:
  336.         //the output neuron's activity equals the amount of stimulation it receives. This 
  337.         //is the function normally used when a network is estimating a continuous variable 
  338.         //(like the angle of a tank's barrel). As with hidden neurons, other activation 
  339.         //functions can be used, though care should be taken to make sure that it is 
  340.         //possible for the network output to reach the desired value. e.g. the angle of 
  341.         //the AI tank's barrel that hits the player is always negative, so there'd be no 
  342.         //point in using the logistic activation function for the network outputs (because
  343.         //the output of the logistic function is always positive).
  344.     }
  345.  
  346.     //Deallocate the temporary storage that was used for the hidden neuron activities
  347.     delete []pdah;
  348.  
  349.     //Remember that we're returning a pointer to "new"ed storage, so the calling function
  350.     //must deallocate it to avoid memory leaks.
  351.     return pdao;
  352. }
  353.  
  354. double CMLP::dGetPerformance(void)
  355. {
  356.     //dBestError is computed in dTrainingStep and indicates the current performance of the
  357.     //network on the exemplar data. The function below need only be called from outside the class
  358.     //if the network's performance on some other data needs to be measured.
  359.     return dBestError;
  360. }
  361.  
  362. double CMLP::dGetPerformance(
  363.                                 const unsigned long ulNumberOfPatterns,
  364.                                 double ** const ppdInputs,
  365.                                 double ** const ppdTargets
  366.                                 )
  367. {
  368.     double dError=0.0;
  369.     double *pdOutputs;
  370.     unsigned long i,j;
  371.  
  372.     //Go through each pattern (example) in the data set and
  373.     for(i=0;i<ulNumberOfPatterns;i++)
  374.     {
  375.         //Compute the output (estimated barrel angle) produced by the network in response 
  376.         //to its inputs (x-displacement, y-displacement and wind speed)
  377.         pdOutputs=pdGetOutputs(ppdInputs[i]);
  378.         for(j=0;j<ulNumberOfOutputs;j++)
  379.         {
  380.             //Compute the squared error between the output produced by the network
  381.             //(the barrel angle it estimated as being correct) and the target output
  382.             //contained in the sample (the barrel angle that actually scored the hit)
  383.             dError+=0.5*pow(fabs(ppdTargets[i][j]-pdOutputs[j]),2.0);
  384.             //Again, multiple outputs are supported, but only one is used in this
  385.             //application. Occasionally, different error measures are employed. Using a number
  386.             //greater than 2.0 in the above equation tends to result in much less variation
  387.             //in the performance of the network accross samples in the data set, but slows 
  388.             //training down, whereas values less than 2.0 speed training up but allow the
  389.             //network to show greater variation in performance accross the data set - almost 
  390.             //ignoring some of the more difficult to learn examples. A value of 2.0 should be
  391.             //sufficient for virtually all applications. Note that if a value other than 2.0 
  392.             //is used, the termination error specified in TanksDoc must be revised accordingly.
  393.         }
  394.         //Deallocate the memory used to store the network outputs.
  395.         delete []pdOutputs;
  396.     }
  397.  
  398.     //Divide the error by the number of patterns to give the average error per sample - makes
  399.     //the error measure independent of the number of samples and hence a little more 
  400.     //interpretable.
  401.     dError/=double(ulNumberOfPatterns);
  402.  
  403.     //Return the computed error
  404.     return dError;
  405. }
  406.  
  407. int CMLP::Save(const char * const pFileName)
  408. {
  409.     unsigned long i,j;
  410.     assert(pFileName);
  411.  
  412.     //Create the output stream to save the network to.
  413.     ofstream *pOut=new ofstream(pFileName);
  414.  
  415.     //Make sure it was created and opened successfully.
  416.     if(!pOut)
  417.     {
  418.         assert(false);
  419.         return 0;
  420.     }
  421.     if(!pOut->is_open())
  422.     {
  423.         assert(false);
  424.         delete pOut;
  425.         return 0;
  426.     }
  427.     //Make sure we don't lose information.
  428.     pOut->precision(15);
  429.  
  430.     //Save all the network info:
  431.     //Its structure...
  432.     *pOut<<ulNumberOfInputs;
  433.     *pOut<<"\n";
  434.     *pOut<<ulNumberOfHiddenNodes;
  435.     *pOut<<"\n";
  436.     *pOut<<ulNumberOfOutputs;
  437.     *pOut<<"\n";
  438.  
  439.     //Its weights
  440.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  441.     {
  442.         for(j=0;j<ulNumberOfInputs+1;j++)
  443.         {
  444.             *pOut<<ppdwih[i][j];
  445.             *pOut<<"\t";
  446.         }
  447.         *pOut<<"\n";
  448.     }
  449.     for(i=0;i<ulNumberOfOutputs;i++)
  450.     {
  451.         for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  452.         {
  453.             *pOut<<ppdwho[i][j];
  454.             *pOut<<"\t";
  455.         }
  456.         *pOut<<"\n";
  457.     }
  458.  
  459.     //When training started...
  460.     *pOut<<"Training started\t";
  461.     *pOut<<pTrainingStartTime;
  462.     *pOut<<"\t";
  463.     *pOut<<pTrainingStartDate;
  464.     *pOut<<"\n";
  465.  
  466.     //the current date and time...
  467.     char *pTime=new char[256];
  468.     *pOut<<"Current time\t\t";
  469.     _strtime(pTime);
  470.     *pOut<<pTime;
  471.     *pOut<<"\t";
  472.     _strdate(pTime);
  473.     *pOut<<pTime;
  474.     *pOut<<"\n";
  475.     delete []pTime;
  476.  
  477.     //And how well the network currently performs.
  478.     *pOut<<"Performance\t\t";
  479.     *pOut<<dBestError;
  480.     *pOut<<"\n";
  481.  
  482.     //Close the file and delete the stream.
  483.     pOut->close();
  484.     delete pOut;
  485.  
  486.     //Return that the save was successful.
  487.     return 1;
  488. }
  489.  
  490. int CMLP::Load(const char * const pFileName)
  491. {
  492.     unsigned long i,j;
  493.     assert(pFileName);
  494.  
  495.     //Create a stream to load the network from
  496.     ifstream *pIn=new ifstream(pFileName,ios::nocreate);
  497.  
  498.     //Check to make sure that it was created and could be opened.
  499.     if(!pIn)
  500.     {
  501.         assert(false);
  502.         return 0;
  503.     }
  504.     if(!pIn->is_open())
  505.     {
  506.         assert(false);
  507.         delete pIn;
  508.         return 0;
  509.     }
  510.  
  511.     //Since we're about to load a new network, we should delete the storage used by the
  512.     //current one to prevent memory leaks.
  513.     DeallocateMemory();
  514.  
  515.     //Load the structure of the new network
  516.     *pIn>>ulNumberOfInputs;
  517.     *pIn>>ulNumberOfHiddenNodes;
  518.     *pIn>>ulNumberOfOutputs;
  519.  
  520.     //Allocate memory to store its weights
  521.     AllocateMemory();
  522.  
  523.     //Reset its status so that it can be trained if necessary
  524.     Reset();
  525.  
  526.     //Load in its weights
  527.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  528.     {
  529.         for(j=0;j<ulNumberOfInputs+1;j++)
  530.         {
  531.             *pIn>>ppdwih[i][j];
  532.             ppdBestwih[i][j]=ppdwih[i][j];
  533.         }
  534.     }
  535.     for(i=0;i<ulNumberOfOutputs;i++)
  536.     {
  537.         for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  538.         {
  539.             *pIn>>ppdwho[i][j];
  540.             ppdBestwho[i][j]=ppdwho[i][j];
  541.         }
  542.     }
  543.  
  544.     //Close and delete the stream.
  545.     pIn->close();
  546.     delete pIn;
  547.  
  548.     //Indicate that we've been successful.
  549.     return 1;
  550. }
  551.