mirror of
https://github.com/Llewellynvdm/php-ml.git
synced 2025-01-26 08:38:32 +00:00
implement and test Backpropagation training
This commit is contained in:
parent
e5d39ee18a
commit
66d029e94f
@ -8,6 +8,8 @@ interface Network
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param mixed $input
|
* @param mixed $input
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
*/
|
*/
|
||||||
public function setInput($input);
|
public function setInput($input);
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ namespace Phpml\NeuralNetwork\Network;
|
|||||||
|
|
||||||
use Phpml\NeuralNetwork\Layer;
|
use Phpml\NeuralNetwork\Layer;
|
||||||
use Phpml\NeuralNetwork\Network;
|
use Phpml\NeuralNetwork\Network;
|
||||||
|
use Phpml\NeuralNetwork\Node\Input;
|
||||||
|
use Phpml\NeuralNetwork\Node\Neuron;
|
||||||
|
|
||||||
abstract class LayeredNetwork implements Network
|
abstract class LayeredNetwork implements Network
|
||||||
{
|
{
|
||||||
@ -53,13 +55,27 @@ abstract class LayeredNetwork implements Network
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $input
|
* @param mixed $input
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setInput($input)
|
public function setInput($input)
|
||||||
{
|
{
|
||||||
$firstLayer = $this->layers[0];
|
$firstLayer = $this->layers[0];
|
||||||
|
|
||||||
foreach ($firstLayer->getNodes() as $key => $neuron) {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,33 @@ declare (strict_types = 1);
|
|||||||
|
|
||||||
namespace Phpml\NeuralNetwork\Training;
|
namespace Phpml\NeuralNetwork\Training;
|
||||||
|
|
||||||
|
use Phpml\NeuralNetwork\Network;
|
||||||
|
use Phpml\NeuralNetwork\Node\Neuron;
|
||||||
use Phpml\NeuralNetwork\Training;
|
use Phpml\NeuralNetwork\Training;
|
||||||
|
use Phpml\NeuralNetwork\Training\Backpropagation\Sigma;
|
||||||
|
|
||||||
class Backpropagation implements Training
|
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 $samples
|
||||||
* @param array $targets
|
* @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)
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
46
src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php
Normal file
46
src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
29
tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php
Normal file
29
tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user