Passed
Push — master ( c4f58f...c4ad11 )
by Arkadiusz
02:22
created

MultilayerPerceptron::setLearningRate()   A

Complexity

Conditions 1
Paths 1

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 5
Code Lines 3

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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 array
25
     */
26
    protected $classes = [];
27
28
    /**
29
     * @var ActivationFunction
30
     */
31
    protected $activationFunction;
32
33
    /**
34
     * @var Backpropagation
35
     */
36
    protected $backpropagation = null;
37
38
    /**
39
     * @var int
40
     */
41
    private $inputLayerFeatures;
42
43
    /**
44
     * @var array
45
     */
46
    private $hiddenLayers = [];
47
48
    /**
49
     * @var float
50
     */
51
    private $learningRate;
52
53
    /**
54
     * @throws InvalidArgumentException
55
     */
56
    public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ?ActivationFunction $activationFunction = null, float $learningRate = 1)
57
    {
58
        if (empty($hiddenLayers)) {
59
            throw InvalidArgumentException::invalidLayersNumber();
60
        }
61
62
        if (count($classes) < 2) {
63
            throw InvalidArgumentException::invalidClassesNumber();
64
        }
65
66
        $this->classes = array_values($classes);
67
        $this->iterations = $iterations;
0 ignored issues
show
Bug introduced by David Monllaó
The property iterations does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

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