From d29c5906df6c736f1e8775b0c23b0b3b2afafa6b Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Mon, 15 Oct 2018 19:47:42 +0200 Subject: [PATCH] Return labels in MultilayerPerceptron output (#315) --- src/Classification/MLPClassifier.php | 5 ++- src/NeuralNetwork/Layer.php | 5 +-- src/NeuralNetwork/Network/LayeredNetwork.php | 2 -- .../Network/MultilayerPerceptron.php | 14 ++++++++ src/NeuralNetwork/Node/Neuron.php | 2 +- tests/Classification/MLPClassifierTest.php | 11 ++++-- .../Network/MultilayerPerceptronTest.php | 34 +++++++++++++++++++ 7 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/Classification/MLPClassifier.php b/src/Classification/MLPClassifier.php index 13963f3..35678d5 100644 --- a/src/Classification/MLPClassifier.php +++ b/src/Classification/MLPClassifier.php @@ -41,7 +41,7 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier } } - return $this->classes[$predictedClass]; + return $predictedClass; } /** @@ -49,9 +49,8 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier */ protected function trainSample(array $sample, $target): void { - // Feed-forward. - $this->setInput($sample)->getOutput(); + $this->setInput($sample); // Back-propagate. $this->backpropagation->backpropagate($this->getLayers(), $this->getTargetClass($target)); diff --git a/src/NeuralNetwork/Layer.php b/src/NeuralNetwork/Layer.php index 1c681f8..1c67c04 100644 --- a/src/NeuralNetwork/Layer.php +++ b/src/NeuralNetwork/Layer.php @@ -41,12 +41,9 @@ class Layer return $this->nodes; } - /** - * @return Neuron - */ private function createNode(string $nodeClass, ?ActivationFunction $activationFunction = null): Node { - if ($nodeClass == Neuron::class) { + if ($nodeClass === Neuron::class) { return new Neuron($activationFunction); } diff --git a/src/NeuralNetwork/Network/LayeredNetwork.php b/src/NeuralNetwork/Network/LayeredNetwork.php index 4f05398..03bfef5 100644 --- a/src/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/NeuralNetwork/Network/LayeredNetwork.php @@ -51,8 +51,6 @@ abstract class LayeredNetwork implements Network /** * @param mixed $input - * - * @return $this */ public function setInput($input): Network { diff --git a/src/NeuralNetwork/Network/MultilayerPerceptron.php b/src/NeuralNetwork/Network/MultilayerPerceptron.php index 3626063..8ff49bc 100644 --- a/src/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/NeuralNetwork/Network/MultilayerPerceptron.php @@ -69,6 +69,10 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, throw new InvalidArgumentException('Provide at least 2 different classes'); } + if (count($classes) !== count(array_unique($classes))) { + throw new InvalidArgumentException('Classes must be unique'); + } + $this->classes = array_values($classes); $this->iterations = $iterations; $this->inputLayerFeatures = $inputLayerFeatures; @@ -109,6 +113,16 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $this->backpropagation->setLearningRate($this->learningRate); } + public function getOutput(): array + { + $result = []; + foreach ($this->getOutputLayer()->getNodes() as $i => $neuron) { + $result[$this->classes[$i]] = $neuron->getOutput(); + } + + return $result; + } + /** * @param mixed $target */ diff --git a/src/NeuralNetwork/Node/Neuron.php b/src/NeuralNetwork/Node/Neuron.php index 47d606d..c537606 100644 --- a/src/NeuralNetwork/Node/Neuron.php +++ b/src/NeuralNetwork/Node/Neuron.php @@ -44,7 +44,7 @@ class Neuron implements Node /** * @return Synapse[] */ - public function getSynapses() + public function getSynapses(): array { return $this->synapses; } diff --git a/tests/Classification/MLPClassifierTest.php b/tests/Classification/MLPClassifierTest.php index d3680b6..ae0871d 100644 --- a/tests/Classification/MLPClassifierTest.php +++ b/tests/Classification/MLPClassifierTest.php @@ -183,7 +183,7 @@ class MLPClassifierTest extends TestCase $testSamples = [[0, 0], [1, 0], [0, 1], [1, 1]]; $predicted = $classifier->predict($testSamples); - $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid(); + $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid('', false); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); @@ -204,7 +204,7 @@ class MLPClassifierTest extends TestCase $this->assertEquals('a', $network->predict([1, 0])); $this->assertEquals('b', $network->predict([0, 1])); - $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid(); + $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid('', false); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($network, $filepath); @@ -245,6 +245,13 @@ class MLPClassifierTest extends TestCase new MLPClassifier(2, [2], [0]); } + public function testOutputWithLabels(): void + { + $output = (new MLPClassifier(2, [2, 2], ['T', 'F']))->getOutput(); + + $this->assertEquals(['T', 'F'], array_keys($output)); + } + private function getSynapsesNodes(array $synapses): array { $nodes = []; diff --git a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php index 006733f..cea2a2f 100644 --- a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Tests\NeuralNetwork\Network; +use Phpml\Exception\InvalidArgumentException; use Phpml\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network\MultilayerPerceptron; @@ -13,6 +14,39 @@ use PHPUnit_Framework_MockObject_MockObject; class MultilayerPerceptronTest extends TestCase { + public function testThrowExceptionWhenHiddenLayersAreEmpty(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Provide at least 1 hidden layer'); + + $this->getMockForAbstractClass( + MultilayerPerceptron::class, + [5, [], [0, 1], 1000, null, 0.42] + ); + } + + public function testThrowExceptionWhenThereIsOnlyOneClass(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Provide at least 2 different classes'); + + $this->getMockForAbstractClass( + MultilayerPerceptron::class, + [5, [3], [0], 1000, null, 0.42] + ); + } + + public function testThrowExceptionWhenClassesAreNotUnique(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Classes must be unique'); + + $this->getMockForAbstractClass( + MultilayerPerceptron::class, + [5, [3], [0, 1, 2, 3, 1], 1000, null, 0.42] + ); + } + public function testLearningRateSetter(): void { /** @var MultilayerPerceptron $mlp */