Completed
Push — develop ( e5d39e...c506a8 )
by Arkadiusz
03:32
created

Backpropagation::isResultWithinError()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
rs 9.2
cc 4
eloc 5
nc 3
nop 3
1
<?php
2
3
declare (strict_types = 1);
4
5
namespace Phpml\NeuralNetwork\Training;
6
7
use Phpml\NeuralNetwork\Network;
8
use Phpml\NeuralNetwork\Node\Neuron;
9
use Phpml\NeuralNetwork\Training;
10
use Phpml\NeuralNetwork\Training\Backpropagation\Sigma;
11
12
class Backpropagation implements Training
13
{
14
    /**
15
     * @var Network
16
     */
17
    private $network;
18
19
    /**
20
     * @var int
21
     */
22
    private $theta;
23
24
    /**
25
     * @var array
26
     */
27
    private $sigmas;
28
29
    /**
30
     * @param Network $network
31
     * @param int     $theta
32
     */
33
    public function __construct(Network $network, int $theta = 1)
34
    {
35
        $this->network = $network;
36
        $this->theta = $theta;
37
    }
38
39
    /**
40
     * @param array $samples
41
     * @param array $targets
42
     * @param float $desiredError
43
     * @param int   $maxIterations
44
     */
45
    public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000)
46
    {
47
        for ($i = 0; $i < $maxIterations; ++$i) {
48
            $resultsWithinError = $this->trainSamples($samples, $targets, $desiredError);
49
50
            if ($resultsWithinError == count($samples)) {
51
                break;
52
            }
53
        }
54
    }
55
56
    /**
57
     * @param array $samples
58
     * @param array $targets
59
     * @param float $desiredError
60
     *
61
     * @return int
62
     */
63
    private function trainSamples(array $samples, array $targets, float $desiredError): int
64
    {
65
        $resultsWithinError = 0;
66
        foreach ($targets as $key => $target) {
67
            $result = $this->network->setInput($samples[$key])->getOutput();
68
69
            if ($this->isResultWithinError($result, $target, $desiredError)) {
70
                ++$resultsWithinError;
71
            } else {
72
                $this->trainSample($samples[$key], $target);
73
            }
74
        }
75
76
        return $resultsWithinError;
77
    }
78
79
    /**
80
     * @param array $sample
81
     * @param array $target
82
     */
83
    private function trainSample(array $sample, array $target)
84
    {
85
        $this->network->setInput($sample)->getOutput();
86
        $this->sigmas = [];
87
88
        $layers = $this->network->getLayers();
89
        $layersNumber = count($layers);
90
91
        for ($i = $layersNumber; $i > 1; --$i) {
92
            foreach ($layers[$i - 1]->getNodes() as $key => $neuron) {
93
                if ($neuron instanceof Neuron) {
94
                    $sigma = $this->getSigma($neuron, $target, $key, $i == $layersNumber);
95
                    foreach ($neuron->getSynapses() as $synapse) {
96
                        $synapse->changeWeight($this->theta * $sigma * $synapse->getNode()->getOutput());
97
                    }
98
                }
99
            }
100
        }
101
    }
102
103
    /**
104
     * @param Neuron $neuron
105
     * @param array  $target
106
     * @param int    $key
107
     * @param bool   $lastLayer
108
     *
109
     * @return float
110
     */
111
    private function getSigma(Neuron $neuron, array $target, int $key, bool $lastLayer): float
112
    {
113
        $neuronOutput = $neuron->getOutput();
114
        $sigma = $neuronOutput * (1 - $neuronOutput);
115
116
        if ($lastLayer) {
117
            $sigma *= ($target[$key] - $neuronOutput);
118
        } else {
119
            $sigma *= $this->getPrevSigma($neuron);
120
        }
121
122
        $this->sigmas[] = new Sigma($neuron, $sigma);
123
124
        return $sigma;
125
    }
126
127
    /**
128
     * @param Neuron $neuron
129
     * 
130
     * @return float
131
     */
132
    private function getPrevSigma(Neuron $neuron): float
133
    {
134
        $sigma = 0.0;
135
136
        foreach ($this->sigmas as $neuronSigma) {
137
            $sigma += $neuronSigma->getSigmaForNeuron($neuron);
138
        }
139
140
        return $sigma;
141
    }
142
143
    /**
144
     * @param array $result
145
     * @param array $target
146
     * @param float $desiredError
147
     *
148
     * @return bool
149
     */
150
    private function isResultWithinError(array $result, array $target, float $desiredError)
151
    {
152
        foreach ($target as $key => $value) {
153
            if ($result[$key] > $value + $desiredError || $result[$key] < $value - $desiredError) {
154
                return false;
155
            }
156
        }
157
158
        return true;
159
    }
160
}
161