neuralnet.php/build/neuralnetwork/core/Genome.php

327 lines
9.4 KiB
PHP

<?php
namespace neuralnetwork\core;
use filemanager\core\FileManager;
class Genome implements \Serializable{
/************************************************
**** Constants ****
************************************************/
const MIN = 0;
const MAX = 1e9;
/************************************************
**** LOCAL ATTRIBUTES ****
************************************************/
public $layers; // Number of layers
public $neurons; // Number of neurons per layer
public $synapses; // Synapses between neurons
/************************************************
**** Genome Construction ****
************************************************/
/* CONSTRUCTOR
*
* -- RANDOM CREATION --
* @layers<int> Number of layers to manage
* @neurons<int> Number of neurons per layer
*
* -- CLONING --
* @base<Genom> Genome to clone to
*
* -- CROSS-OVER CREATION --
* @father<Genome> First parent
* @mother<Genome> Second parent
*
*
*/
public function __construct(){
/* (1) Get arguments */
$argv = func_get_args();
$argc = count($argv);
/* (2) If CrossoverCreation */
if( $argc > 1 && $argv[0] instanceof Genome && $argv[1] instanceof Genome )
$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]);
/* (4) If InheritanceCreation (clone) */
else if( $argc > 0 && $argv[0] instanceof Genome )
$this->construct_inheritance($argv[0]);
/* (5) If no match */
else
throw new \Error('Invalid Genome constructor\'s arguments.');
}
/* BUILDS A Genome RANDOMLY WITH PARAMETERS
*
* @layers<int> The number of hidden layers to manage
* @neurons<int> The number neurons per layer
*
* @return created<Boolean> If Genome has been successfully created
*
*/
private function construct_new($layers=-1, $neurons=-1){
/* (1) Checks parameters */
if( abs(intval($layers)) !== $layers || abs(intval($neurons)) !== $neurons )
return false;
// set layers
$this->layers = $layers;
/* (2) Store number of neurons */
$this->neurons = $neurons;
/* (3) Creating random synapses */
$this->synapses = [];
for( $i = 0, $l = $layers*pow($neurons,2) ; $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<Genome> Parent genome to clone into children
*
* @return created<Boolean> 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<Genome> First parent ($father)
* @mother<Genome> Second parent ($mother)
*
* @return created<Boolean> 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 number of layers+neurons (same species) */
if( $father->layers !== $mother->layers || $father->neurons !== $mother->neurons )
return false;
/* (3) Set layer count */
$this->layers = $father->layers;
/* (4) Set neurons number */
$this->neurons = $father->neurons;
/* (5) Do random crossover for synapses */
$this->synapses = [];
for( $i = 0, $l = $this->layers*pow($this->neurons, 2) ; $i < $l ; $i++ )
if( !!rand(0,1) ) $this->synapses[$i] = $father->synapses[$i];
else $this->synapses[$i] = $mother->synapses[$i];
// Success state
return true;
}
/************************************************
**** Genome Actions ****
************************************************/
/* APPLIES A MUTATION ON THE Genome WITH A SPECIFIC @threshold
*
* @threshold<double> Mutation threshold
*
*/
public function mutation($threshold=0.5){
/* (1) Checks @threshold argument */
if( floatval($threshold) !== $threshold || $threshold < 0 || $threshold > 1 )
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 */
$iSynapses = [];
while( count($iSynapses) < $synapsesMutations ){
$r = rand(0, count($this->synapses)-1);
if( !in_array($r, $iSynapses) )
$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 */
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<Array> Input for which we want fitness
*
* @return output<double> Output calculated with this input
*
*/
public function process($input=null){
/* (1) Checks @input argument */
if( !is_array($input) || count($input) !== $this->neurons )
throw new \Error('Invalid @input for Genome\'s output calculation.');
/* [1] Set temporary calculation data
=========================================================*/
/* (1) Set temporary neurons data */
$neurons = array_merge( $input, array_fill(0, $this->neurons*$this->layers, 0) );
/* (2) Set temporary synapses data */
$synapses = $this->synapses;
/* [2] Calculates output
=========================================================*/
/* (1) For each hidden layer
---------------------------------------------------------*/
for( $l = 1 ; $l < $this->layers+1 ; $l++ ){
/* (2) For each neuron of this layer
---------------------------------------------------------*/
for( $n = $l*$this->neurons, $nl = ($l+1)*$this->neurons ; $n < $nl ; $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
/* 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
=========================================================*/
return $output / $this->neurons;
}
/************************************************
**** Serialization ****
************************************************/
/* SERIALIZES A Genome
*
* @return serialized<String> Serialized representation of the Genome
*
*/
public function serialize(){
/* (1) Initialize result */
$csv = '';
/* (2) Adds global attributes */
$csv .= $this->layers .','. $this->neurons .';';
/* (3) Adds synapses data */
$csv .= implode(',', $this->synapses);
return $csv;
}
/* BUILDS A Genome BASED ON HIS SERIALIZED REPRESENTATION
*
* @serialized<String> Serialized representation of a Genome
*
*/
public function unserialize($serialized){
/* (1) Segmenting data */
$segments = explode(';', $serialized);
// Manage segmentation error
if( count($segments) < 3 )
throw new \Error('Format error during Genome unserialization.');
/* (2) Get global attributes */
$global = explode(',', $segments[0]);
if( count($global) < 2 )
throw new \Error('Format error during Genome unserialization.');
$this->layers = intval($global[0]);
$this->layers = intval($global[1]);
/* (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); // 2 layers of 3 neurons each -> randomly filled
/* (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
}
?>