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 * * -- CROSS-OVER CREATION -- * @father First parent * @mother Second parent * * */ public function __construct(){ /* (1) Get arguments */ $argv = func_get_args(); $argc = count($argv); /* (2) If RandomCreation */ 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]); /* (3) If CrossoverCreation */ else if( $argc > 1 && $argv[0] instanceof Genome && $argv[1] instanceof Genome ) $this->construct_crossover($argv[0], $argv[1]); /* (4) If InheritanceCreation (clone) */ else if( $argc > 0 && $argv[0] instanceof Genome ) $this->construct_inheritance($argv[0]); /* (5) If no match */ else throw new \Exception('Invalid Genome constructor\'s arguments.'); /* (6) Default values */ $this->fitness = null; $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, $neurons, $inN, $outN){ /* (1) Checks parameters */ if( abs(intval($layers)) !== $layers || abs(intval($neurons)) !== $neurons || abs(intval($inN)) !== $inN || abs(intval($outN)) !== $outN ) return false; // set layers $this->layers = $layers; /* (2) Store number of neurons */ $this->neurons = $neurons; $this->inputN = $inN; $this->outputN = $outN; /* (3) Creating random synapses */ $this->synapses = []; for( $i = 0, $l = $neurons*($inN+$outN+$neurons*$layers) ; $i < $l ; $i++ ) $this->synapses[$i] = rand(self::MIN, self::MAX) / self::MAX; // Success status return true; } /* BUILDS A Genome BASED ON A PARENT * * @parent Parent genome to clone into children * * @return created If cloned Genome has been created successfully * */ private function construct_inheritance($parent=null){ /* (1) Checks parent type */ if( !($parent instanceof Genome) ) return false; /* (2) Clones into this Genome */ $this->layers = $parent->layers; $this->neurons = $parent->neurons; $this->synapses = array_slice($parent->synapses, 0); // Success state return true; } /* BUILDS A Genome BASED ON TWO PARENTS * * @father First parent ($father) * @mother Second parent ($mother) * * @return created If crossed-over Genome has been created successfully * */ private function construct_crossover($father=null, $mother=null){ /* (1) Checks parent type */ if( !($father instanceof Genome) || !($mother instanceof Genome) ) return false; /* (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->outputN; /* (5) Do random crossover for synapses */ $this->synapses = []; for( $i = 0, $l = count($mother->synapses) ; $i < $l ; $i++ ) if( !!rand(0,1) ) $this->synapses[$i] = $father->synapses[$i]; else $this->synapses[$i] = $mother->synapses[$i]; // Success state return true; } /************************************************ **** 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 \Exception('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 \Exception('Wrong argument for specifying Genome\'s fitness.'); /* (2) Set fitness */ $this->fitness = floatval($fitness); } /************************************************ **** Getter **** ************************************************/ /* RETURNS THE FITNESS OF THE GENOME * * @return fitness Current fitness (or NULL if not defined) * */ public function getFitness(){ return $this->fitness; } /************************************************ **** Genome Actions **** ************************************************/ /* APPLIES A MUTATION ON THE Genome WITH A SPECIFIC @threshold * * @threshold Mutation threshold * */ public function mutation($threshold=1){ /* (1) Checks @threshold argument */ if( !is_numeric($threshold) || $threshold < 0 || $threshold > 1 ) throw new \Exception('Invalid threshold for Genome mutation.'); /* (2) Calculates how many neurons/synapses to mutate */ $synapsesMutations = round( (count($this->synapses) - 1) * rand(0, $threshold*self::MAX)/self::MAX ); /* (3) Choose random synapses' indexes */ $iSynapses = []; while( count($iSynapses) < $synapsesMutations ){ $r = rand(0, count($this->synapses)-1); if( !in_array($r, $iSynapses) ) $iSynapses[] = $r; } /* (4) Update chosen synapses */ for( $i = 0, $l = count($iSynapses) ; $i < $l ; $i++ ) $this->synapses[$iSynapses[$i]] = rand(self::MIN, self::MAX) / self::MAX; } /* CALCULATES THE OUTPUT OF THE GENOME * * @input Input for which we want fitness * @callback [OPTIONAL] Callback function * * @note: will call @callback function with INPUTS as 1st argument and OUTPUTS as 2nd arguments * */ public function train($input=null, $callback=null){ /* (1) Checks @input argument */ if( !is_array($input) || count($input) !== $this->inputN ) throw new \Exception('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), array_fill(0, $this->outputN, 0) ); /* (2) Set temporary synapses data */ $synapses = $this->synapses; /* [2] Calculates output =========================================================*/ /* (1) For each hidden layer ---------------------------------------------------------*/ foreach($this->layersIterator() as $lr=>$l){ /* (2) For each neuron of this layer ---------------------------------------------------------*/ foreach($this->neuronsIterator($l) as $nr=>$n){ $neurons[$n] = 0; /* (3) For each synapse between current neuron and last layer ---------------------------------------------------------*/ 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"; } // if( $l == 1 ) $neurons[$n] /= $this->inputN; // else $neurons[$n] /= $this->neurons; } } /* [3] Callback the output layer's values =========================================================*/ 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-1)); 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 **** ************************************************/ /* SERIALIZES A Genome * * @return serialized Serialized representation of the Genome * */ public function serialize(){ /* (1) Initialize result */ $csv = ''; /* (2) Adds global attributes */ $csv .= $this->layers .','. $this->inputN .','. $this->neurons .','. $this->outputN .';'; /* (3) Adds synapses data */ $csv .= implode(',', $this->synapses); return $csv; } /* BUILDS A Genome BASED ON HIS SERIALIZED REPRESENTATION * * @serialized Serialized representation of a Genome * */ public function unserialize($serialized){ /* (1) Segmenting data */ $segments = explode(';', trim($serialized) ); // Manage segmentation error if( count($segments) < 2 ) throw new \Exception('Format error during Genome unserialization.'); /* (2) Get global attributes */ $globals = explode(',', $segments[0]); if( count($globals) < 4 ) throw new \Exception('Format error during Genome unserialization.'); $this->layers = intval($globals[0]); $this->inputN = intval($globals[1]); $this->neurons = intval($globals[2]); $this->outputN = intval($globals[3]); /* (3) Get synapses values */ $this->synapses = explode(',', $segments[1]); } } /************************************************ **** USE CASE **** ************************************************/ $use_case = false; if( $use_case ){ /* (1) Basic Creation */ $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 /* (3) Section Title */ $b->mutation(0.3); // @b has now mutated with a threshold of 30% /* (4) Cross-over (father+mother) */ $c = new Genome($a, $b); // @c is a randomly-done mix of @a and @b } ?>