implement and test Backpropagation training

This commit is contained in:
Arkadiusz Kondas 2016-08-10 22:43:47 +02:00
parent e5d39ee18a
commit 66d029e94f
5 changed files with 208 additions and 1 deletions

View File

@ -8,6 +8,8 @@ interface Network
{
/**
* @param mixed $input
*
* @return self
*/
public function setInput($input);

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,46 @@
<?php
declare (strict_types = 1);
namespace Phpml\NeuralNetwork\Training\Backpropagation;
use Phpml\NeuralNetwork\Node\Neuron;
class Sigma
{
/**
* @var Neuron
*/
private $neuron;
/**
* @var float
*/
private $sigma;
/**
* @param Neuron $neuron
* @param float $sigma
*/
public function __construct(Neuron $neuron, $sigma)
{
$this->neuron = $neuron;
$this->sigma = $sigma;
}
/**
* @return Neuron
*/
public function getNeuron()
{
return $this->neuron;
}
/**
* @return float
*/
public function getSigma()
{
return $this->sigma;
}
}

View File

@ -0,0 +1,29 @@
<?php
declare (strict_types = 1);
namespace tests\Phpml\NeuralNetwork\Training;
use Phpml\NeuralNetwork\Network\MultilayerPerceptron;
use Phpml\NeuralNetwork\Training\Backpropagation;
class BackpropagationTest extends \PHPUnit_Framework_TestCase
{
public function testBackpropagationForXORLearning()
{
$network = new MultilayerPerceptron([2, 2, 1]);
$training = new Backpropagation($network);
$training->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);
}
}