Passed
Push — master ( 3dff40...de5049 )
by Arkadiusz
03:58
created

MultilayerPerceptron::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Phpml\NeuralNetwork\Network;
6
7
use Phpml\Estimator;
8
use Phpml\IncrementalEstimator;
9
use Phpml\Exception\InvalidArgumentException;
10
use Phpml\NeuralNetwork\Training\Backpropagation;
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\Helper\Predictable;
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 int
50
     */
51
    private $theta;
52
53
    /**
54
     * @var Backpropagation
55
     */
56
    protected $backpropagation = null;
57
58
    /**
59
     * @param int                     $inputLayerFeatures
60
     * @param array                   $hiddenLayers
61
     * @param array                   $classes
62
     * @param int                     $iterations
63
     * @param ActivationFunction|null $activationFunction
64
     * @param int                     $theta
65
     *
66
     * @throws InvalidArgumentException
67
     */
68
    public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ActivationFunction $activationFunction = null, int $theta = 1)
69
    {
70
        if (empty($hiddenLayers)) {
71
            throw InvalidArgumentException::invalidLayersNumber();
72
        }
73
74
        if (count($classes) < 2) {
75
            throw InvalidArgumentException::invalidClassesNumber();
76
        }
77
78
        $this->classes = array_values($classes);
79
        $this->iterations = $iterations;
80
        $this->inputLayerFeatures = $inputLayerFeatures;
81
        $this->hiddenLayers = $hiddenLayers;
82
        $this->activationFunction = $activationFunction;
83
        $this->theta = $theta;
84
85
        $this->initNetwork();
86
    }
87
88
    /**
89
     * @return void
90
     */
91
    private function initNetwork()
92
    {
93
        $this->addInputLayer($this->inputLayerFeatures);
94
        $this->addNeuronLayers($this->hiddenLayers, $this->activationFunction);
95
        $this->addNeuronLayers([count($this->classes)], $this->activationFunction);
96
97
        $this->addBiasNodes();
98
        $this->generateSynapses();
99
100
        $this->backpropagation = new Backpropagation($this->theta);
101
    }
102
103
    /**
104
     * @param array $samples
105
     * @param array $targets
106
     */
107
    public function train(array $samples, array $targets)
108
    {
109
        $this->reset();
110
        $this->initNetwork();
111
        $this->partialTrain($samples, $targets, $this->classes);
112
    }
113
114
    /**
115
     * @param array $samples
116
     * @param array $targets
117
     */
118
    public function partialTrain(array $samples, array $targets, array $classes = [])
119
    {
120
        if (!empty($classes) && array_values($classes) !== $this->classes) {
121
            // We require the list of classes in the constructor.
122
            throw InvalidArgumentException::inconsistentClasses();
123
        }
124
125
        for ($i = 0; $i < $this->iterations; ++$i) {
126
            $this->trainSamples($samples, $targets);
127
        }
128
    }
129
130
    /**
131
     * @param array $sample
132
     * @param mixed $target
133
     */
134
    abstract protected function trainSample(array $sample, $target);
135
136
    /**
137
     * @param array $sample
138
     * @return mixed
139
     */
140
    abstract protected function predictSample(array $sample);
141
142
    /**
143
     * @return void
144
     */
145
    protected function reset()
146
    {
147
        $this->removeLayers();
148
    }
149
150
    /**
151
     * @param int $nodes
152
     */
153
    private function addInputLayer(int $nodes)
154
    {
155
        $this->addLayer(new Layer($nodes, Input::class));
156
    }
157
158
    /**
159
     * @param array                   $layers
160
     * @param ActivationFunction|null $activationFunction
161
     */
162
    private function addNeuronLayers(array $layers, ActivationFunction $activationFunction = null)
163
    {
164
        foreach ($layers as $neurons) {
165
            $this->addLayer(new Layer($neurons, Neuron::class, $activationFunction));
166
        }
167
    }
168
169
    private function generateSynapses()
170
    {
171
        $layersNumber = count($this->layers) - 1;
172
        for ($i = 0; $i < $layersNumber; ++$i) {
173
            $currentLayer = $this->layers[$i];
174
            $nextLayer = $this->layers[$i + 1];
175
            $this->generateLayerSynapses($nextLayer, $currentLayer);
176
        }
177
    }
178
179
    private function addBiasNodes()
180
    {
181
        $biasLayers = count($this->layers) - 1;
182
        for ($i = 0; $i < $biasLayers; ++$i) {
183
            $this->layers[$i]->addNode(new Bias());
184
        }
185
    }
186
187
    /**
188
     * @param Layer $nextLayer
189
     * @param Layer $currentLayer
190
     */
191
    private function generateLayerSynapses(Layer $nextLayer, Layer $currentLayer)
192
    {
193
        foreach ($nextLayer->getNodes() as $nextNeuron) {
194
            if ($nextNeuron instanceof Neuron) {
195
                $this->generateNeuronSynapses($currentLayer, $nextNeuron);
196
            }
197
        }
198
    }
199
200
    /**
201
     * @param Layer  $currentLayer
202
     * @param Neuron $nextNeuron
203
     */
204
    private function generateNeuronSynapses(Layer $currentLayer, Neuron $nextNeuron)
205
    {
206
        foreach ($currentLayer->getNodes() as $currentNeuron) {
207
            $nextNeuron->addSynapse(new Synapse($currentNeuron));
208
        }
209
    }
210
211
    /**
212
     * @param array $samples
213
     * @param array $targets
214
     */
215
    private function trainSamples(array $samples, array $targets)
216
    {
217
        foreach ($targets as $key => $target) {
218
            $this->trainSample($samples[$key], $target);
219
        }
220
    }
221
}
222