Passed
Pull Request — master (#160)
by
unknown
02:12
created

MultilayerPerceptron::generateNeuronSynapses()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 2
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
    public function setLearningRate(float $learningRate)
116
    {
117
        $this->learningRate = $learningRate;
118
        $this->backpropagation->setLearningRate($this->learningRate);
119
    }
120
121
    /**
122
     * @param mixed $target
123
     */
124
    abstract protected function trainSample(array $sample, $target);
125
126
    /**
127
     * @return mixed
128
     */
129
    abstract protected function predictSample(array $sample);
130
131
    protected function reset(): void
132
    {
133
        $this->removeLayers();
134
    }
135
136
    private function addInputLayer(int $nodes): void
137
    {
138
        $this->addLayer(new Layer($nodes, Input::class));
139
    }
140
141
    private function addNeuronLayers(array $layers, ?ActivationFunction $activationFunction = null): void
142
    {
143
        foreach ($layers as $neurons) {
144
            $this->addLayer(new Layer($neurons, Neuron::class, $activationFunction));
145
        }
146
    }
147
148
    private function generateSynapses(): void
149
    {
150
        $layersNumber = count($this->layers) - 1;
151
        for ($i = 0; $i < $layersNumber; ++$i) {
152
            $currentLayer = $this->layers[$i];
153
            $nextLayer = $this->layers[$i + 1];
154
            $this->generateLayerSynapses($nextLayer, $currentLayer);
155
        }
156
    }
157
158
    private function addBiasNodes(): void
159
    {
160
        $biasLayers = count($this->layers) - 1;
161
        for ($i = 0; $i < $biasLayers; ++$i) {
162
            $this->layers[$i]->addNode(new Bias());
163
        }
164
    }
165
166
    private function generateLayerSynapses(Layer $nextLayer, Layer $currentLayer): void
167
    {
168
        foreach ($nextLayer->getNodes() as $nextNeuron) {
169
            if ($nextNeuron instanceof Neuron) {
170
                $this->generateNeuronSynapses($currentLayer, $nextNeuron);
171
            }
172
        }
173
    }
174
175
    private function generateNeuronSynapses(Layer $currentLayer, Neuron $nextNeuron): void
176
    {
177
        foreach ($currentLayer->getNodes() as $currentNeuron) {
178
            $nextNeuron->addSynapse(new Synapse($currentNeuron));
179
        }
180
    }
181
182
    private function trainSamples(array $samples, array $targets): void
183
    {
184
        foreach ($targets as $key => $target) {
185
            $this->trainSample($samples[$key], $target);
186
        }
187
    }
188
}
189