163 lines
4.0 KiB
PHP
163 lines
4.0 KiB
PHP
<?php
|
|
|
|
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;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
private $sigmas;
|
|
|
|
/**
|
|
* @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
|
|
* @param float $desiredError
|
|
* @param int $maxIterations
|
|
*/
|
|
public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000)
|
|
{
|
|
$samplesCount = count($samples);
|
|
|
|
for ($i = 0; $i < $maxIterations; ++$i) {
|
|
$resultsWithinError = $this->trainSamples($samples, $targets, $desiredError);
|
|
|
|
if ($resultsWithinError === $samplesCount) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @param array $sample
|
|
* @param array $target
|
|
*/
|
|
private function trainSample(array $sample, array $target)
|
|
{
|
|
$this->network->setInput($sample)->getOutput();
|
|
$this->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) {
|
|
$sigma = $this->getSigma($neuron, $target, $key, $i == $layersNumber);
|
|
foreach ($neuron->getSynapses() as $synapse) {
|
|
$synapse->changeWeight($this->theta * $sigma * $synapse->getNode()->getOutput());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param Neuron $neuron
|
|
* @param array $target
|
|
* @param int $key
|
|
* @param bool $lastLayer
|
|
*
|
|
* @return float
|
|
*/
|
|
private function getSigma(Neuron $neuron, array $target, int $key, bool $lastLayer): float
|
|
{
|
|
$neuronOutput = $neuron->getOutput();
|
|
$sigma = $neuronOutput * (1 - $neuronOutput);
|
|
|
|
if ($lastLayer) {
|
|
$sigma *= ($target[$key] - $neuronOutput);
|
|
} else {
|
|
$sigma *= $this->getPrevSigma($neuron);
|
|
}
|
|
|
|
$this->sigmas[] = new Sigma($neuron, $sigma);
|
|
|
|
return $sigma;
|
|
}
|
|
|
|
/**
|
|
* @param Neuron $neuron
|
|
*
|
|
* @return float
|
|
*/
|
|
private function getPrevSigma(Neuron $neuron): float
|
|
{
|
|
$sigma = 0.0;
|
|
|
|
foreach ($this->sigmas as $neuronSigma) {
|
|
$sigma += $neuronSigma->getSigmaForNeuron($neuron);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|