Passed
Push — master ( fbbe5c...a34811 )
by Arkadiusz
07:00
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 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
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;
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