mirror of
https://github.com/Llewellynvdm/php-ml.git
synced 2025-01-24 23:58:24 +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
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setInput($input);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
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