From 66d029e94fb6bb024dee5781504be0d32011e651 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 10 Aug 2016 22:43:47 +0200 Subject: [PATCH] implement and test Backpropagation training --- src/Phpml/NeuralNetwork/Network.php | 2 + .../NeuralNetwork/Network/LayeredNetwork.php | 18 ++- .../Training/Backpropagation.php | 114 ++++++++++++++++++ .../Training/Backpropagation/Sigma.php | 46 +++++++ .../Training/BackpropagationTest.php | 29 +++++ 5 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php create mode 100644 tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php diff --git a/src/Phpml/NeuralNetwork/Network.php b/src/Phpml/NeuralNetwork/Network.php index 269351f..a03b8b6 100644 --- a/src/Phpml/NeuralNetwork/Network.php +++ b/src/Phpml/NeuralNetwork/Network.php @@ -8,6 +8,8 @@ interface Network { /** * @param mixed $input + * + * @return self */ public function setInput($input); diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index 699c4d4..4413403 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -6,6 +6,8 @@ namespace Phpml\NeuralNetwork\Network; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network; +use Phpml\NeuralNetwork\Node\Input; +use Phpml\NeuralNetwork\Node\Neuron; abstract class LayeredNetwork implements Network { @@ -53,13 +55,27 @@ abstract class LayeredNetwork implements Network /** * @param mixed $input + * + * @return $this */ public function setInput($input) { $firstLayer = $this->layers[0]; foreach ($firstLayer->getNodes() as $key => $neuron) { - $neuron->setInput($input[$key]); + if ($neuron instanceof Input) { + $neuron->setInput($input[$key]); + } } + + foreach ($this->getLayers() as $layer) { + foreach ($layer->getNodes() as $node) { + if ($node instanceof Neuron) { + $node->refresh(); + } + } + } + + return $this; } } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index ce9f5e6..e6691e2 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -4,10 +4,33 @@ declare (strict_types = 1); namespace Phpml\NeuralNetwork\Training; +use Phpml\NeuralNetwork\Network; +use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Training; +use Phpml\NeuralNetwork\Training\Backpropagation\Sigma; class Backpropagation implements Training { + /** + * @var Network + */ + private $network; + + /** + * @var int + */ + private $theta; + + /** + * @param Network $network + * @param int $theta + */ + public function __construct(Network $network, int $theta = 1) + { + $this->network = $network; + $this->theta = $theta; + } + /** * @param array $samples * @param array $targets @@ -16,5 +39,96 @@ class Backpropagation implements Training */ public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000) { + for ($i = 0; $i < $maxIterations; ++$i) { + $resultsWithinError = $this->trainSamples($samples, $targets, $desiredError); + + if ($resultsWithinError == count($samples)) { + break; + } + } + } + + /** + * @param array $samples + * @param array $targets + * @param float $desiredError + * + * @return int + */ + private function trainSamples(array $samples, array $targets, float $desiredError): int + { + $resultsWithinError = 0; + foreach ($targets as $key => $target) { + $result = $this->network->setInput($samples[$key])->getOutput(); + + if ($this->isResultWithinError($result, $target, $desiredError)) { + ++$resultsWithinError; + } else { + $this->trainSample($samples[$key], $target); + } + } + + return $resultsWithinError; + } + + private function trainSample(array $sample, array $target) + { + $this->network->setInput($sample)->getOutput(); + + $sigmas = []; + $layers = $this->network->getLayers(); + $layersNumber = count($layers); + + for ($i = $layersNumber; $i > 1; --$i) { + foreach ($layers[$i - 1]->getNodes() as $key => $neuron) { + if ($neuron instanceof Neuron) { + $neuronOutput = $neuron->getOutput(); + $sigma = $neuronOutput * (1 - $neuronOutput) * ($i == $layersNumber ? ($target[$key] - $neuronOutput) : $this->getPrevSigma($sigmas, $neuron)); + $sigmas[] = new Sigma($neuron, $sigma); + foreach ($neuron->getSynapses() as $synapse) { + $synapse->changeWeight($this->theta * $sigma * $synapse->getNode()->getOutput()); + } + } + } + } + } + + /** + * @param Sigma[] $sigmas + * @param Neuron $forNeuron + * + * @return float + */ + private function getPrevSigma(array $sigmas, Neuron $forNeuron): float + { + $sigma = 0.0; + + foreach ($sigmas as $neuronSigma) { + foreach ($neuronSigma->getNeuron()->getSynapses() as $synapse) { + if ($synapse->getNode() == $forNeuron) { + $sigma += $synapse->getWeight() * $neuronSigma->getSigma(); + } + } + } + + return $sigma; + } + + /** + * @param array $result + * @param array $target + * @param float $desiredError + * + * @return bool + */ + private function isResultWithinError(array $result, array $target, float $desiredError) + { + foreach ($target as $key => $value) { + if ($result[$key] > $value + $desiredError || $result[$key] < $value - $desiredError) { + return false; + } + } + + return true; } } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php new file mode 100644 index 0000000..8ce397b --- /dev/null +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php @@ -0,0 +1,46 @@ +neuron = $neuron; + $this->sigma = $sigma; + } + + /** + * @return Neuron + */ + public function getNeuron() + { + return $this->neuron; + } + + /** + * @return float + */ + public function getSigma() + { + return $this->sigma; + } +} diff --git a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php new file mode 100644 index 0000000..a44c1d5 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php @@ -0,0 +1,29 @@ +train( + [[1, 0], [0, 1], [1, 1], [0, 0]], + [[1], [1], [0], [0]], + $desiredError = 0.2, + 10000 + ); + + $this->assertEquals(0, $network->setInput([1, 1])->getOutput()[0], '', $desiredError); + $this->assertEquals(0, $network->setInput([0, 0])->getOutput()[0], '', $desiredError); + $this->assertEquals(1, $network->setInput([1, 0])->getOutput()[0], '', $desiredError); + $this->assertEquals(1, $network->setInput([0, 1])->getOutput()[0], '', $desiredError); + } +}