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
|
|||
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 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: