Passed
Push — master ( 333598...b1d40b )
by Arkadiusz
02:18
created

NeuralNetwork/Network/MultilayerPerceptron.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Phpml\NeuralNetwork\Network;
6
7
use Phpml\Estimator;
8
use Phpml\Exception\InvalidArgumentException;
9
use Phpml\Helper\Predictable;
10
use Phpml\IncrementalEstimator;
11
use Phpml\NeuralNetwork\ActivationFunction;
12
use Phpml\NeuralNetwork\Layer;
13
use Phpml\NeuralNetwork\Node\Bias;
14
use Phpml\NeuralNetwork\Node\Input;
15
use Phpml\NeuralNetwork\Node\Neuron;
16
use Phpml\NeuralNetwork\Node\Neuron\Synapse;
17
use Phpml\NeuralNetwork\Training\Backpropagation;
18
19
abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, IncrementalEstimator
20
{
21
    use Predictable;
22
23
    /**
24
     * @var int
25
     */
26
    private $inputLayerFeatures;
27
28
    /**
29
     * @var array
30
     */
31
    private $hiddenLayers;
32
33
    /**
34
     * @var array
35
     */
36
    protected $classes = [];
37
38
    /**
39
     * @var int
40
     */
41
    private $iterations;
42
43
    /**
44
     * @var ActivationFunction
45
     */
46
    protected $activationFunction;
47
48
    /**
49
     * @var float
50
     */
51
    private $learningRate;
52
53
    /**
54
     * @var Backpropagation
55
     */
56
    protected $backpropagation = null;
57
58
    /**
59
     * @throws InvalidArgumentException
60
     */
61
    public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ?ActivationFunction $activationFunction = null, float $learningRate = 1)
62
    {
63
        if (empty($hiddenLayers)) {
64
            throw InvalidArgumentException::invalidLayersNumber();
65
        }
66
67
        if (count($classes) < 2) {
68
            throw InvalidArgumentException::invalidClassesNumber();
69
        }
70
71
        $this->classes = array_values($classes);
72
        $this->iterations = $iterations;
73
        $this->inputLayerFeatures = $inputLayerFeatures;
74
        $this->hiddenLayers = $hiddenLayers;
75
        $this->activationFunction = $activationFunction;
76
        $this->learningRate = $learningRate;
0 ignored issues
show
Documentation Bug introduced by
It seems like $learningRate can also be of type integer. However, the property $learningRate is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
77
78
        $this->initNetwork();
79
    }
80
81
    private function initNetwork(): void
82
    {
83
        $this->addInputLayer($this->inputLayerFeatures);
84
        $this->addNeuronLayers($this->hiddenLayers, $this->activationFunction);
85
        $this->addNeuronLayers([count($this->classes)], $this->activationFunction);
86
87
        $this->addBiasNodes();
88
        $this->generateSynapses();
89
90
        $this->backpropagation = new Backpropagation($this->learningRate);
91
    }
92
93
    public function train(array $samples, array $targets): void
94
    {
95
        $this->reset();
96
        $this->initNetwork();
97
        $this->partialTrain($samples, $targets, $this->classes);
98
    }
99
100
    /**
101
     * @throws InvalidArgumentException
102
     */
103
    public function partialTrain(array $samples, array $targets, array $classes = []): void
104
    {
105
        if (!empty($classes) && array_values($classes) !== $this->classes) {
106
            // We require the list of classes in the constructor.
107
            throw InvalidArgumentException::inconsistentClasses();
108
        }
109
110
        for ($i = 0; $i < $this->iterations; ++$i) {
111
            $this->trainSamples($samples, $targets);
112
        }
113
    }
114
115
    /**
116
     * @param mixed $target
117
     */
118
    abstract protected function trainSample(array $sample, $target);
119
120
    /**
121
     * @return mixed
122
     */
123
    abstract protected function predictSample(array $sample);
124
125
    protected function reset(): void
126
    {
127
        $this->removeLayers();
128
    }
129
130
    private function addInputLayer(int $nodes): void
131
    {
132
        $this->addLayer(new Layer($nodes, Input::class));
133
    }
134
135
    private function addNeuronLayers(array $layers, ?ActivationFunction $activationFunction = null): void
136
    {
137
        foreach ($layers as $neurons) {
138
            $this->addLayer(new Layer($neurons, Neuron::class, $activationFunction));
139
        }
140
    }
141
142
    private function generateSynapses(): void
143
    {
144
        $layersNumber = count($this->layers) - 1;
145
        for ($i = 0; $i < $layersNumber; ++$i) {
146
            $currentLayer = $this->layers[$i];
147
            $nextLayer = $this->layers[$i + 1];
148
            $this->generateLayerSynapses($nextLayer, $currentLayer);
149
        }
150
    }
151
152
    private function addBiasNodes(): void
153
    {
154
        $biasLayers = count($this->layers) - 1;
155
        for ($i = 0; $i < $biasLayers; ++$i) {
156
            $this->layers[$i]->addNode(new Bias());
157
        }
158
    }
159
160
    private function generateLayerSynapses(Layer $nextLayer, Layer $currentLayer): void
161
    {
162
        foreach ($nextLayer->getNodes() as $nextNeuron) {
163
            if ($nextNeuron instanceof Neuron) {
164
                $this->generateNeuronSynapses($currentLayer, $nextNeuron);
165
            }
166
        }
167
    }
168
169
    private function generateNeuronSynapses(Layer $currentLayer, Neuron $nextNeuron): void
170
    {
171
        foreach ($currentLayer->getNodes() as $currentNeuron) {
172
            $nextNeuron->addSynapse(new Synapse($currentNeuron));
173
        }
174
    }
175
176
    private function trainSamples(array $samples, array $targets): void
177
    {
178
        foreach ($targets as $key => $target) {
179
            $this->trainSample($samples[$key], $target);
180
        }
181
    }
182
}
183