Passed
Pull Request — master (#89)
by
unknown
03:38
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 int
26
     */
27
    private $maxIterations;
28
29
    /**
30
     * @var array
31
     */
32
    private $sigmas;
33
34
    /**
35
     * @var array
36
     */
37
    private $prevSigmas;
38
39
    /**
40
     * @param Network $network
41
     * @param int     $theta
42
     */
43
    public function __construct(Network $network, int $theta = 1, int $maxIterations = 10000)
44
    {
45
        $this->network = $network;
46
        $this->theta = $theta;
47
        $this->maxIterations = $maxIterations;
48
    }
49
50
    /**
51
     * @param array $samples
52
     * @param array $targets
53
     */
54
    public function train(array $samples, array $targets)
55
    {
56
        for ($i = 0; $i < $this->maxIterations; ++$i) {
57
            $this->trainSamples($samples, $targets);
58
        }
59
    }
60
61
    /**
62
     * @param array $samples
63
     * @param array $targets
64
     */
65
    private function trainSamples(array $samples, array $targets)
66
    {
67
        foreach ($targets as $key => $target) {
68
            $this->trainSample($samples[$key], $target);
69
        }
70
    }
71
72
    /**
73
     * @param array $sample
74
     * @param mixed $target
75
     */
76
    private function trainSample(array $sample, $target)
77
    {
78
79
        // Feed-forward.
80
        $this->network->setInput($sample)->getOutput();
81
82
        $layers = $this->network->getLayers();
83
        $layersNumber = count($layers);
84
85
        $targetClass = $this->network->getTargetClass($target);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Phpml\NeuralNetwork\Network as the method getTargetClass() does only exist in the following implementations of said interface: Phpml\NeuralNetwork\Network\MultilayerPerceptron.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
86
87
        // Backpropagation.
88
        for ($i = $layersNumber; $i > 1; --$i) {
89
            $this->sigmas = [];
90
            foreach ($layers[$i - 1]->getNodes() as $key => $neuron) {
91
92
                if ($neuron instanceof Neuron) {
93
                    $sigma = $this->getSigma($neuron, $targetClass, $key, $i == $layersNumber);
94
                    foreach ($neuron->getSynapses() as $synapse) {
95
                        $synapse->changeWeight($this->theta * $sigma * $synapse->getNode()->getOutput());
96
                    }
97
                }
98
            }
99
            $this->prevSigmas = $this->sigmas;
100
        }
101
    }
102
103
    /**
104
     * @param Neuron $neuron
105
     * @param int    $targetClass
106
     * @param int    $key
107
     * @param bool   $lastLayer
108
     *
109
     * @return float
110
     */
111
    private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer): float
112
    {
113
        $neuronOutput = $neuron->getOutput();
114
        $sigma = $neuronOutput * (1 - $neuronOutput);
115
116
        if ($lastLayer) {
117
            if ($targetClass == $key) {
118
                $value = 1;
119
            } else {
120
                $value = 0;
121
            }
122
            $sigma *= ($value - $neuronOutput);
123
        } else {
124
            $sigma *= $this->getPrevSigma($neuron);
125
        }
126
127
        $this->sigmas[] = new Sigma($neuron, $sigma);
128
129
        return $sigma;
130
    }
131
132
    /**
133
     * @param Neuron $neuron
134
     *
135
     * @return float
136
     */
137
    private function getPrevSigma(Neuron $neuron): float
138
    {
139
        $sigma = 0.0;
140
141
        foreach ($this->prevSigmas as $neuronSigma) {
142
            $sigma += $neuronSigma->getSigmaForNeuron($neuron);
143
        }
144
145
        return $sigma;
146
    }
147
}
148