From e16048b569f2efea50bf5ef12ba734d3fdc4942e Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Thu, 27 Oct 2016 14:47:08 +0200 Subject: [PATCH] Management of genome's training with callback + fitness user-defined + generation&genomes management to do in NeuralNetworkCore::learn() --- build/neuralnetwork/core/Genome.php | 245 ++++++++++++++---- .../neuralnetwork/core/NeuralNetworkCore.php | 59 +---- .../core/NeuralNetworkManager.php | 114 -------- config/test_long_file.txt | 1 - public/main.php | 52 +++- 5 files changed, 235 insertions(+), 236 deletions(-) delete mode 100644 build/neuralnetwork/core/NeuralNetworkManager.php delete mode 100644 config/test_long_file.txt diff --git a/build/neuralnetwork/core/Genome.php b/build/neuralnetwork/core/Genome.php index 882a6af..d7ae481 100644 --- a/build/neuralnetwork/core/Genome.php +++ b/build/neuralnetwork/core/Genome.php @@ -15,9 +15,17 @@ /************************************************ **** LOCAL ATTRIBUTES **** ************************************************/ - public $layers; // Number of layers - public $neurons; // Number of neurons per layer - public $synapses; // Synapses between neurons + private $layers; // Number of hidden layers + + private $inputN; // Number of input neurons + private $neurons; // Number of neurons for each hidden layer + private $outputN; // Number of output neurons + + private $synapses; // Synapses between neurons + + private $callback; // callback training function + + private $fitness; // Genome's fitness /************************************************ @@ -27,8 +35,10 @@ /* CONSTRUCTOR * * -- RANDOM CREATION -- - * @layers Number of layers to manage - * @neurons Number of neurons per layer + * @layers Number of hidden layers to manage + * @neurons Number of neurons per hidden layer + * @inN Number of input neurons + * @outN Number of output neurons * * -- CLONING -- * @base Genome to clone to @@ -50,8 +60,8 @@ $this->construct_crossover($argv[0], $argv[1]); /* (3) If RandomCreation */ - else if( $argc > 1 && abs(intval($argv[0])) === $argv[0] && abs(intval($argv[1])) === $argv[1] ) - $this->construct_new($argv[0], $argv[1]); + else if( $argc > 3 && is_numeric($argv[0]) && is_numeric($argv[1]) && is_numeric($argv[2]) && is_numeric($argv[3]) ) + $this->construct_new($argv[0], $argv[1], $argv[2], $argv[3]); /* (4) If InheritanceCreation (clone) */ else if( $argc > 0 && $argv[0] instanceof Genome ) @@ -60,19 +70,23 @@ /* (5) If no match */ else throw new \Error('Invalid Genome constructor\'s arguments.'); + + $this->callback = function(){}; } /* BUILDS A Genome RANDOMLY WITH PARAMETERS * * @layers The number of hidden layers to manage * @neurons The number neurons per layer + * @inN Number of input neurons + * @outN Number of output neurons * * @return created If Genome has been successfully created * */ - private function construct_new($layers=-1, $neurons=-1){ + private function construct_new($layers, $neurons, $inN, $outN){ /* (1) Checks parameters */ - if( abs(intval($layers)) !== $layers || abs(intval($neurons)) !== $neurons ) + if( abs(intval($layers)) !== $layers || abs(intval($neurons)) !== $neurons || abs(intval($inN)) !== $inN || abs(intval($outN)) !== $outN ) return false; // set layers @@ -80,10 +94,12 @@ /* (2) Store number of neurons */ $this->neurons = $neurons; + $this->inputN = $inN; + $this->outputN = $outN; /* (3) Creating random synapses */ $this->synapses = []; - for( $i = 0, $l = $layers*pow($neurons,2) ; $i < $l ; $i++ ) + for( $i = 0, $l = $neurons*($inN+$outN+$neurons*$layers) ; $i < $l ; $i++ ) $this->synapses[$i] = rand(self::MIN, self::MAX) / self::MAX; // Success status @@ -124,19 +140,26 @@ if( !($father instanceof Genome) || !($mother instanceof Genome) ) return false; - /* (2) Checks number of layers+neurons (same species) */ - if( $father->layers !== $mother->layers || $father->neurons !== $mother->neurons ) + /* (2) Checks the attributes (same species) */ + $diffAttr = $father->layers !== $mother->layers; + $diffAttr = $diffAttr || $father->inputN !== $mother->inputN; + $diffAttr = $diffAttr || $father->neurons !== $mother->neurons; + $diffAttr = $diffAttr || $father->outputN !== $mother->outputN; + + if( $diffAttr ) return false; /* (3) Set layer count */ $this->layers = $father->layers; /* (4) Set neurons number */ + $this->inputN = $father->inputN; $this->neurons = $father->neurons; + $this->outputN = $father->inputN; /* (5) Do random crossover for synapses */ $this->synapses = []; - for( $i = 0, $l = $this->layers*pow($this->neurons, 2) ; $i < $l ; $i++ ) + for( $i = 0, $l = $this->neurons*($this->inputN+$this->outputN+$this->neurons*$this->layers) ; $i < $l ; $i++ ) if( !!rand(0,1) ) $this->synapses[$i] = $father->synapses[$i]; else $this->synapses[$i] = $mother->synapses[$i]; @@ -145,6 +168,39 @@ } + /************************************************ + **** Setters **** + ************************************************/ + + /* SETS THE CALLBACK FUNCTION + * + * @callback Callback function to train with + * + */ + public function setCallback($callback=null){ + /* (1) Checks @callback argument */ + if( !is_callable($callback) ) + throw new \Error('Wrong argument for Genome\'s callback function.'); + + /* (2) Set callback function */ + $this->callback = $callback; + } + + /* SETS THE FITNESS OF THE GENOME + * + * @fitness Calculated fitness of the genome + * + */ + public function setFitness($fitness=null){ + /* (1) Checks @fitness argument */ + if( !is_numeric($fitness) ) + throw new \Error('Wrong argument for specifying Genome\'s fitness.'); + + /* (2) Set fitness */ + $this->fitness = floatval($fitness); + } + + /************************************************ **** Genome Actions **** ************************************************/ @@ -160,19 +216,9 @@ throw new \Error('Invalid threshold for Genome mutation.'); /* (2) Calculates how many neurons/synapses to mutate */ - $neuronsMutations = round( (count($this->neurons) - 1) * $threshold ); $synapsesMutations = round( (count($this->synapses) - 1) * $threshold ); - /* (3) Choose random neurons' indexes */ - $iNeurons = []; - while( count($iNeurons) < $neuronsMutations ){ - $r = rand(0, count($this->neurons)-1); - - if( !in_array($r, $iNeurons) ) - $iNeurons[] = $r; - } - - /* (4) Choose random synapses' indexes */ + /* (3) Choose random synapses' indexes */ $iSynapses = []; while( count($iSynapses) < $synapsesMutations ){ $r = rand(0, count($this->synapses)-1); @@ -181,11 +227,7 @@ $iSynapses[] = $r; } - /* (5) Update chosen neurons */ - for( $i = 0, $l = count($iNeurons) ; $i < $l ; $i++ ) - $this->neurons[$iNeurons[$i]] = rand(self::MIN, self::MAX) / self::MAX; - - /* (6) Update chosen synapses */ + /* (4) Update chosen synapses */ for( $i = 0, $l = count($iSynapses) ; $i < $l ; $i++ ) $this->synapses[$iSynapses[$i]] = rand(self::MIN, self::MAX) / self::MAX; } @@ -193,20 +235,25 @@ /* CALCULATES THE OUTPUT OF THE GENOME * * @input Input for which we want fitness + * @callback [OPTIONAL] Callback function * - * @return output Output calculated with this input + * @note: will call @callback function with INPUTS as 1st argument and OUTPUTS as 2nd arguments * */ - public function process($input=null){ + public function train($input=null, $callback=null){ /* (1) Checks @input argument */ - if( !is_array($input) || count($input) !== $this->neurons ) - throw new \Error('Invalid @input for Genome\'s output calculation.'); + if( !is_array($input) || count($input) !== $this->inputN ) + throw new \Error('Invalid @input for Genome\'s training.'); + + /* (2) Checks optional @callback argument */ + if( is_callable($callback) ) + $this->setCallback($callback); /* [1] Set temporary calculation data =========================================================*/ /* (1) Set temporary neurons data */ - $neurons = array_merge( $input, array_fill(0, $this->neurons*$this->layers, 0) ); + $neurons = array_merge( $input, array_fill(0, $this->neurons*$this->layers, 0), array_fill(0, $this->outputN, 0) ); /* (2) Set temporary synapses data */ $synapses = $this->synapses; @@ -216,39 +263,122 @@ =========================================================*/ /* (1) For each hidden layer ---------------------------------------------------------*/ - for( $l = 1 ; $l < $this->layers+1 ; $l++ ){ + foreach($this->layersIterator() as $lr=>$l){ /* (2) For each neuron of this layer ---------------------------------------------------------*/ - for( $n = $l*$this->neurons, $nl = ($l+1)*$this->neurons ; $n < $nl ; $n++ ){ + foreach($this->neuronsIterator($l) as $nr=>$n){ $neurons[$n] = 0; /* (3) For each synapse between current neuron and last layer ---------------------------------------------------------*/ - for( $s = ($l-1)*pow($this->neurons,2), $sl = $l*pow($this->neurons,2) ; $s < $sl ; $s++ ) - $neurons[$n] += $synapses[$s] * $neurons[ floor($s/$this->neurons) ]; - // newNeuron += synapse*lastLayerNeuron + foreach($this->synapsesIterator($l, $nr) as $sr=>$s){ + // echo "current: n#$nr/l#$l = s#$s * n#$sr\n"; + // echo "calc: ${neurons[$n]} += ${synapses[$s]} * ${neurons[$sr]}\n"; + $neurons[$n] += $synapses[$s] * $neurons[$sr]; + // echo "result: ${neurons[$n]}\n\n"; + } - /* Divide the sum to make a mean */ - $neurons[$n] /= $this->neurons; } + + } - /* (4) Calculates single output - ---------------------------------------------------------*/ - $output = 0; - for( $n = ($this->layers-1)*$this->neurons, $nl = $this->layers*$this->neurons ; $n < $nl ; $n++ ) - $output += $neurons[$n]; - - - /* [3] Returns output + /* [3] Callback the output layer's values =========================================================*/ - return $output / $this->neurons; + call_user_func($this->callback, $input, array_slice($neurons, -$this->outputN) ); } + + /************************************************ + **** Utilities **** + ************************************************/ + + /* RETURNS ITERATOR THROUGH EACH LAYER + * + * @return indexes Iterator through each layer (relative->absolute) + * + */ + private function layersIterator(){ + for( $l = 1 ; $l < $this->layers+2 ; $l++ ) + yield $l => $l; + } + + /* RETURNS AN ITERATOR ON THE NEURONS OF A LAYER + * + * @layer Layer to browse + * + * @return indexes Iterator through the neurons indexes of the layer (relative->absolute) + * + */ + private function neuronsIterator($layer){ + /* (1) Formats @layer argument */ + if( $layer < 0 ) $layer = 0; + if( $layer > $this->layers+1 ) $layer = $this->layers+1; + + $layer = intval($layer); + + /* (2) If between input/hidden */ + if( $layer === 0 ){ + $offset = 0; + for( $n = $offset, $nl = $this->inputN ; $n < $nl ; $n++ ) + yield $n-$offset => $n; + + /* (3) If between hidden/output */ + }else if( $layer == $this->layers+1 ){ + $offset = $this->inputN + $this->neurons * $this->layers; + for( $n = $offset, $nl = $this->inputN + $this->neurons * $this->layers + $this->outputN ; $n < $nl ; $n++ ) + yield $n-$offset => $n; + + /* (2) If in between hidden layer */ + }else{ + $offset = $this->inputN + $this->neurons * ($layer-1); + for( $n = $offset, $nl = $this->inputN+$this->neurons*$layer ; $n < $nl ; $n++ ) + yield $n-$offset => $n; + } + } + + /* RETURNS AN ITERATOR ON THE SYNAPSES BETWEEN A NEURON AND ITS PREVIOUS LAYER'S NEURONS + * + * @layer Destination layer + * @neuron Destination neuron + * + * @return indexes Iterator through the synapses indexes between the destination neuron and the source layer's neurons (neuronIndex->SynapseIndex) + * + */ + private function synapsesIterator($layer, $neuron){ + /* (1) Formats @layer argument */ + if( $layer < 1 ) $layer = 1; + if( $layer > $this->layers+1 ) $layer = $this->layers+1; + + $layer = intval($layer); + $prev = $layer-1; + + /* (2) If between input/hidden */ + if( $layer === 1 ){ + $offset = 0; + for( $s = $offset+$neuron, $sl = $this->inputN*$this->neurons ; $s < $sl ; $s += $this->neurons ) + yield ($s-$offset-$neuron)/$this->neurons => $s; + + + /* (3) If between hidden/output */ + }else if( $layer == $this->layers+1 ){ + $offset = $this->neurons * ($this->inputN+$this->neurons*$this->layers); + for( $s = $offset+$neuron, $sl = $offset+$this->neurons*$this->outputN ; $s < $sl ; $s += $this->outputN ) + yield $this->inputN+$this->neurons*($this->layers-1) + ($s-$offset-$neuron)/$this->outputN => $s; + + /* (2) If in between hidden layer */ + }else{ + $offset = $this->neurons * ($this->inputN+$this->neurons*($prev-1)); + for( $s = $offset+$neuron, $sl = $offset+$this->neurons*$this->neurons ; $s < $sl ; $s += $this->neurons ) + yield $this->inputN+$this->neurons*($prev-1) + ($s-$offset-$neuron)/$this->neurons => $s; + } + } + + /************************************************ **** Serialization **** ************************************************/ @@ -263,7 +393,7 @@ $csv = ''; /* (2) Adds global attributes */ - $csv .= $this->layers .','. $this->neurons .';'; + $csv .= $this->layers .','. $this->inputN .','. $this->neurons .','. $this->outputN .';'; /* (3) Adds synapses data */ $csv .= implode(',', $this->synapses); @@ -271,7 +401,6 @@ return $csv; } - /* BUILDS A Genome BASED ON HIS SERIALIZED REPRESENTATION * * @serialized Serialized representation of a Genome @@ -286,13 +415,15 @@ throw new \Error('Format error during Genome unserialization.'); /* (2) Get global attributes */ - $global = explode(',', $segments[0]); + $globals = explode(',', $segments[0]); - if( count($global) < 2 ) + if( count($globals) < 4 ) throw new \Error('Format error during Genome unserialization.'); - $this->layers = intval($global[0]); - $this->layers = intval($global[1]); + $this->layers = intval($globals[0]); + $this->inputN = intval($globals[1]); + $this->layers = intval($globals[2]); + $this->outputN = intval($globals[3]); /* (3) Get synapses values */ $this->synapses = explode(',', $segments[1]); @@ -310,7 +441,7 @@ if( $use_case ){ /* (1) Basic Creation */ - $a = new Genome(2, 3); // 2 layers of 3 neurons each -> randomly filled + $a = new Genome(2, 3, 3, 2); // 2 layers of 3 neurons each -> randomly filled + 3 input neurons + 2 output neurons /* (2) Inheritance */ $b = new Genome($a); // Clone of @a diff --git a/build/neuralnetwork/core/NeuralNetworkCore.php b/build/neuralnetwork/core/NeuralNetworkCore.php index 4d777c4..d8b6c99 100644 --- a/build/neuralnetwork/core/NeuralNetworkCore.php +++ b/build/neuralnetwork/core/NeuralNetworkCore.php @@ -17,7 +17,6 @@ private $numHid; // Number of hidden layer(s) private $numNeu; // Number of neurons for each hidden layer - private $max; // max values for input and output neurons private $storage; // path to storage /************************************************ @@ -221,63 +220,9 @@ **** Sample Setters **** ************************************************/ - /* SET SAMPLE INPUT/OUTPUT MAX VALUES - * - * @input Sample input-ordered-like max values - * @output Sample output-ordered-like max values - * - */ - public function setMaxValues($input, $output){ - if( !is_array($input) || !is_array($output) ) return; - - $this->max = [ - 'input' => $input, - 'output' => $output, - ]; - } - /* ADD SAMPLE TO THE NEURAL NETWORK - * - * @input Sample's input values - * @output Sample's output values - * - * @return added If sample has been added - * - */ public function addSample($input, $output){ - /* [1] Check min/max values - =========================================================*/ - /* (1) If not set yet */ - if( is_null($this->max) ) - throw new Error('You must use setMaxValues() before adding samples.'); - - /* (2) Checking each input */ - foreach($input as $i=>$value){ - // {2.1} If no max for this value // - if( !isset($this->max['input'][$i]) ) - return false; - - // {2.2} Checks value // - if( $value > $this->max['input'][$i] ) - return false; - } - - /* (3) Checking each output */ - foreach($output as $i=>$value){ - // {2.1} If no max for this value // - if( !isset($this->max['output'][$i]) ) - return false; - - // {2.2} Checks value // - if( $value > $this->max['output'][$i] ) - return false; - } - - /* [2] Adds values - =========================================================*/ FileManager::append( $this->storage[1]['filename'], implode(',',$input) .';'. implode(',',$output) ); - - return true; } @@ -344,7 +289,7 @@ /* [2] Creates the neural network =========================================================*/ // best of last generation - $best = new Genome($this->numHid, $this->numNeu); + $best = null; /* (1) For each generation @@ -401,7 +346,6 @@ $json['numHid'] = $this->numHid; $json['numNeu'] = $this->numNeu; - $json['max'] = $this->max; $json['storage'] = $this->storage; /* (3) Returns serialized string */ @@ -430,7 +374,6 @@ $this->numHid = $json['numHid']; $this->numNeu = $json['numNeu']; - $this->max = $json['max']; $this->storage = $json['storage']; } diff --git a/build/neuralnetwork/core/NeuralNetworkManager.php b/build/neuralnetwork/core/NeuralNetworkManager.php deleted file mode 100644 index 3319c2b..0000000 --- a/build/neuralnetwork/core/NeuralNetworkManager.php +++ /dev/null @@ -1,114 +0,0 @@ -setFitnessEnd(0.89); - - /* (3) Specifies that it will keep the 2 best genomes of each generation */ - $neunet->setKeptGenomes(2); - - /* (4) Specifies that for each mutation, it must be at maximum 30% different */ - $neunet->setMutationThreshold(0.4); - - /* (5) Specifies the number of hidden layers */ - $neunet->setHiddenLayersCount(2); - - /* (6) Specifies the number of neurons per hidden layer */ - $neunet->setHiddenLayerNeuronsCount(4); - - - /* (2) Let's learn - ---------------------------------------------------------*/ - /* (1) Set dataset sample maximum values */ - $neunet->setMaxValues([2,2,7], [10, 10]); - // input output - - /* (2) Setting our example dataset */ - $neunet->addSample([1.9, 2, 5.3], [6, 0]); - $neunet->addSample([1.2, 1.2, 0.1], [0, 2]); - $neunet->addSample([0, 0, 0], [5, 5]); - - /* (3) Launch learning routine with callback function */ - $neunet->learn(function($ctx){ // callback with @ctx the current context - echo 'generation '. $ctx['generation'] .'/'. $ctx['generations'] ."\n"; - echo 'genome '. $ctx['genome'] .'/'. $ctx['genomes'] ."\n"; - echo 'fitness: '. $ctx['fitness'] .'/'. $ctx['fitness_end']. "\n"; - }); - - - /* (3) What to do next ? - ---------------------------------------------------------*/ - /* (1) Store your data */ - $neunet->store('/some/path/to/storage'); - - /* (2) [HIDDEN] will create those 2 files */ - '/some/path/to/storage.nn'; // containing neural network configuration - '/somt/path/to/storage.ln'; // containing neural network learnt values & weights - '/some/path/to/storage.ex'; // containing neural network samples - - - /* (4) And next ? - ---------------------------------------------------------*/ - /* (1) Load your stored neural network */ - $trainednn = NeuralNetwork::load('/some/path/to/storage'); - - /* (2) Use it to guess output, with well-formed input */ - $guessed = $trainednn->guess([1.2, 0.9, 6.1]); - - /* (3) And correct it if needed*/ - echo "1: ". $guessed[0] ."\n"; - echo "2: ". $guessed[2] ."\n"; - echo "is it correct ?\n"; - // .. some code to manage and read correct output if it was wrong - $trainednn->addSample([1.2, 0.9, 6.1], [9, 0]); - - // You can now relaunch a new neural network's learning - // or You can just add a few generations to evolve your neural network - - } - -?> diff --git a/config/test_long_file.txt b/config/test_long_file.txt deleted file mode 100644 index 7ec9a4b..0000000 --- a/config/test_long_file.txt +++ /dev/null @@ -1 +0,0 @@ -aa \ No newline at end of file diff --git a/public/main.php b/public/main.php index d9b8bdc..ff22ac0 100644 --- a/public/main.php +++ b/public/main.php @@ -6,19 +6,18 @@ use \neuralnetwork\core\NeuralNetwork; use \filemanager\core\FileManager; + function behaviour($abc){ + return [($abc[0] & $abc[1]), $abc[1] | $abc[2]]; + } if( false && 'test_creating_dataset' ){ - function behaviour($abc){ - return [($abc[0] & $abc[1]), $abc[1] | $abc[2]]; - } echo "Welcome to neural-network.php\n"; echo "-----------------------------\n"; $nn = NeuralNetwork::create(50, 100); - $nn->setMaxValues([1, 1, 1], [1, 1]); $nn->setHiddenLayersCount(2); $nn->setHiddenLayerNeuronsCount(3); @@ -30,6 +29,14 @@ $d = [1, 0, 1]; $nn->addSample($d, behaviour($d)); $d = [1, 1, 0]; $nn->addSample($d, behaviour($d)); $d = [1, 1, 1]; $nn->addSample($d, behaviour($d)); + $d = [0, 0, 0]; $nn->addSample($d, behaviour($d)); + $d = [0, 0, 2]; $nn->addSample($d, behaviour($d)); + $d = [0, 2, 0]; $nn->addSample($d, behaviour($d)); + $d = [0, 2, 2]; $nn->addSample($d, behaviour($d)); + $d = [2, 0, 0]; $nn->addSample($d, behaviour($d)); + $d = [2, 0, 2]; $nn->addSample($d, behaviour($d)); + $d = [2, 2, 0]; $nn->addSample($d, behaviour($d)); + $d = [2, 2, 2]; $nn->addSample($d, behaviour($d)); $nn->store('test/test1', true); @@ -67,9 +74,42 @@ if( true ){ - $g = new Genome(2, 3); + $g = new Genome(2, 3, 3, 2); + $fitness = 0; + $g->setCallback(function($input, $output){ + global $fitness; + echo "callback output: ".round($output[0]).", ".round($output[1])."\n"; + $result = behaviour($input); + if( $output[0] == $result[0] ) + $fitness++; - echo $g->process([1, 1, 0]); + if( $output[1] == $result[1] ) + $fitness++; + }); + + echo $g->train([0, 0, 0]); + echo $g->train([0, 0, 1]); + echo $g->train([0, 1, 0]); + echo $g->train([0, 1, 1]); + echo $g->train([1, 0, 0]); + echo $g->train([1, 0, 1]); + echo $g->train([1, 1, 0]); + echo $g->train([1, 1, 1]); + + + + echo $g->train([0, 0, 0]); + echo $g->train([0, 0, 2]); + echo $g->train([0, 2, 0]); + echo $g->train([0, 2, 2]); + echo $g->train([2, 0, 0]); + echo $g->train([2, 0, 2]); + echo $g->train([2, 2, 0]); + echo $g->train([2, 2, 2]); + + echo "fitness: $fitness\n"; + $g->setFitness($fitness); + echo $g->serialize(); } // REWRITE TEST